diff options
Diffstat (limited to 'ipc/util.c')
-rw-r--r-- | ipc/util.c | 202 |
1 files changed, 134 insertions, 68 deletions
diff --git a/ipc/util.c b/ipc/util.c index 1a2cb02467ab..ff045fec8d83 100644 --- a/ipc/util.c +++ b/ipc/util.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * linux/ipc/util.c * Copyright (C) 1992 Krishna Balasubramanian @@ -83,27 +84,49 @@ 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; + ids->max_id = -1; +#ifdef CONFIG_CHECKPOINT_RESTORE + ids->next_id = -1; +#endif + return 0; } #ifdef CONFIG_PROC_FS @@ -147,68 +170,70 @@ 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; + struct kern_ipc_perm *ipcp = NULL; - for (total = 0, next_id = 0; total < ids->in_use; next_id++) { - ipc = idr_find(&ids->ipcs_idr, next_id); - - if (ipc == NULL) - continue; - - 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; } -/** - * ipc_get_maxid - get the last assigned id - * @ids: ipc identifier set - * - * Called with ipc_ids.rwsem held. +#ifdef CONFIG_CHECKPOINT_RESTORE +/* + * Specify desired id for next allocated IPC object. */ -int ipc_get_maxid(struct ipc_ids *ids) +#define ipc_idr_alloc(ids, new) \ + idr_alloc(&(ids)->ipcs_idr, (new), \ + (ids)->next_id < 0 ? 0 : ipcid_to_idx((ids)->next_id),\ + 0, GFP_NOWAIT) + +static inline int ipc_buildid(int id, struct ipc_ids *ids, + struct kern_ipc_perm *new) { - struct kern_ipc_perm *ipc; - int max_id = -1; - int total, id; + if (ids->next_id < 0) { /* default, behave as !CHECKPOINT_RESTORE */ + new->seq = ids->seq++; + if (ids->seq > IPCID_SEQ_MAX) + ids->seq = 0; + } else { + new->seq = ipcid_to_seqx(ids->next_id); + ids->next_id = -1; + } - if (ids->in_use == 0) - return -1; + return SEQ_MULTIPLIER * new->seq + id; +} - if (ids->in_use == IPCMNI) - return IPCMNI - 1; +#else +#define ipc_idr_alloc(ids, new) \ + idr_alloc(&(ids)->ipcs_idr, (new), 0, 0, GFP_NOWAIT) - /* Look for the last assigned id */ - total = 0; - for (id = 0; id < IPCMNI && total < ids->in_use; id++) { - ipc = idr_find(&ids->ipcs_idr, id); - if (ipc != NULL) { - max_id = id; - total++; - } - } - return max_id; +static inline int ipc_buildid(int id, struct ipc_ids *ids, + struct kern_ipc_perm *new) +{ + new->seq = ids->seq++; + if (ids->seq > IPCID_SEQ_MAX) + ids->seq = 0; + + return SEQ_MULTIPLIER * new->seq + id; } +#endif /* CONFIG_CHECKPOINT_RESTORE */ + /** * ipc_addid - add an ipc identifier * @ids: ipc identifier set * @new: new ipc permission set - * @size: limit for the number of used ids + * @limit: limit for the number of used ids * * Add an entry 'new' to the ipc ids idr. The permissions object is * initialised and the first free entry is set up and the id assigned @@ -217,22 +242,21 @@ int ipc_get_maxid(struct ipc_ids *ids) * * Called with writer ipc_ids.rwsem held. */ -int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size) +int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit) { kuid_t euid; kgid_t egid; - int id; - int next_id = ids->next_id; + int id, err; - if (size > IPCMNI) - size = IPCMNI; + if (limit > IPCMNI) + limit = IPCMNI; - if (ids->in_use >= size) + if (!ids->tables_initialized || ids->in_use >= limit) 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(); @@ -242,10 +266,17 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size) new->cuid = new->uid = euid; new->gid = new->cgid = egid; - id = idr_alloc(&ids->ipcs_idr, new, - (next_id < 0) ? 0 : ipcid_to_idx(next_id), 0, - GFP_NOWAIT); + id = ipc_idr_alloc(ids, new); 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(); @@ -253,17 +284,11 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size) } ids->in_use++; + if (id > ids->max_id) + ids->max_id = id; - if (next_id < 0) { - new->seq = ids->seq++; - if (ids->seq > IPCID_SEQ_MAX) - ids->seq = 0; - } else { - new->seq = ipcid_to_seqx(next_id); - ids->next_id = -1; - } + new->id = ipc_buildid(id, ids, new); - new->id = ipc_buildid(id, new->seq); return id; } @@ -377,6 +402,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 +430,43 @@ 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; + + if (unlikely(lid == ids->max_id)) { + do { + lid--; + if (lid == -1) + break; + } while (!idr_find(&ids->ipcs_idr, lid)); + ids->max_id = lid; + } +} + +/** + * 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 +548,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 +562,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); |