]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - ipc/util.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[karo-tx-linux.git] / ipc / util.c
index 464a8abd779fb0bc54b861bc5f4bb29347686dbe..abfc13e8677f58ad667ce0c32a9e82c9c25ef247 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/msg.h>
 #include <linux/vmalloc.h>
 #include <linux/slab.h>
+#include <linux/notifier.h>
 #include <linux/capability.h>
 #include <linux/highuid.h>
 #include <linux/security.h>
@@ -47,19 +48,16 @@ struct ipc_proc_iface {
        int (*show)(struct seq_file *, void *);
 };
 
-#ifdef CONFIG_MEMORY_HOTPLUG
-
 static void ipc_memory_notifier(struct work_struct *work)
 {
        ipcns_notify(IPCNS_MEMCHANGED);
 }
 
-static DECLARE_WORK(ipc_memory_wq, ipc_memory_notifier);
-
-
 static int ipc_memory_callback(struct notifier_block *self,
                                unsigned long action, void *arg)
 {
+       static DECLARE_WORK(ipc_memory_wq, ipc_memory_notifier);
+
        switch (action) {
        case MEM_ONLINE:    /* memory successfully brought online */
        case MEM_OFFLINE:   /* or offline: it's time to recompute msgmni */
@@ -85,7 +83,10 @@ static int ipc_memory_callback(struct notifier_block *self,
        return NOTIFY_OK;
 }
 
-#endif /* CONFIG_MEMORY_HOTPLUG */
+static struct notifier_block ipc_memory_nb = {
+       .notifier_call = ipc_memory_callback,
+       .priority = IPC_CALLBACK_PRI,
+};
 
 /**
  *     ipc_init        -       initialise IPC subsystem
@@ -102,7 +103,7 @@ static int __init ipc_init(void)
        sem_init();
        msg_init();
        shm_init();
-       hotplug_memory_notifier(ipc_memory_callback, IPC_CALLBACK_PRI);
+       register_hotmemory_notifier(&ipc_memory_nb);
        register_ipcns_notifier(&init_ipc_ns);
        return 0;
 }
@@ -438,9 +439,9 @@ void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
  *     NULL is returned if the allocation fails
  */
  
-voidipc_alloc(int size)
+void *ipc_alloc(int size)
 {
-       voidout;
+       void *out;
        if(size > PAGE_SIZE)
                out = vmalloc(size);
        else
@@ -477,7 +478,7 @@ void ipc_free(void* ptr, int size)
  */
 struct ipc_rcu_hdr
 {
-       int refcount;
+       atomic_t refcount;
        int is_vmalloc;
        void *data[0];
 };
@@ -515,39 +516,41 @@ static inline int rcu_use_vmalloc(int size)
  *     @size: size desired
  *
  *     Allocate memory for the rcu header structure +  the object.
- *     Returns the pointer to the object.
- *     NULL is returned if the allocation fails. 
+ *     Returns the pointer to the object or NULL upon failure.
  */
-void* ipc_rcu_alloc(int size)
+void *ipc_rcu_alloc(int size)
 {
-       void* out;
-       /* 
+       void *out;
+
+       /*
         * We prepend the allocation with the rcu struct, and
-        * workqueue if necessary (for vmalloc). 
+        * workqueue if necessary (for vmalloc).
         */
        if (rcu_use_vmalloc(size)) {
                out = vmalloc(HDRLEN_VMALLOC + size);
-               if (out) {
-                       out += HDRLEN_VMALLOC;
-                       container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
-                       container_of(out, struct ipc_rcu_hdr, data)->refcount = 1;
-               }
+               if (!out)
+                       goto done;
+
+               out += HDRLEN_VMALLOC;
+               container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
        } else {
                out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
-               if (out) {
-                       out += HDRLEN_KMALLOC;
-                       container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
-                       container_of(out, struct ipc_rcu_hdr, data)->refcount = 1;
-               }
+               if (!out)
+                       goto done;
+
+               out += HDRLEN_KMALLOC;
+               container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
        }
 
+       /* set reference counter no matter what kind of allocation was done */
+       atomic_set(&container_of(out, struct ipc_rcu_hdr, data)->refcount, 1);
+done:
        return out;
 }
 
-void ipc_rcu_getref(void *ptr)
+int ipc_rcu_getref(void *ptr)
 {
-       container_of(ptr, struct ipc_rcu_hdr, data)->refcount++;
+       return atomic_inc_not_zero(&container_of(ptr, struct ipc_rcu_hdr, data)->refcount);
 }
 
 static void ipc_do_vfree(struct work_struct *work)
@@ -577,7 +580,7 @@ static void ipc_schedule_free(struct rcu_head *head)
 
 void ipc_rcu_putref(void *ptr)
 {
-       if (--container_of(ptr, struct ipc_rcu_hdr, data)->refcount > 0)
+       if (!atomic_dec_and_test(&container_of(ptr, struct ipc_rcu_hdr, data)->refcount))
                return;
 
        if (container_of(ptr, struct ipc_rcu_hdr, data)->is_vmalloc) {
@@ -667,6 +670,28 @@ void ipc64_perm_to_ipc_perm (struct ipc64_perm *in, struct ipc_perm *out)
        out->seq        = in->seq;
 }
 
+/**
+ * ipc_obtain_object
+ * @ids: ipc identifier set
+ * @id: ipc id to look for
+ *
+ * Look for an id in the ipc ids idr and return associated ipc object.
+ *
+ * Call inside the RCU critical section.
+ * The ipc object is *not* locked on exit.
+ */
+struct kern_ipc_perm *ipc_obtain_object(struct ipc_ids *ids, int id)
+{
+       struct kern_ipc_perm *out;
+       int lid = ipcid_to_idx(id);
+
+       out = idr_find(&ids->ipcs_idr, lid);
+       if (!out)
+               return ERR_PTR(-EINVAL);
+
+       return out;
+}
+
 /**
  * ipc_lock - Lock an ipc structure without rw_mutex held
  * @ids: IPC identifier set
@@ -674,32 +699,53 @@ void ipc64_perm_to_ipc_perm (struct ipc64_perm *in, struct ipc_perm *out)
  *
  * Look for an id in the ipc ids idr and lock the associated ipc object.
  *
- * The ipc object is locked on exit.
+ * The ipc object is locked on successful exit.
  */
-
 struct kern_ipc_perm *ipc_lock(struct ipc_ids *ids, int id)
 {
        struct kern_ipc_perm *out;
-       int lid = ipcid_to_idx(id);
 
        rcu_read_lock();
-       out = idr_find(&ids->ipcs_idr, lid);
-       if (out == NULL) {
-               rcu_read_unlock();
-               return ERR_PTR(-EINVAL);
-       }
+       out = ipc_obtain_object(ids, id);
+       if (IS_ERR(out))
+               goto err1;
 
        spin_lock(&out->lock);
-       
+
        /* ipc_rmid() may have already freed the ID while ipc_lock
         * was spinning: here verify that the structure is still valid
         */
-       if (out->deleted) {
-               spin_unlock(&out->lock);
-               rcu_read_unlock();
-               return ERR_PTR(-EINVAL);
-       }
+       if (!out->deleted)
+               return out;
+
+       spin_unlock(&out->lock);
+       out = ERR_PTR(-EINVAL);
+err1:
+       rcu_read_unlock();
+       return out;
+}
+
+/**
+ * ipc_obtain_object_check
+ * @ids: ipc identifier set
+ * @id: ipc id to look for
+ *
+ * Similar to ipc_obtain_object() but also checks
+ * the ipc object reference counter.
+ *
+ * Call inside the RCU critical section.
+ * The ipc object is *not* locked on exit.
+ */
+struct kern_ipc_perm *ipc_obtain_object_check(struct ipc_ids *ids, int id)
+{
+       struct kern_ipc_perm *out = ipc_obtain_object(ids, id);
+
+       if (IS_ERR(out))
+               goto out;
 
+       if (ipc_checkid(out, id))
+               return ERR_PTR(-EIDRM);
+out:
        return out;
 }
 
@@ -780,11 +826,28 @@ struct kern_ipc_perm *ipcctl_pre_down(struct ipc_namespace *ns,
                                      struct ipc64_perm *perm, int extra_perm)
 {
        struct kern_ipc_perm *ipcp;
+
+       ipcp = ipcctl_pre_down_nolock(ns, ids, id, cmd, perm, extra_perm);
+       if (IS_ERR(ipcp))
+               goto out;
+
+       spin_lock(&ipcp->lock);
+out:
+       return ipcp;
+}
+
+struct kern_ipc_perm *ipcctl_pre_down_nolock(struct ipc_namespace *ns,
+                                            struct ipc_ids *ids, int id, int cmd,
+                                            struct ipc64_perm *perm, int extra_perm)
+{
        kuid_t euid;
-       int err;
+       int err = -EPERM;
+       struct kern_ipc_perm *ipcp;
 
        down_write(&ids->rw_mutex);
-       ipcp = ipc_lock_check(ids, id);
+       rcu_read_lock();
+
+       ipcp = ipc_obtain_object_check(ids, id);
        if (IS_ERR(ipcp)) {
                err = PTR_ERR(ipcp);
                goto out_up;
@@ -793,17 +856,21 @@ struct kern_ipc_perm *ipcctl_pre_down(struct ipc_namespace *ns,
        audit_ipc_obj(ipcp);
        if (cmd == IPC_SET)
                audit_ipc_set_perm(extra_perm, perm->uid,
-                                        perm->gid, perm->mode);
+                                  perm->gid, perm->mode);
 
        euid = current_euid();
        if (uid_eq(euid, ipcp->cuid) || uid_eq(euid, ipcp->uid)  ||
            ns_capable(ns->user_ns, CAP_SYS_ADMIN))
                return ipcp;
 
-       err = -EPERM;
-       ipc_unlock(ipcp);
 out_up:
+       /*
+        * Unsuccessful lookup, unlock and return
+        * the corresponding error.
+        */
+       rcu_read_unlock();
        up_write(&ids->rw_mutex);
+
        return ERR_PTR(err);
 }
 
@@ -964,7 +1031,7 @@ static int sysvipc_proc_open(struct inode *inode, struct file *file)
        seq = file->private_data;
        seq->private = iter;
 
-       iter->iface = PDE(inode)->data;
+       iter->iface = PDE_DATA(inode);
        iter->ns    = get_ipc_ns(current->nsproxy->ipc_ns);
 out:
        return ret;