ARM: OMAP2/3: CM: fix cm_split_idlest functionality
[sfrench/cifs-2.6.git] / ipc / util.c
index 1a2cb02467ab5f33f309a4542620cbec8d18361a..78755873cc5b9e2dfe28f85e44933c282aa7d91e 100644 (file)
@@ -83,27 +83,46 @@ struct ipc_proc_iface {
  */
 static int __init ipc_init(void)
 {
-       sem_init();
-       msg_init();
+       int err_sem, err_msg;
+
+       err_sem = sem_init();
+       WARN(err_sem, "ipc: sysv sem_init failed: %d\n", err_sem);
+       err_msg = msg_init();
+       WARN(err_msg, "ipc: sysv msg_init failed: %d\n", err_msg);
        shm_init();
-       return 0;
+
+       return err_msg ? err_msg : err_sem;
 }
 device_initcall(ipc_init);
 
+static const struct rhashtable_params ipc_kht_params = {
+       .head_offset            = offsetof(struct kern_ipc_perm, khtnode),
+       .key_offset             = offsetof(struct kern_ipc_perm, key),
+       .key_len                = FIELD_SIZEOF(struct kern_ipc_perm, key),
+       .locks_mul              = 1,
+       .automatic_shrinking    = true,
+};
+
 /**
  * ipc_init_ids        - initialise ipc identifiers
  * @ids: ipc identifier set
  *
  * Set up the sequence range to use for the ipc identifier range (limited
- * below IPCMNI) then initialise the ids idr.
+ * below IPCMNI) then initialise the keys hashtable and ids idr.
  */
-void ipc_init_ids(struct ipc_ids *ids)
+int ipc_init_ids(struct ipc_ids *ids)
 {
+       int err;
        ids->in_use = 0;
        ids->seq = 0;
        ids->next_id = -1;
        init_rwsem(&ids->rwsem);
+       err = rhashtable_init(&ids->key_ht, &ipc_kht_params);
+       if (err)
+               return err;
        idr_init(&ids->ipcs_idr);
+       ids->tables_initialized = true;
+       return 0;
 }
 
 #ifdef CONFIG_PROC_FS
@@ -147,28 +166,20 @@ void __init ipc_init_proc_interface(const char *path, const char *header,
  * Returns the locked pointer to the ipc structure if found or NULL
  * otherwise. If key is found ipc points to the owning ipc structure
  *
- * Called with ipc_ids.rwsem held.
+ * Called with writer ipc_ids.rwsem held.
  */
 static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key)
 {
-       struct kern_ipc_perm *ipc;
-       int next_id;
-       int total;
-
-       for (total = 0, next_id = 0; total < ids->in_use; next_id++) {
-               ipc = idr_find(&ids->ipcs_idr, next_id);
-
-               if (ipc == NULL)
-                       continue;
+       struct kern_ipc_perm *ipcp = NULL;
 
-               if (ipc->key != key) {
-                       total++;
-                       continue;
-               }
+       if (likely(ids->tables_initialized))
+               ipcp = rhashtable_lookup_fast(&ids->key_ht, &key,
+                                             ipc_kht_params);
 
+       if (ipcp) {
                rcu_read_lock();
-               ipc_lock_object(ipc);
-               return ipc;
+               ipc_lock_object(ipcp);
+               return ipcp;
        }
 
        return NULL;
@@ -221,18 +232,18 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size)
 {
        kuid_t euid;
        kgid_t egid;
-       int id;
+       int id, err;
        int next_id = ids->next_id;
 
        if (size > IPCMNI)
                size = IPCMNI;
 
-       if (ids->in_use >= size)
+       if (!ids->tables_initialized || ids->in_use >= size)
                return -ENOSPC;
 
        idr_preload(GFP_KERNEL);
 
-       atomic_set(&new->refcount, 1);
+       refcount_set(&new->refcount, 1);
        spin_lock_init(&new->lock);
        new->deleted = false;
        rcu_read_lock();
@@ -246,6 +257,15 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size)
                       (next_id < 0) ? 0 : ipcid_to_idx(next_id), 0,
                       GFP_NOWAIT);
        idr_preload_end();
+
+       if (id >= 0 && new->key != IPC_PRIVATE) {
+               err = rhashtable_insert_fast(&ids->key_ht, &new->khtnode,
+                                            ipc_kht_params);
+               if (err < 0) {
+                       idr_remove(&ids->ipcs_idr, id);
+                       id = err;
+               }
+       }
        if (id < 0) {
                spin_unlock(&new->lock);
                rcu_read_unlock();
@@ -377,6 +397,20 @@ static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
        return err;
 }
 
+/**
+ * ipc_kht_remove - remove an ipc from the key hashtable
+ * @ids: ipc identifier set
+ * @ipcp: ipc perm structure containing the key to remove
+ *
+ * ipc_ids.rwsem (as a writer) and the spinlock for this ID are held
+ * before this function is called, and remain locked on the exit.
+ */
+static void ipc_kht_remove(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
+{
+       if (ipcp->key != IPC_PRIVATE)
+               rhashtable_remove_fast(&ids->key_ht, &ipcp->khtnode,
+                                      ipc_kht_params);
+}
 
 /**
  * ipc_rmid - remove an ipc identifier
@@ -391,19 +425,34 @@ void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
        int lid = ipcid_to_idx(ipcp->id);
 
        idr_remove(&ids->ipcs_idr, lid);
+       ipc_kht_remove(ids, ipcp);
        ids->in_use--;
        ipcp->deleted = true;
 }
 
+/**
+ * ipc_set_key_private - switch the key of an existing ipc to IPC_PRIVATE
+ * @ids: ipc identifier set
+ * @ipcp: ipc perm structure containing the key to modify
+ *
+ * ipc_ids.rwsem (as a writer) and the spinlock for this ID are held
+ * before this function is called, and remain locked on the exit.
+ */
+void ipc_set_key_private(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
+{
+       ipc_kht_remove(ids, ipcp);
+       ipcp->key = IPC_PRIVATE;
+}
+
 int ipc_rcu_getref(struct kern_ipc_perm *ptr)
 {
-       return atomic_inc_not_zero(&ptr->refcount);
+       return refcount_inc_not_zero(&ptr->refcount);
 }
 
 void ipc_rcu_putref(struct kern_ipc_perm *ptr,
                        void (*func)(struct rcu_head *head))
 {
-       if (!atomic_dec_and_test(&ptr->refcount))
+       if (!refcount_dec_and_test(&ptr->refcount))
                return;
 
        call_rcu(&ptr->rcu, func);
@@ -485,7 +534,7 @@ void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out)
 }
 
 /**
- * ipc_obtain_object
+ * ipc_obtain_object_idr
  * @ids: ipc identifier set
  * @id: ipc id to look for
  *
@@ -499,6 +548,9 @@ struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id)
        struct kern_ipc_perm *out;
        int lid = ipcid_to_idx(id);
 
+       if (unlikely(!ids->tables_initialized))
+               return ERR_PTR(-EINVAL);
+
        out = idr_find(&ids->ipcs_idr, lid);
        if (!out)
                return ERR_PTR(-EINVAL);