proc: fix proc_dir_entry refcounting
[safe/jmp/linux-2.6] / fs / proc / generic.c
index 20e5c45..8d49838 100644 (file)
@@ -19,6 +19,8 @@
 #include <linux/idr.h>
 #include <linux/namei.h>
 #include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -29,14 +31,16 @@ static ssize_t proc_file_write(struct file *file, const char __user *buffer,
                               size_t count, loff_t *ppos);
 static loff_t proc_file_lseek(struct file *, loff_t, int);
 
-int proc_match(int len, const char *name, struct proc_dir_entry *de)
+DEFINE_SPINLOCK(proc_subdir_lock);
+
+static int proc_match(int len, const char *name, struct proc_dir_entry *de)
 {
        if (de->namelen != len)
                return 0;
        return !memcmp(name, de->name, len);
 }
 
-static struct file_operations proc_file_operations = {
+static const struct file_operations proc_file_operations = {
        .llseek         = proc_file_lseek,
        .read           = proc_file_read,
        .write          = proc_file_write,
@@ -49,7 +53,7 @@ static ssize_t
 proc_file_read(struct file *file, char __user *buf, size_t nbytes,
               loff_t *ppos)
 {
-       struct inode * inode = file->f_dentry->d_inode;
+       struct inode * inode = file->f_path.dentry->d_inode;
        char    *page;
        ssize_t retval=0;
        int     eof=0;
@@ -70,7 +74,7 @@ proc_file_read(struct file *file, char __user *buf, size_t nbytes,
                nbytes = MAX_NON_LFS - pos;
 
        dp = PDE(inode);
-       if (!(page = (char*) __get_free_page(GFP_KERNEL)))
+       if (!(page = (char*) __get_free_page(GFP_TEMPORARY)))
                return -ENOMEM;
 
        while ((nbytes > 0) && !eof) {
@@ -200,7 +204,7 @@ static ssize_t
 proc_file_write(struct file *file, const char __user *buffer,
                size_t count, loff_t *ppos)
 {
-       struct inode *inode = file->f_dentry->d_inode;
+       struct inode *inode = file->f_path.dentry->d_inode;
        struct proc_dir_entry * dp;
        
        dp = PDE(inode);
@@ -262,7 +266,7 @@ static int proc_getattr(struct vfsmount *mnt, struct dentry *dentry,
        return 0;
 }
 
-static struct inode_operations proc_file_inode_operations = {
+static const struct inode_operations proc_file_inode_operations = {
        .setattr        = proc_notify_change,
 };
 
@@ -277,7 +281,9 @@ static int xlate_proc_name(const char *name,
        const char              *cp = name, *next;
        struct proc_dir_entry   *de;
        int                     len;
+       int                     rtn = 0;
 
+       spin_lock(&proc_subdir_lock);
        de = &proc_root;
        while (1) {
                next = strchr(cp, '/');
@@ -289,13 +295,17 @@ static int xlate_proc_name(const char *name,
                        if (proc_match(len, cp, de))
                                break;
                }
-               if (!de)
-                       return -ENOENT;
+               if (!de) {
+                       rtn = -ENOENT;
+                       goto out;
+               }
                cp += len + 1;
        }
        *residual = cp;
        *ret = de;
-       return 0;
+out:
+       spin_unlock(&proc_subdir_lock);
+       return rtn;
 }
 
 static DEFINE_IDR(proc_inum_idr);
@@ -348,7 +358,7 @@ static void *proc_follow_link(struct dentry *dentry, struct nameidata *nd)
        return NULL;
 }
 
-static struct inode_operations proc_link_inode_operations = {
+static const struct inode_operations proc_link_inode_operations = {
        .readlink       = generic_readlink,
        .follow_link    = proc_follow_link,
 };
@@ -364,9 +374,16 @@ static int proc_delete_dentry(struct dentry * dentry)
        return 1;
 }
 
+static int proc_revalidate_dentry(struct dentry *dentry, struct nameidata *nd)
+{
+       d_drop(dentry);
+       return 0;
+}
+
 static struct dentry_operations proc_dentry_operations =
 {
        .d_delete       = proc_delete_dentry,
+       .d_revalidate   = proc_revalidate_dentry,
 };
 
 /*
@@ -380,20 +397,28 @@ struct dentry *proc_lookup(struct inode * dir, struct dentry *dentry, struct nam
        int error = -ENOENT;
 
        lock_kernel();
+       spin_lock(&proc_subdir_lock);
        de = PDE(dir);
        if (de) {
                for (de = de->subdir; de ; de = de->next) {
                        if (de->namelen != dentry->d_name.len)
                                continue;
                        if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
-                               unsigned int ino = de->low_ino;
+                               unsigned int ino;
 
+                               if (de->shadow_proc)
+                                       de = de->shadow_proc(current, de);
+                               ino = de->low_ino;
+                               de_get(de);
+                               spin_unlock(&proc_subdir_lock);
                                error = -EINVAL;
                                inode = proc_get_inode(dir->i_sb, ino, de);
+                               spin_lock(&proc_subdir_lock);
                                break;
                        }
                }
        }
+       spin_unlock(&proc_subdir_lock);
        unlock_kernel();
 
        if (inode) {
@@ -401,6 +426,7 @@ struct dentry *proc_lookup(struct inode * dir, struct dentry *dentry, struct nam
                d_add(dentry, inode);
                return NULL;
        }
+       de_put(de);
        return ERR_PTR(error);
 }
 
@@ -419,7 +445,7 @@ int proc_readdir(struct file * filp,
        struct proc_dir_entry * de;
        unsigned int ino;
        int i;
-       struct inode *inode = filp->f_dentry->d_inode;
+       struct inode *inode = filp->f_path.dentry->d_inode;
        int ret = 0;
 
        lock_kernel();
@@ -440,18 +466,20 @@ int proc_readdir(struct file * filp,
                        /* fall through */
                case 1:
                        if (filldir(dirent, "..", 2, i,
-                                   parent_ino(filp->f_dentry),
+                                   parent_ino(filp->f_path.dentry),
                                    DT_DIR) < 0)
                                goto out;
                        i++;
                        filp->f_pos++;
                        /* fall through */
                default:
+                       spin_lock(&proc_subdir_lock);
                        de = de->subdir;
                        i -= 2;
                        for (;;) {
                                if (!de) {
                                        ret = 1;
+                                       spin_unlock(&proc_subdir_lock);
                                        goto out;
                                }
                                if (!i)
@@ -461,12 +489,23 @@ int proc_readdir(struct file * filp,
                        }
 
                        do {
+                               struct proc_dir_entry *next;
+
+                               /* filldir passes info to user space */
+                               de_get(de);
+                               spin_unlock(&proc_subdir_lock);
                                if (filldir(dirent, de->name, de->namelen, filp->f_pos,
-                                           de->low_ino, de->mode >> 12) < 0)
+                                           de->low_ino, de->mode >> 12) < 0) {
+                                       de_put(de);
                                        goto out;
+                               }
+                               spin_lock(&proc_subdir_lock);
                                filp->f_pos++;
-                               de = de->next;
+                               next = de->next;
+                               de_put(de);
+                               de = next;
                        } while (de);
+                       spin_unlock(&proc_subdir_lock);
        }
        ret = 1;
 out:   unlock_kernel();
@@ -478,7 +517,7 @@ out:        unlock_kernel();
  * use the in-memory "struct proc_dir_entry" tree to parse
  * the /proc directory.
  */
-static struct file_operations proc_dir_operations = {
+static const struct file_operations proc_dir_operations = {
        .read                   = generic_read_dir,
        .readdir                = proc_readdir,
 };
@@ -486,7 +525,7 @@ static struct file_operations proc_dir_operations = {
 /*
  * proc directories can do almost nothing..
  */
-static struct inode_operations proc_dir_inode_operations = {
+static const struct inode_operations proc_dir_inode_operations = {
        .lookup         = proc_lookup,
        .getattr        = proc_getattr,
        .setattr        = proc_notify_change,
@@ -500,9 +539,7 @@ static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp
        if (i == 0)
                return -EAGAIN;
        dp->low_ino = i;
-       dp->next = dir->subdir;
-       dp->parent = dir;
-       dir->subdir = dp;
+
        if (S_ISDIR(dp->mode)) {
                if (dp->proc_iops == NULL) {
                        dp->proc_fops = &proc_dir_operations;
@@ -518,37 +555,14 @@ static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp
                if (dp->proc_iops == NULL)
                        dp->proc_iops = &proc_file_inode_operations;
        }
-       return 0;
-}
 
-/*
- * Kill an inode that got unregistered..
- */
-static void proc_kill_inodes(struct proc_dir_entry *de)
-{
-       struct list_head *p;
-       struct super_block *sb = proc_mnt->mnt_sb;
+       spin_lock(&proc_subdir_lock);
+       dp->next = dir->subdir;
+       dp->parent = dir;
+       dir->subdir = dp;
+       spin_unlock(&proc_subdir_lock);
 
-       /*
-        * Actually it's a partial revoke().
-        */
-       file_list_lock();
-       list_for_each(p, &sb->s_files) {
-               struct file * filp = list_entry(p, struct file, f_u.fu_list);
-               struct dentry * dentry = filp->f_dentry;
-               struct inode * inode;
-               struct file_operations *fops;
-
-               if (dentry->d_op != &proc_dentry_operations)
-                       continue;
-               inode = dentry->d_inode;
-               if (PDE(inode) != de)
-                       continue;
-               fops = filp->f_op;
-               filp->f_op = NULL;
-               fops_put(fops);
-       }
-       file_list_unlock();
+       return 0;
 }
 
 static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent,
@@ -581,6 +595,10 @@ static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent,
        ent->namelen = len;
        ent->mode = mode;
        ent->nlink = nlink;
+       atomic_set(&ent->count, 1);
+       ent->pde_users = 0;
+       spin_lock_init(&ent->pde_unload_lock);
+       ent->pde_unload_completion = NULL;
  out:
        return ent;
 }
@@ -617,9 +635,6 @@ struct proc_dir_entry *proc_mkdir_mode(const char *name, mode_t mode,
 
        ent = proc_create(&parent, name, S_IFDIR | mode, 2);
        if (ent) {
-               ent->proc_fops = &proc_dir_operations;
-               ent->proc_iops = &proc_dir_inode_operations;
-
                if (proc_register(parent, ent) < 0) {
                        kfree(ent);
                        ent = NULL;
@@ -654,10 +669,6 @@ struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
 
        ent = proc_create(&parent,name,mode,nlink);
        if (ent) {
-               if (S_ISDIR(mode)) {
-                       ent->proc_fops = &proc_dir_operations;
-                       ent->proc_iops = &proc_dir_inode_operations;
-               }
                if (proc_register(parent, ent) < 0) {
                        kfree(ent);
                        ent = NULL;
@@ -682,7 +693,6 @@ void free_proc_entry(struct proc_dir_entry *de)
 
 /*
  * Remove a /proc entry and free it if it's not currently in use.
- * If it is in use, we set the 'deleted' flag.
  */
 void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
 {
@@ -694,26 +704,48 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
        if (!parent && xlate_proc_name(name, &parent, &fn) != 0)
                goto out;
        len = strlen(fn);
+
+       spin_lock(&proc_subdir_lock);
        for (p = &parent->subdir; *p; p=&(*p)->next ) {
                if (!proc_match(len, fn, *p))
                        continue;
                de = *p;
                *p = de->next;
                de->next = NULL;
+
+               spin_lock(&de->pde_unload_lock);
+               /*
+                * Stop accepting new callers into module. If you're
+                * dynamically allocating ->proc_fops, save a pointer somewhere.
+                */
+               de->proc_fops = NULL;
+               /* Wait until all existing callers into module are done. */
+               if (de->pde_users > 0) {
+                       DECLARE_COMPLETION_ONSTACK(c);
+
+                       if (!de->pde_unload_completion)
+                               de->pde_unload_completion = &c;
+
+                       spin_unlock(&de->pde_unload_lock);
+                       spin_unlock(&proc_subdir_lock);
+
+                       wait_for_completion(de->pde_unload_completion);
+
+                       spin_lock(&proc_subdir_lock);
+                       goto continue_removing;
+               }
+               spin_unlock(&de->pde_unload_lock);
+
+continue_removing:
                if (S_ISDIR(de->mode))
                        parent->nlink--;
-               proc_kill_inodes(de);
                de->nlink = 0;
                WARN_ON(de->subdir);
-               if (!atomic_read(&de->count))
+               if (atomic_dec_and_test(&de->count))
                        free_proc_entry(de);
-               else {
-                       de->deleted = 1;
-                       printk("remove_proc_entry: %s/%s busy, count=%d\n",
-                               parent->name, de->name, atomic_read(&de->count));
-               }
                break;
        }
+       spin_unlock(&proc_subdir_lock);
 out:
        return;
 }