procfs remembers the open files of an entry that has a ->proc_release(),
so that it can call the release hook when the entry is removed while
files are still open. The list head for that and the lock that protects
it sit in every struct proc_dir_entry, for the few entries that need
them, and the next patches want to keep more there, the vm_operations
of an entry's mappings for a start.

Move the list into a struct pde_open_node that only those entries get.
It is allocated when the entry is registered, whether an entry needs
one follows from its proc_ops, and freed with the entry. So ->open is
constant for the life of an entry, nothing has to look it up or pin it,
and the removal path stays what it is. kernfs allocates its
kernfs_open_node on the first open instead, but there every attribute
can be polled and so every one of them would need one. In procfs the
entries that need the node are the exception. Permanent entries don't
get one, they are never removed, so their release hook only ever runs
when a file is closed.

The spinlock stays in the entry and now protects what hangs off ->open.
Moving it behind the gid fills a hole, so struct proc_dir_entry shrinks
from 160 to 144 bytes on x86_64 here, and it ends up at 160 bytes at
the end of this series instead of the 184 it would have grown to with
the read and write limits and the vm_operations of its mappings in it.
That keeps 32 bytes of inline name instead of 8. 34 of the 68 /proc/net
names a network namespace gets are longer than 7 characters.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 fs/proc/generic.c  | 20 +++++++++++++++++++-
 fs/proc/inode.c    | 23 +++++++++++++----------
 fs/proc/internal.h | 16 +++++++++++++---
 3 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index f6d620a5c3d2..84f064a651e4 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -40,6 +40,7 @@ void pde_free(struct proc_dir_entry *pde)
                kfree(pde->data);
        if (pde->name != pde->inline_name)
                kfree(pde->name);
+       kfree(pde->open);
        kmem_cache_free(proc_dir_entry_cache, pde);
 }
 
@@ -445,6 +446,18 @@ static void pde_set_flags(struct proc_dir_entry *pde)
                pde->flags |= PROC_ENTRY_proc_lseek;
 }
 
+/*
+ * procfs has to know the open files of an entry whose ->proc_release() it
+ * calls at removal. A permanent entry is never removed and skips all that,
+ * see proc_reg_open().
+ */
+static bool pde_needs_open_node(const struct proc_dir_entry *pde)
+{
+       if (!S_ISREG(pde->mode) || pde_is_permanent(pde))
+               return false;
+       return pde->proc_ops->proc_release;
+}
+
 /* returns the registered entry, or frees dp and returns NULL on failure */
 struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
                struct proc_dir_entry *dp)
@@ -454,6 +467,12 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry 
*dir,
 
        if (!S_ISDIR(dp->mode))
                pde_set_flags(dp);
+       if (pde_needs_open_node(dp)) {
+               dp->open = kzalloc(sizeof(*dp->open), GFP_KERNEL);
+               if (!dp->open)
+                       goto out_free_inum;
+               INIT_LIST_HEAD(&dp->open->files);
+       }
 
        write_lock(&proc_subdir_lock);
        dp->parent = dir;
@@ -533,7 +552,6 @@ static struct proc_dir_entry *__proc_create(struct 
proc_dir_entry **parent,
        ent->subdir = RB_ROOT;
        refcount_set(&ent->refcnt, 1);
        spin_lock_init(&ent->pde_unload_lock);
-       INIT_LIST_HEAD(&ent->pde_openers);
        proc_set_user(ent, (*parent)->uid, (*parent)->gid);
 
        /* Revalidate everything under /proc/${pid}/net */
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 8795add1898d..56eab69e1dcc 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -271,12 +271,13 @@ void proc_entry_rundown(struct proc_dir_entry *de)
        if (atomic_add_return(BIAS, &de->in_use) != BIAS)
                wait_var_event(&de->in_use, atomic_read(&de->in_use) == BIAS);
 
-       /* ->pde_openers list can't grow from now on. */
-
+       /* ->open->files can't grow from now on. */
+       if (!de->open)
+               return;
        spin_lock(&de->pde_unload_lock);
-       while (!list_empty(&de->pde_openers)) {
+       while (!list_empty(&de->open->files)) {
                struct pde_opener *pdeo;
-               pdeo = list_first_entry(&de->pde_openers, struct pde_opener, 
lh);
+               pdeo = list_first_entry(&de->open->files, struct pde_opener, 
lh);
                close_pdeo(de, pdeo);
                spin_lock(&de->pde_unload_lock);
        }
@@ -499,13 +500,13 @@ static int proc_reg_open(struct inode *inode, struct file 
*file)
         * 2) rmmod isn't blocked by opening file in /proc and sitting on
         *    the descriptor (including "rmmod foo </proc/foo" scenario).
         *
-        * Save every "struct file" with custom ->release hook.
+        * Save every "struct file" with custom ->release hook. Those entries
+        * have an open node, see proc_register().
         */
        if (!use_pde(pde))
                return -ENOENT;
 
-       const auto release = pde->proc_ops->proc_release;
-       if (release) {
+       if (pde->open) {
                pdeo = kmem_cache_alloc(pde_opener_cache, GFP_KERNEL);
                if (!pdeo) {
                        rv = -ENOMEM;
@@ -517,14 +518,14 @@ static int proc_reg_open(struct inode *inode, struct file 
*file)
        if (open)
                rv = open(inode, file);
 
-       if (release) {
+       if (pde->open) {
                if (rv == 0) {
                        /* To know what to release. */
                        pdeo->file = file;
                        pdeo->closing = false;
                        pdeo->c = NULL;
                        spin_lock(&pde->pde_unload_lock);
-                       list_add(&pdeo->lh, &pde->pde_openers);
+                       list_add(&pdeo->lh, &pde->open->files);
                        spin_unlock(&pde->pde_unload_lock);
                } else
                        kmem_cache_free(pde_opener_cache, pdeo);
@@ -547,8 +548,10 @@ static int proc_reg_release(struct inode *inode, struct 
file *file)
                return 0;
        }
 
+       if (!pde->open)
+               return 0;
        spin_lock(&pde->pde_unload_lock);
-       list_for_each_entry(pdeo, &pde->pde_openers, lh) {
+       list_for_each_entry(pdeo, &pde->open->files, lh) {
                if (pdeo->file == file) {
                        close_pdeo(pde, pdeo);
                        return 0;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index ab4d7bd1fb75..33776bc00ff0 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -35,9 +35,6 @@ struct proc_dir_entry {
         */
        atomic_t in_use;
        refcount_t refcnt;
-       struct list_head pde_openers;   /* who did ->open, but not ->release */
-       /* protects ->pde_openers and all struct pde_opener instances */
-       spinlock_t pde_unload_lock;
        const struct inode_operations *proc_iops;
        union {
                const struct proc_ops *proc_ops;
@@ -47,6 +44,8 @@ struct proc_dir_entry {
                const struct seq_operations *seq_ops;
                int (*single_show)(struct seq_file *, void *);
        };
+       /* the open files procfs has to know, see struct pde_open_node */
+       struct pde_open_node *open;
        proc_write_t write;
        void *data;
        unsigned int state_size;
@@ -54,6 +53,8 @@ struct proc_dir_entry {
        nlink_t nlink;
        kuid_t uid;
        kgid_t gid;
+       /* protects ->open->files and all struct pde_opener instances */
+       spinlock_t pde_unload_lock;
        loff_t size;
        struct proc_dir_entry *parent;
        struct rb_root subdir;
@@ -325,6 +326,15 @@ struct pde_opener {
        bool closing;
        struct completion *c;
 } __randomize_layout;
+
+/*
+ * The open files of an entry whose ->proc_release() procfs has to call when
+ * the entry is removed while they are still open. Allocated with the entry,
+ * see proc_register().
+ */
+struct pde_open_node {
+       struct list_head files;         /* who did ->open, but not ->release */
+};
 extern const struct inode_operations proc_link_inode_operations;
 extern const struct inode_operations proc_pid_link_inode_operations;
 extern const struct super_operations proc_sops;

-- 
2.53.0

Reply via email to