--- /dev/null
+/*
+ * /proc/sys support
+ */
+
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <linux/security.h>
+#include "internal.h"
+
+static struct dentry_operations proc_sys_dentry_operations;
+static const struct file_operations proc_sys_file_operations;
+static struct inode_operations proc_sys_inode_operations;
+
+static void proc_sys_refresh_inode(struct inode *inode, struct ctl_table *table)
+{
+       /* Refresh the cached information bits in the inode */
+       if (table) {
+               inode->i_uid = 0;
+               inode->i_gid = 0;
+               inode->i_mode = table->mode;
+               if (table->proc_handler) {
+                       inode->i_mode |= S_IFREG;
+                       inode->i_nlink = 1;
+               } else {
+                       inode->i_mode |= S_IFDIR;
+                       inode->i_nlink = 0;     /* It is too hard to figure out */
+               }
+       }
+}
+
+static struct inode *proc_sys_make_inode(struct inode *dir, struct ctl_table *table)
+{
+       struct inode *inode;
+       struct proc_inode *dir_ei, *ei;
+       int depth;
+
+       inode = new_inode(dir->i_sb);
+       if (!inode)
+               goto out;
+
+       /* A directory is always one deeper than it's parent */
+       dir_ei = PROC_I(dir);
+       depth = dir_ei->fd + 1;
+
+       ei = PROC_I(inode);
+       ei->fd = depth;
+       inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
+       inode->i_op = &proc_sys_inode_operations;
+       inode->i_fop = &proc_sys_file_operations;
+       proc_sys_refresh_inode(inode, table);
+out:
+       return inode;
+}
+
+static struct dentry *proc_sys_ancestor(struct dentry *dentry, int depth)
+{
+       for (;;) {
+               struct proc_inode *ei;
+
+               ei = PROC_I(dentry->d_inode);
+               if (ei->fd == depth)
+                       break; /* found */
+
+               dentry = dentry->d_parent;
+       }
+       return dentry;
+}
+
+static struct ctl_table *proc_sys_lookup_table_one(struct ctl_table *table,
+                                                       struct qstr *name)
+{
+       int len;
+       for ( ; table->ctl_name || table->procname; table++) {
+
+               if (!table->procname)
+                       continue;
+
+               len = strlen(table->procname);
+               if (len != name->len)
+                       continue;
+
+               if (memcmp(table->procname, name->name, len) != 0)
+                       continue;
+
+               /* I have a match */
+               return table;
+       }
+       return NULL;
+}
+
+static struct ctl_table *proc_sys_lookup_table(struct dentry *dentry,
+                                               struct ctl_table *table)
+{
+       struct dentry *ancestor;
+       struct proc_inode *ei;
+       int depth, i;
+
+       ei = PROC_I(dentry->d_inode);
+       depth = ei->fd;
+
+       if (depth == 0)
+               return table;
+
+       for (i = 1; table && (i <= depth); i++) {
+               ancestor = proc_sys_ancestor(dentry, i);
+               table = proc_sys_lookup_table_one(table, &ancestor->d_name);
+               if (table)
+                       table = table->child;
+       }
+       return table;
+
+}
+static struct ctl_table *proc_sys_lookup_entry(struct dentry *dparent,
+                                               struct qstr *name,
+                                               struct ctl_table *table)
+{
+       table = proc_sys_lookup_table(dparent, table);
+       if (table)
+               table = proc_sys_lookup_table_one(table, name);
+       return table;
+}
+
+static struct ctl_table *do_proc_sys_lookup(struct dentry *parent,
+                                               struct qstr *name,
+                                               struct ctl_table_header **ptr)
+{
+       struct ctl_table_header *head;
+       struct ctl_table *table = NULL;
+
+       for (head = sysctl_head_next(NULL); head;
+                       head = sysctl_head_next(head)) {
+               table = proc_sys_lookup_entry(parent, name, head->ctl_table);
+               if (table)
+                       break;
+       }
+       *ptr = head;
+       return table;
+}
+
+static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry,
+                                       struct nameidata *nd)
+{
+       struct ctl_table_header *head;
+       struct inode *inode;
+       struct dentry *err;
+       struct ctl_table *table;
+
+       err = ERR_PTR(-ENOENT);
+       table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
+       if (!table)
+               goto out;
+
+       err = ERR_PTR(-ENOMEM);
+       inode = proc_sys_make_inode(dir, table);
+       if (!inode)
+               goto out;
+
+       err = NULL;
+       dentry->d_op = &proc_sys_dentry_operations;
+       d_add(dentry, inode);
+
+out:
+       sysctl_head_finish(head);
+       return err;
+}
+
+static ssize_t proc_sys_read(struct file *filp, char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       struct dentry *dentry = filp->f_dentry;
+       struct ctl_table_header *head;
+       struct ctl_table *table;
+       ssize_t error, res;
+
+       table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
+       /* Has the sysctl entry disappeared on us? */
+       error = -ENOENT;
+       if (!table)
+               goto out;
+
+       /* Has the sysctl entry been replaced by a directory? */
+       error = -EISDIR;
+       if (!table->proc_handler)
+               goto out;
+
+       /*
+        * At this point we know that the sysctl was not unregistered
+        * and won't be until we finish.
+        */
+       error = -EPERM;
+       if (sysctl_perm(table, MAY_READ))
+               goto out;
+
+       /* careful: calling conventions are nasty here */
+       res = count;
+       error = table->proc_handler(table, 0, filp, buf, &res, ppos);
+       if (!error)
+               error = res;
+out:
+       sysctl_head_finish(head);
+
+       return error;
+}
+
+static ssize_t proc_sys_write(struct file *filp, const char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       struct dentry *dentry = filp->f_dentry;
+       struct ctl_table_header *head;
+       struct ctl_table *table;
+       ssize_t error, res;
+
+       table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
+       /* Has the sysctl entry disappeared on us? */
+       error = -ENOENT;
+       if (!table)
+               goto out;
+
+       /* Has the sysctl entry been replaced by a directory? */
+       error = -EISDIR;
+       if (!table->proc_handler)
+               goto out;
+
+       /*
+        * At this point we know that the sysctl was not unregistered
+        * and won't be until we finish.
+        */
+       error = -EPERM;
+       if (sysctl_perm(table, MAY_WRITE))
+               goto out;
+
+       /* careful: calling conventions are nasty here */
+       res = count;
+       error = table->proc_handler(table, 1, filp, (char __user *)buf,
+                                   &res, ppos);
+       if (!error)
+               error = res;
+out:
+       sysctl_head_finish(head);
+
+       return error;
+}
+
+
+static int proc_sys_fill_cache(struct file *filp, void *dirent,
+                               filldir_t filldir, struct ctl_table *table)
+{
+       struct ctl_table_header *head;
+       struct ctl_table *child_table = NULL;
+       struct dentry *child, *dir = filp->f_path.dentry;
+       struct inode *inode;
+       struct qstr qname;
+       ino_t ino = 0;
+       unsigned type = DT_UNKNOWN;
+       int ret;
+
+       qname.name = table->procname;
+       qname.len  = strlen(table->procname);
+       qname.hash = full_name_hash(qname.name, qname.len);
+
+       /* Suppress duplicates.
+        * Only fill a directory entry if it is the value that
+        * an ordinary lookup of that name returns.  Hide all
+        * others.
+        *
+        * If we ever cache this translation in the dcache
+        * I should do a dcache lookup first.  But for now
+        * it is just simpler not to.
+        */
+       ret = 0;
+       child_table = do_proc_sys_lookup(dir, &qname, &head);
+       sysctl_head_finish(head);
+       if (child_table != table)
+               return 0;
+
+       child = d_lookup(dir, &qname);
+       if (!child) {
+               struct dentry *new;
+               new = d_alloc(dir, &qname);
+               if (new) {
+                       inode = proc_sys_make_inode(dir->d_inode, table);
+                       if (!inode)
+                               child = ERR_PTR(-ENOMEM);
+                       else {
+                               new->d_op = &proc_sys_dentry_operations;
+                               d_add(new, inode);
+                       }
+                       if (child)
+                               dput(new);
+                       else
+                               child = new;
+               }
+       }
+       if (!child || IS_ERR(child) || !child->d_inode)
+               goto end_instantiate;
+       inode = child->d_inode;
+       if (inode) {
+               ino  = inode->i_ino;
+               type = inode->i_mode >> 12;
+       }
+       dput(child);
+end_instantiate:
+       if (!ino)
+               ino= find_inode_number(dir, &qname);
+       if (!ino)
+               ino = 1;
+       return filldir(dirent, qname.name, qname.len, filp->f_pos, ino, type);
+}
+
+static int proc_sys_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       struct dentry *dentry = filp->f_dentry;
+       struct inode *inode = dentry->d_inode;
+       struct ctl_table_header *head = NULL;
+       struct ctl_table *table;
+       unsigned long pos;
+       int ret;
+
+       ret = -ENOTDIR;
+       if (!S_ISDIR(inode->i_mode))
+               goto out;
+
+       ret = 0;
+       /* Avoid a switch here: arm builds fail with missing __cmpdi2 */
+       if (filp->f_pos == 0) {
+               if (filldir(dirent, ".", 1, filp->f_pos,
+                               inode->i_ino, DT_DIR) < 0)
+                       goto out;
+               filp->f_pos++;
+       }
+       if (filp->f_pos == 1) {
+               if (filldir(dirent, "..", 2, filp->f_pos,
+                               parent_ino(dentry), DT_DIR) < 0)
+                       goto out;
+               filp->f_pos++;
+       }
+       pos = 2;
+
+       /* - Find each instance of the directory
+        * - Read all entries in each instance
+        * - Before returning an entry to user space lookup the entry
+        *   by name and if I find a different entry don't return
+        *   this one because it means it is a buried dup.
+        * For sysctl this should only happen for directory entries.
+        */
+       for (head = sysctl_head_next(NULL); head; head = sysctl_head_next(head)) {
+               table = proc_sys_lookup_table(dentry, head->ctl_table);
+
+               if (!table)
+                       continue;
+
+               for (; table->ctl_name || table->procname; table++, pos++) {
+                       /* Can't do anything without a proc name */
+                       if (!table->procname)
+                               continue;
+
+                       if (pos < filp->f_pos)
+                               continue;
+
+                       if (proc_sys_fill_cache(filp, dirent, filldir, table) < 0)
+                               goto out;
+                       filp->f_pos = pos + 1;
+               }
+       }
+       ret = 1;
+out:
+       sysctl_head_finish(head);
+       return ret;
+}
+
+static int proc_sys_permission(struct inode *inode, int mask, struct nameidata *nd)
+{
+       /*
+        * sysctl entries that are not writeable,
+        * are _NOT_ writeable, capabilities or not.
+        */
+       struct ctl_table_header *head;
+       struct ctl_table *table;
+       struct dentry *dentry;
+       int mode;
+       int depth;
+       int error;
+
+       head = NULL;
+       depth = PROC_I(inode)->fd;
+
+       /* First check the cached permissions, in case we don't have
+        * enough information to lookup the sysctl table entry.
+        */
+       error = -EACCES;
+       mode = inode->i_mode;
+
+       if (current->euid == 0)
+               mode >>= 6;
+       else if (in_group_p(0))
+               mode >>= 3;
+
+       if ((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask)
+               error = 0;
+
+       /* If we can't get a sysctl table entry the permission
+        * checks on the cached mode will have to be enough.
+        */
+       if (!nd || !depth)
+               goto out;
+
+       dentry = nd->dentry;
+       table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
+
+       /* If the entry does not exist deny permission */
+       error = -EACCES;
+       if (!table)
+               goto out;
+
+       /* Use the permissions on the sysctl table entry */
+       error = sysctl_perm(table, mask);
+out:
+       sysctl_head_finish(head);
+       return error;
+}
+
+static int proc_sys_setattr(struct dentry *dentry, struct iattr *attr)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))
+               return -EPERM;
+
+       error = inode_change_ok(inode, attr);
+       if (!error) {
+               error = security_inode_setattr(dentry, attr);
+               if (!error)
+                       error = inode_setattr(inode, attr);
+       }
+
+       return error;
+}
+
+/* I'm lazy and don't distinguish between files and directories,
+ * until access time.
+ */
+static const struct file_operations proc_sys_file_operations = {
+       .read           = proc_sys_read,
+       .write          = proc_sys_write,
+       .readdir        = proc_sys_readdir,
+};
+
+static struct inode_operations proc_sys_inode_operations = {
+       .lookup         = proc_sys_lookup,
+       .permission     = proc_sys_permission,
+       .setattr        = proc_sys_setattr,
+};
+
+static int proc_sys_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+       struct ctl_table_header *head;
+       struct ctl_table *table;
+       table = do_proc_sys_lookup(dentry->d_parent, &dentry->d_name, &head);
+       proc_sys_refresh_inode(dentry->d_inode, table);
+       sysctl_head_finish(head);
+       return !!table;
+}
+
+static struct dentry_operations proc_sys_dentry_operations = {
+       .d_revalidate   = proc_sys_revalidate,
+};
+
+static struct proc_dir_entry *proc_sys_root;
+
+int proc_sys_init(void)
+{
+       proc_sys_root = proc_mkdir("sys", NULL);
+       proc_sys_root->proc_iops = &proc_sys_inode_operations;
+       proc_sys_root->proc_fops = &proc_sys_file_operations;
+       proc_sys_root->nlink = 0;
+       return 0;
+}
 
 #endif
 
 
-/* /proc declarations: */
-
-#ifdef CONFIG_PROC_SYSCTL
-
-static ssize_t proc_readsys(struct file *, char __user *, size_t, loff_t *);
-static ssize_t proc_writesys(struct file *, const char __user *, size_t, loff_t *);
-static int proc_opensys(struct inode *, struct file *);
-
-const struct file_operations proc_sys_file_operations = {
-       .open           = proc_opensys,
-       .read           = proc_readsys,
-       .write          = proc_writesys,
-};
-
-extern struct proc_dir_entry *proc_sys_root;
-
-static void register_proc_table(ctl_table *, struct proc_dir_entry *, void *);
-static void unregister_proc_table(ctl_table *, struct proc_dir_entry *);
-#endif
-
 /* The default sysctl tables: */
 
 static ctl_table root_table[] = {
        return NULL;
 }
 
-void __init sysctl_init(void)
-{
-#ifdef CONFIG_PROC_SYSCTL
-       register_proc_table(root_table, proc_sys_root, &root_table_header);
-#endif
-}
-
 #ifdef CONFIG_SYSCTL_SYSCALL
 int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp,
               void __user *newval, size_t newlen)
        spin_lock(&sysctl_lock);
        list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry);
        spin_unlock(&sysctl_lock);
-#ifdef CONFIG_PROC_SYSCTL
-       register_proc_table(table, proc_sys_root, tmp);
-#endif
        return tmp;
 }
 
        might_sleep();
        spin_lock(&sysctl_lock);
        start_unregistering(header);
-#ifdef CONFIG_PROC_SYSCTL
-       unregister_proc_table(header->ctl_table, proc_sys_root);
-#endif
        spin_unlock(&sysctl_lock);
        kfree(header);
 }
 
 #ifdef CONFIG_PROC_SYSCTL
 
-/* Scan the sysctl entries in table and add them all into /proc */
-static void register_proc_table(ctl_table * table, struct proc_dir_entry *root, void *set)
-{
-       struct proc_dir_entry *de;
-       int len;
-       mode_t mode;
-       
-       for (; table->ctl_name || table->procname; table++) {
-               /* Can't do anything without a proc name. */
-               if (!table->procname)
-                       continue;
-               /* Maybe we can't do anything with it... */
-               if (!table->proc_handler && !table->child) {
-                       printk(KERN_WARNING "SYSCTL: Can't register %s\n",
-                               table->procname);
-                       continue;
-               }
-
-               len = strlen(table->procname);
-               mode = table->mode;
-
-               de = NULL;
-               if (table->proc_handler)
-                       mode |= S_IFREG;
-               else {
-                       mode |= S_IFDIR;
-                       for (de = root->subdir; de; de = de->next) {
-                               if (proc_match(len, table->procname, de))
-                                       break;
-                       }
-                       /* If the subdir exists already, de is non-NULL */
-               }
-
-               if (!de) {
-                       de = create_proc_entry(table->procname, mode, root);
-                       if (!de)
-                               continue;
-                       de->set = set;
-                       de->data = (void *) table;
-                       if (table->proc_handler)
-                               de->proc_fops = &proc_sys_file_operations;
-               }
-               table->de = de;
-               if (de->mode & S_IFDIR)
-                       register_proc_table(table->child, de, set);
-       }
-}
-
-/*
- * Unregister a /proc sysctl table and any subdirectories.
- */
-static void unregister_proc_table(ctl_table * table, struct proc_dir_entry *root)
-{
-       struct proc_dir_entry *de;
-       for (; table->ctl_name || table->procname; table++) {
-               if (!(de = table->de))
-                       continue;
-               if (de->mode & S_IFDIR) {
-                       if (!table->child) {
-                               printk (KERN_ALERT "Help - malformed sysctl tree on free\n");
-                               continue;
-                       }
-                       unregister_proc_table(table->child, de);
-
-                       /* Don't unregister directories which still have entries.. */
-                       if (de->subdir)
-                               continue;
-               }
-
-               /*
-                * In any case, mark the entry as goner; we'll keep it
-                * around if it's busy, but we'll know to do nothing with
-                * its fields.  We are under sysctl_lock here.
-                */
-               de->data = NULL;
-
-               /* Don't unregister proc entries that are still being used.. */
-               if (atomic_read(&de->count))
-                       continue;
-
-               table->de = NULL;
-               remove_proc_entry(table->procname, root);
-       }
-}
-
-static ssize_t do_rw_proc(int write, struct file * file, char __user * buf,
-                         size_t count, loff_t *ppos)
-{
-       int op;
-       struct proc_dir_entry *de = PDE(file->f_path.dentry->d_inode);
-       struct ctl_table *table;
-       size_t res;
-       ssize_t error = -ENOTDIR;
-       
-       spin_lock(&sysctl_lock);
-       if (de && de->data && use_table(de->set)) {
-               /*
-                * at that point we know that sysctl was not unregistered
-                * and won't be until we finish
-                */
-               spin_unlock(&sysctl_lock);
-               table = (struct ctl_table *) de->data;
-               if (!table || !table->proc_handler)
-                       goto out;
-               error = -EPERM;
-               op = (write ? 002 : 004);
-               if (sysctl_perm(table, op))
-                       goto out;
-               
-               /* careful: calling conventions are nasty here */
-               res = count;
-               error = (*table->proc_handler)(table, write, file,
-                                               buf, &res, ppos);
-               if (!error)
-                       error = res;
-       out:
-               spin_lock(&sysctl_lock);
-               unuse_table(de->set);
-       }
-       spin_unlock(&sysctl_lock);
-       return error;
-}
-
-static int proc_opensys(struct inode *inode, struct file *file)
-{
-       if (file->f_mode & FMODE_WRITE) {
-               /*
-                * sysctl entries that are not writable,
-                * are _NOT_ writable, capabilities or not.
-                */
-               if (!(inode->i_mode & S_IWUSR))
-                       return -EPERM;
-       }
-
-       return 0;
-}
-
-static ssize_t proc_readsys(struct file * file, char __user * buf,
-                           size_t count, loff_t *ppos)
-{
-       return do_rw_proc(0, file, buf, count, ppos);
-}
-
-static ssize_t proc_writesys(struct file * file, const char __user * buf,
-                            size_t count, loff_t *ppos)
-{
-       return do_rw_proc(1, file, (char __user *) buf, count, ppos);
-}
-
 static int _proc_do_string(void* data, int maxlen, int write,
                           struct file *filp, void __user *buffer,
                           size_t *lenp, loff_t *ppos)