Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / fs / proc / generic.c
index d2c88eb..153bc26 100644 (file)
 #include <linux/smp_lock.h>
 #include <linux/init.h>
 #include <linux/idr.h>
+#include <linux/namei.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/vserver/inode.h>
 #include <asm/uaccess.h>
-#include <asm/bitops.h>
+
+#include "internal.h"
 
 static ssize_t proc_file_read(struct file *file, char __user *buf,
                              size_t nbytes, loff_t *ppos);
@@ -26,6 +31,8 @@ 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);
 
+DEFINE_SPINLOCK(proc_subdir_lock);
+
 int proc_match(int len, const char *name, struct proc_dir_entry *de)
 {
        if (de->namelen != len)
@@ -53,13 +60,25 @@ proc_file_read(struct file *file, char __user *buf, size_t nbytes,
        ssize_t n, count;
        char    *start;
        struct proc_dir_entry * dp;
+       unsigned long long pos;
+
+       /*
+        * Gaah, please just use "seq_file" instead. The legacy /proc
+        * interfaces cut loff_t down to off_t for reads, and ignore
+        * the offset entirely for writes..
+        */
+       pos = *ppos;
+       if (pos > MAX_NON_LFS)
+               return 0;
+       if (nbytes > MAX_NON_LFS - pos)
+               nbytes = MAX_NON_LFS - pos;
 
        dp = PDE(inode);
        if (!(page = (char*) __get_free_page(GFP_KERNEL)))
                return -ENOMEM;
 
        while ((nbytes > 0) && !eof) {
-               count = min_t(ssize_t, PROC_BLOCK_SIZE, nbytes);
+               count = min_t(size_t, PROC_BLOCK_SIZE, nbytes);
 
                start = NULL;
                if (dp->get_info) {
@@ -201,44 +220,50 @@ proc_file_write(struct file *file, const char __user *buffer,
 static loff_t
 proc_file_lseek(struct file *file, loff_t offset, int orig)
 {
-    lock_kernel();
+       loff_t retval = -EINVAL;
+       switch (orig) {
+       case 1:
+               offset += file->f_pos;
+       /* fallthrough */
+       case 0:
+               if (offset < 0 || offset > MAX_NON_LFS)
+                       break;
+               file->f_pos = retval = offset;
+       }
+       return retval;
+}
+
+static int proc_notify_change(struct dentry *dentry, struct iattr *iattr)
+{
+       struct inode *inode = dentry->d_inode;
+       struct proc_dir_entry *de = PDE(inode);
+       int error;
 
-    switch (orig) {
-    case 0:
-       if (offset < 0)
-           goto out;
-       file->f_pos = offset;
-       unlock_kernel();
-       return(file->f_pos);
-    case 1:
-       if (offset + file->f_pos < 0)
-           goto out;
-       file->f_pos += offset;
-       unlock_kernel();
-       return(file->f_pos);
-    case 2:
-       goto out;
-    default:
-       goto out;
-    }
+       error = inode_change_ok(inode, iattr);
+       if (error)
+               goto out;
 
+       error = inode_setattr(inode, iattr);
+       if (error)
+               goto out;
+       
+       de->uid = inode->i_uid;
+       de->gid = inode->i_gid;
+       de->mode = inode->i_mode;
 out:
-    unlock_kernel();
-    return -EINVAL;
+       return error;
 }
 
-static int proc_notify_change(struct dentry *dentry, struct iattr *iattr)
+static int proc_getattr(struct vfsmount *mnt, struct dentry *dentry,
+                       struct kstat *stat)
 {
        struct inode *inode = dentry->d_inode;
-       int error = inode_setattr(inode, iattr);
-       if (!error) {
-               struct proc_dir_entry *de = PDE(inode);
-               de->uid = inode->i_uid;
-               de->gid = inode->i_gid;
-               de->mode = inode->i_mode;
-       }
+       struct proc_dir_entry *de = PROC_I(inode)->pde;
+       if (de && de->nlink)
+               inode->i_nlink = de->nlink;
 
-       return error;
+       generic_fillattr(inode, stat);
+       return 0;
 }
 
 static struct inode_operations proc_file_inode_operations = {
@@ -256,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, '/');
@@ -268,17 +295,21 @@ 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);
-static spinlock_t proc_inum_lock = SPIN_LOCK_UNLOCKED; /* protects the above */
+static DEFINE_SPINLOCK(proc_inum_lock); /* protects the above */
 
 #define PROC_DYNAMIC_FIRST 0xF0000000UL
 
@@ -288,18 +319,20 @@ static spinlock_t proc_inum_lock = SPIN_LOCK_UNLOCKED; /* protects the above */
  */
 static unsigned int get_inode_number(void)
 {
-       unsigned int i, inum = 0;
+       int i, inum = 0;
+       int error;
 
 retry:
        if (idr_pre_get(&proc_inum_idr, GFP_KERNEL) == 0)
                return 0;
 
        spin_lock(&proc_inum_lock);
-       i = idr_get_new(&proc_inum_idr, NULL);
+       error = idr_get_new(&proc_inum_idr, NULL, &i);
        spin_unlock(&proc_inum_lock);
-
-       if (i == -1)
+       if (error == -EAGAIN)
                goto retry;
+       else if (error)
+               return 0;
 
        inum = (i & MAX_ID_MASK) + PROC_DYNAMIC_FIRST;
 
@@ -319,21 +352,14 @@ static void release_inode_number(unsigned int inum)
        spin_unlock(&proc_inum_lock);
 }
 
-static int
-proc_readlink(struct dentry *dentry, char __user *buffer, int buflen)
-{
-       char *s = PDE(dentry->d_inode)->data;
-       return vfs_readlink(dentry, buffer, buflen, s);
-}
-
-static int proc_follow_link(struct dentry *dentry, struct nameidata *nd)
+static void *proc_follow_link(struct dentry *dentry, struct nameidata *nd)
 {
-       char *s = PDE(dentry->d_inode)->data;
-       return vfs_follow_link(nd, s);
+       nd_set_link(nd, PDE(dentry->d_inode)->data);
+       return NULL;
 }
 
 static struct inode_operations proc_link_inode_operations = {
-       .readlink       = proc_readlink,
+       .readlink       = generic_readlink,
        .follow_link    = proc_follow_link,
 };
 
@@ -364,20 +390,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 (!vx_hide_check(0, de->vx_flags))
+                               continue;
                        if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
                                unsigned int ino = de->low_ino;
 
+                               spin_unlock(&proc_subdir_lock);
                                error = -EINVAL;
                                inode = proc_get_inode(dir->i_sb, ino, de);
+                               /* generic proc entries belong to the host */
+                               inode->i_xid = 0;
+                               spin_lock(&proc_subdir_lock);
                                break;
                        }
                }
        }
+       spin_unlock(&proc_subdir_lock);
        unlock_kernel();
 
        if (inode) {
@@ -431,11 +465,13 @@ int proc_readdir(struct file * filp,
                        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)
@@ -445,12 +481,19 @@ int proc_readdir(struct file * filp,
                        }
 
                        do {
+                               if (!vx_hide_check(0, de->vx_flags))
+                                       goto skip;
+                               /* filldir passes info to user space */
+                               spin_unlock(&proc_subdir_lock);
                                if (filldir(dirent, de->name, de->namelen, filp->f_pos,
                                            de->low_ino, de->mode >> 12) < 0)
                                        goto out;
+                               spin_lock(&proc_subdir_lock);
+                       skip:
                                filp->f_pos++;
                                de = de->next;
                        } while (de);
+                       spin_unlock(&proc_subdir_lock);
        }
        ret = 1;
 out:   unlock_kernel();
@@ -472,6 +515,7 @@ static struct file_operations proc_dir_operations = {
  */
 static struct inode_operations proc_dir_inode_operations = {
        .lookup         = proc_lookup,
+       .getattr        = proc_getattr,
        .setattr        = proc_notify_change,
 };
 
@@ -483,9 +527,13 @@ static int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp
        if (i == 0)
                return -EAGAIN;
        dp->low_ino = i;
+
+       spin_lock(&proc_subdir_lock);
        dp->next = dir->subdir;
        dp->parent = dir;
        dir->subdir = dp;
+       spin_unlock(&proc_subdir_lock);
+
        if (S_ISDIR(dp->mode)) {
                if (dp->proc_iops == NULL) {
                        dp->proc_fops = &proc_dir_operations;
@@ -517,10 +565,10 @@ static void proc_kill_inodes(struct proc_dir_entry *de)
         */
        file_list_lock();
        list_for_each(p, &sb->s_files) {
-               struct file * filp = list_entry(p, struct file, f_list);
+               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;
+               const struct file_operations *fops;
 
                if (dentry->d_op != &proc_dentry_operations)
                        continue;
@@ -548,6 +596,11 @@ static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent,
 
        if (!(*parent) && xlate_proc_name(name, parent, &fn) != 0)
                goto out;
+
+       /* At this point there must not be any '/' characters beyond *fn */
+       if (strchr(fn, '/'))
+               goto out;
+
        len = strlen(fn);
 
        ent = kmalloc(sizeof(struct proc_dir_entry) + len + 1, GFP_KERNEL);
@@ -559,6 +612,7 @@ static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent,
        ent->namelen = len;
        ent->mode = mode;
        ent->nlink = nlink;
+       ent->vx_flags = IATTR_PROC_DEFAULT;
  out:
        return ent;
 }
@@ -579,7 +633,8 @@ struct proc_dir_entry *proc_symlink(const char *name,
                                kfree(ent->data);
                                kfree(ent);
                                ent = NULL;
-                       }
+                       } else
+                               ent->vx_flags = IATTR_PROC_SYMLINK;
                } else {
                        kfree(ent);
                        ent = NULL;
@@ -672,6 +727,8 @@ 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;
@@ -692,6 +749,7 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
                }
                break;
        }
+       spin_unlock(&proc_subdir_lock);
 out:
        return;
 }