Linux Headquarters
[ Register ]
[ About us ] [ Home Page ]

Advertisement
[ Kernel ] [ Documentation ] [ Links ] [ Books ]

Advertisement

Kernel v2.4.33-rc1 /kernel/sysctl.c

Filename:/kernel/sysctl.c
Lines Added:111
Lines Deleted:32
Also changed in: (Previous) 2.4.33-pre3  2.4.33-pre1  2.4.29  2.4.29-rc4  2.4.29-rc3  2.4.29-rc1 
(Following) 2.4.33-rc2  2.4.33  2.4.36-pre1  2.4.36-pre2  2.4.36-rc1  2.4.36 

Location
[  2.4.33-rc1
  [  kernel
     o  sysctl.c

Patch

diff -Nur -p linux-2.4.32/kernel/sysctl.c linux-2.4.33-rc1/kernel/sysctl.c
--- linux-2.4.32/kernel/sysctl.c   2005-01-19 12:10:13.000000000 -0200
+++ linux-2.4.33-rc1/kernel/sysctl.c   2006-06-16 14:39:40.000000000 -0300
@@ -147,7 +147,7 @@ static struct inode_operations proc_sys_
 
 extern struct proc_dir_entry *proc_sys_root;
 
-static void register_proc_table(ctl_table *, struct proc_dir_entry *);
+static void register_proc_table(ctl_table *, struct proc_dir_entry *, void *);
 static void unregister_proc_table(ctl_table *, struct proc_dir_entry *);
 #endif
 
@@ -360,10 +360,51 @@ static ctl_table dev_table[] = {
 
 extern void init_irq_proc (void);
 
+static spinlock_t sysctl_lock = SPIN_LOCK_UNLOCKED;
+
+/* called under sysctl_lock */
+static int use_table(struct ctl_table_header *p)
+{
+   if (unlikely(p->unregistering != NULL))
+      return 0;
+   p->used++;
+   return 1;
+}
+
+/* called under sysctl_lock */
+static void unuse_table(struct ctl_table_header *p)
+{
+   if (!--p->used)
+      if (unlikely(p->unregistering != NULL))
+         complete(p->unregistering);
+}
+
+/* called under sysctl_lock, will reacquire if has to wait */
+static void start_unregistering(struct ctl_table_header *p)
+{
+   /*
+    * if p->used is 0, nobody will ever touch that entry again;
+    * we'll eliminate all paths to it before dropping sysctl_lock
+    */
+   if (unlikely(p->used)) {
+      struct completion wait;
+      init_completion(&wait);
+      p->unregistering = &wait;
+      spin_unlock(&sysctl_lock);
+      wait_for_completion(&wait);
+      spin_lock(&sysctl_lock);
+   }
+   /*
+    * do not remove from the list until nobody holds it; walking the
+    * list in do_sysctl() relies on that.
+    */
+   list_del_init(&p->ctl_entry);
+}
+
 void __init sysctl_init(void)
 {
 #ifdef CONFIG_PROC_FS
-   register_proc_table(root_table, proc_sys_root);
+   register_proc_table(root_table, proc_sys_root, &root_table_header);
    init_irq_proc();
 #endif
 }
@@ -372,6 +413,7 @@ int do_sysctl(int *name, int nlen, void 
           void *newval, size_t newlen)
 {
    struct list_head *tmp;
+   int error = -ENOTDIR;
 
    if (nlen <= 0 || nlen >= CTL_MAXNAME)
       return -ENOTDIR;
@@ -383,21 +425,31 @@ int do_sysctl(int *name, int nlen, void 
       if ((ssize_t)old_len < 0)
          return -EINVAL;
    }
+   spin_lock(&sysctl_lock);
    tmp = &root_table_header.ctl_entry;
    do {
       struct ctl_table_header *head =
          list_entry(tmp, struct ctl_table_header, ctl_entry);
       void *context = NULL;
-      int error = parse_table(name, nlen, oldval, oldlenp, 
+
+      if (!use_table(head))
+         continue;
+
+      spin_unlock(&sysctl_lock);
+
+      error = parse_table(name, nlen, oldval, oldlenp, 
                newval, newlen, head->ctl_table,
                &context);
       if (context)
          kfree(context);
+
+      spin_lock(&sysctl_lock);
+      unuse_table(head);
       if (error != -ENOTDIR)
-         return error;
-      tmp = tmp->next;
-   } while (tmp != &root_table_header.ctl_entry);
-   return -ENOTDIR;
+         break;
+   } while ((tmp = tmp->next) != &root_table_header.ctl_entry);
+   spin_unlock(&sysctl_lock);
+   return error;
 }
 
 extern asmlinkage long sys_sysctl(struct __sysctl_args *args)
@@ -604,12 +656,16 @@ struct ctl_table_header *register_sysctl
       return NULL;
    tmp->ctl_table = table;
    INIT_LIST_HEAD(&tmp->ctl_entry);
+   tmp->used = 0;
+   tmp->unregistering = NULL;
+   spin_lock(&sysctl_lock);
    if (insert_at_head)
       list_add(&tmp->ctl_entry, &root_table_header.ctl_entry);
    else
       list_add_tail(&tmp->ctl_entry, &root_table_header.ctl_entry);
+   spin_unlock(&sysctl_lock);
 #ifdef CONFIG_PROC_FS
-   register_proc_table(table, proc_sys_root);
+   register_proc_table(table, proc_sys_root, tmp);
 #endif
    return tmp;
 }
@@ -623,10 +679,12 @@ struct ctl_table_header *register_sysctl
  */
 void unregister_sysctl_table(struct ctl_table_header * header)
 {
-   list_del(&header->ctl_entry);
+   spin_lock(&sysctl_lock);
+   start_unregistering(header);
 #ifdef CONFIG_PROC_FS
    unregister_proc_table(header->ctl_table, proc_sys_root);
 #endif
+   spin_unlock(&sysctl_lock);
    kfree(header);
 }
 
@@ -637,7 +695,7 @@ void unregister_sysctl_table(struct ctl_
 #ifdef CONFIG_PROC_FS
 
 /* 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)
+static void register_proc_table(ctl_table * table, struct proc_dir_entry *root, void *set)
 {
    struct proc_dir_entry *de;
    int len;
@@ -673,6 +731,7 @@ static void register_proc_table(ctl_tabl
          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;
@@ -681,7 +740,7 @@ static void register_proc_table(ctl_tabl
       }
       table->de = de;
       if (de->mode & S_IFDIR)
-         register_proc_table(table->child, de);
+         register_proc_table(table->child, de, set);
    }
 }
 
@@ -706,6 +765,13 @@ static void unregister_proc_table(ctl_ta
             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;
@@ -719,31 +785,44 @@ static ssize_t do_rw_proc(int write, str
            size_t count, loff_t *ppos)
 {
    int op;
-   struct proc_dir_entry *de;
+   struct proc_dir_entry *de =
+     (struct proc_dir_entry*) file->f_dentry->d_inode->u.generic_ip;
    struct ctl_table *table;
    size_t res;
-   ssize_t error;
-   
-   de = (struct proc_dir_entry*) file->f_dentry->d_inode->u.generic_ip;
-   if (!de || !de->data)
-      return -ENOTDIR;
-   table = (struct ctl_table *) de->data;
-   if (!table || !table->proc_handler)
-      return -ENOTDIR;
-   op = (write ? 002 : 004);
-   if (ctl_perm(table, op))
-      return -EPERM;
-   
-   res = count;
+   ssize_t error = -ENOTDIR;
 
-   /*
-    * FIXME: we need to pass on ppos to the handler.
-    */
+   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 (ctl_perm(table, op))
+         goto out;
+      
+      /* careful: calling conventions are nasty here */
+      res = count;
 
-   error = (*table->proc_handler) (table, write, file, buf, &res);
-   if (error)
-      return error;
-   return res;
+      /*
+       * FIXME: we need to pass on ppos to the handler.
+       */
+
+      error = (*table->proc_handler)(table, write, file,
+                  buf, &res);
+      if (!error)
+         error = res;
+   out:
+      spin_lock(&sysctl_lock);
+      unuse_table(de->set);
+   }
+   spin_unlock(&sysctl_lock);
+   return error;
 }
 
 static ssize_t proc_readsys(struct file * file, char * buf,


Comments: webmaster (at) linuxhq.com.
Advertising: banners (at) linuxhq.com.
Compilation ©1998-2008 Linux Headquarters, Inc.