diff options
Diffstat (limited to 'ipc/sem.c')
-rw-r--r-- | ipc/sem.c | 308 |
1 files changed, 155 insertions, 153 deletions
diff --git a/ipc/sem.c b/ipc/sem.c index b676fef6d20..35952c0bae4 100644 --- a/ipc/sem.c +++ b/ipc/sem.c @@ -80,7 +80,7 @@ #include <linux/audit.h> #include <linux/capability.h> #include <linux/seq_file.h> -#include <linux/mutex.h> +#include <linux/rwsem.h> #include <linux/nsproxy.h> #include <asm/uaccess.h> @@ -88,18 +88,14 @@ #define sem_ids(ns) (*((ns)->ids[IPC_SEM_IDS])) -#define sem_lock(ns, id) ((struct sem_array*)ipc_lock(&sem_ids(ns), id)) #define sem_unlock(sma) ipc_unlock(&(sma)->sem_perm) -#define sem_rmid(ns, id) ((struct sem_array*)ipc_rmid(&sem_ids(ns), id)) -#define sem_checkid(ns, sma, semid) \ - ipc_checkid(&sem_ids(ns),&sma->sem_perm,semid) -#define sem_buildid(ns, id, seq) \ - ipc_buildid(&sem_ids(ns), id, seq) +#define sem_checkid(sma, semid) ipc_checkid(&sma->sem_perm, semid) +#define sem_buildid(id, seq) ipc_buildid(id, seq) static struct ipc_ids init_sem_ids; -static int newary(struct ipc_namespace *, key_t, int, int); -static void freeary(struct ipc_namespace *ns, struct sem_array *sma, int id); +static int newary(struct ipc_namespace *, struct ipc_params *); +static void freeary(struct ipc_namespace *, struct sem_array *); #ifdef CONFIG_PROC_FS static int sysvipc_sem_proc_show(struct seq_file *s, void *it); #endif @@ -129,7 +125,7 @@ static void __sem_init_ns(struct ipc_namespace *ns, struct ipc_ids *ids) ns->sc_semopm = SEMOPM; ns->sc_semmni = SEMMNI; ns->used_sems = 0; - ipc_init_ids(ids, ns->sc_semmni); + ipc_init_ids(ids); } int sem_init_ns(struct ipc_namespace *ns) @@ -146,20 +142,24 @@ int sem_init_ns(struct ipc_namespace *ns) void sem_exit_ns(struct ipc_namespace *ns) { - int i; struct sem_array *sma; + int next_id; + int total, in_use; - mutex_lock(&sem_ids(ns).mutex); - for (i = 0; i <= sem_ids(ns).max_id; i++) { - sma = sem_lock(ns, i); + down_write(&sem_ids(ns).rw_mutex); + + in_use = sem_ids(ns).in_use; + + for (total = 0, next_id = 0; total < in_use; next_id++) { + sma = idr_find(&sem_ids(ns).ipcs_idr, next_id); if (sma == NULL) continue; - - freeary(ns, sma, i); + ipc_lock_by_ptr(&sma->sem_perm); + freeary(ns, sma); + total++; } - mutex_unlock(&sem_ids(ns).mutex); + up_write(&sem_ids(ns).rw_mutex); - ipc_fini_ids(ns->ids[IPC_SEM_IDS]); kfree(ns->ids[IPC_SEM_IDS]); ns->ids[IPC_SEM_IDS] = NULL; } @@ -173,6 +173,42 @@ void __init sem_init (void) } /* + * This routine is called in the paths where the rw_mutex is held to protect + * access to the idr tree. + */ +static inline struct sem_array *sem_lock_check_down(struct ipc_namespace *ns, + int id) +{ + struct kern_ipc_perm *ipcp = ipc_lock_check_down(&sem_ids(ns), id); + + return container_of(ipcp, struct sem_array, sem_perm); +} + +/* + * sem_lock_(check_) routines are called in the paths where the rw_mutex + * is not held. + */ +static inline struct sem_array *sem_lock(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp = ipc_lock(&sem_ids(ns), id); + + return container_of(ipcp, struct sem_array, sem_perm); +} + +static inline struct sem_array *sem_lock_check(struct ipc_namespace *ns, + int id) +{ + struct kern_ipc_perm *ipcp = ipc_lock_check(&sem_ids(ns), id); + + return container_of(ipcp, struct sem_array, sem_perm); +} + +static inline void sem_rmid(struct ipc_namespace *ns, struct sem_array *s) +{ + ipc_rmid(&sem_ids(ns), &s->sem_perm); +} + +/* * Lockless wakeup algorithm: * Without the check/retry algorithm a lockless wakeup is possible: * - queue.status is initialized to -EINTR before blocking. @@ -206,12 +242,23 @@ void __init sem_init (void) */ #define IN_WAKEUP 1 -static int newary (struct ipc_namespace *ns, key_t key, int nsems, int semflg) +/** + * newary - Create a new semaphore set + * @ns: namespace + * @params: ptr to the structure that contains key, semflg and nsems + * + * Called with sem_ids.rw_mutex held (as a writer) + */ + +static int newary(struct ipc_namespace *ns, struct ipc_params *params) { int id; int retval; struct sem_array *sma; int size; + key_t key = params->key; + int nsems = params->u.nsems; + int semflg = params->flg; if (!nsems) return -EINVAL; @@ -236,14 +283,14 @@ static int newary (struct ipc_namespace *ns, key_t key, int nsems, int semflg) } id = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); - if(id == -1) { + if (id < 0) { security_sem_free(sma); ipc_rcu_putref(sma); - return -ENOSPC; + return id; } ns->used_sems += nsems; - sma->sem_id = sem_buildid(ns, id, sma->sem_perm.seq); + sma->sem_perm.id = sem_buildid(id, sma->sem_perm.seq); sma->sem_base = (struct sem *) &sma[1]; /* sma->sem_pending = NULL; */ sma->sem_pending_last = &sma->sem_pending; @@ -252,48 +299,56 @@ static int newary (struct ipc_namespace *ns, key_t key, int nsems, int semflg) sma->sem_ctime = get_seconds(); sem_unlock(sma); - return sma->sem_id; + return sma->sem_perm.id; +} + + +/* + * Called with sem_ids.rw_mutex and ipcp locked. + */ +static inline int sem_security(struct kern_ipc_perm *ipcp, int semflg) +{ + struct sem_array *sma; + + sma = container_of(ipcp, struct sem_array, sem_perm); + return security_sem_associate(sma, semflg); } -asmlinkage long sys_semget (key_t key, int nsems, int semflg) +/* + * Called with sem_ids.rw_mutex and ipcp locked. + */ +static inline int sem_more_checks(struct kern_ipc_perm *ipcp, + struct ipc_params *params) { - int id, err = -EINVAL; struct sem_array *sma; + + sma = container_of(ipcp, struct sem_array, sem_perm); + if (params->u.nsems > sma->sem_nsems) + return -EINVAL; + + return 0; +} + +asmlinkage long sys_semget(key_t key, int nsems, int semflg) +{ struct ipc_namespace *ns; + struct ipc_ops sem_ops; + struct ipc_params sem_params; ns = current->nsproxy->ipc_ns; if (nsems < 0 || nsems > ns->sc_semmsl) return -EINVAL; - mutex_lock(&sem_ids(ns).mutex); - - if (key == IPC_PRIVATE) { - err = newary(ns, key, nsems, semflg); - } else if ((id = ipc_findkey(&sem_ids(ns), key)) == -1) { /* key not used */ - if (!(semflg & IPC_CREAT)) - err = -ENOENT; - else - err = newary(ns, key, nsems, semflg); - } else if (semflg & IPC_CREAT && semflg & IPC_EXCL) { - err = -EEXIST; - } else { - sma = sem_lock(ns, id); - BUG_ON(sma==NULL); - if (nsems > sma->sem_nsems) - err = -EINVAL; - else if (ipcperms(&sma->sem_perm, semflg)) - err = -EACCES; - else { - int semid = sem_buildid(ns, id, sma->sem_perm.seq); - err = security_sem_associate(sma, semflg); - if (!err) - err = semid; - } - sem_unlock(sma); - } - mutex_unlock(&sem_ids(ns).mutex); - return err; + sem_ops.getnew = newary; + sem_ops.associate = sem_security; + sem_ops.more_checks = sem_more_checks; + + sem_params.key = key; + sem_params.flg = semflg; + sem_params.u.nsems = nsems; + + return ipcget(ns, &sem_ids(ns), &sem_ops, &sem_params); } /* Manage the doubly linked list sma->sem_pending as a FIFO: @@ -487,15 +542,14 @@ static int count_semzcnt (struct sem_array * sma, ushort semnum) return semzcnt; } -/* Free a semaphore set. freeary() is called with sem_ids.mutex locked and - * the spinlock for this semaphore set hold. sem_ids.mutex remains locked - * on exit. +/* Free a semaphore set. freeary() is called with sem_ids.rw_mutex locked + * as a writer and the spinlock for this semaphore set hold. sem_ids.rw_mutex + * remains locked on exit. */ -static void freeary (struct ipc_namespace *ns, struct sem_array *sma, int id) +static void freeary(struct ipc_namespace *ns, struct sem_array *sma) { struct sem_undo *un; struct sem_queue *q; - int size; /* Invalidate the existing undo structures for this semaphore set. * (They will be freed without any further action in exit_sem() @@ -518,12 +572,11 @@ static void freeary (struct ipc_namespace *ns, struct sem_array *sma, int id) q = n; } - /* Remove the semaphore set from the ID array*/ - sma = sem_rmid(ns, id); + /* Remove the semaphore set from the IDR */ + sem_rmid(ns, sma); sem_unlock(sma); ns->used_sems -= sma->sem_nsems; - size = sizeof (*sma) + sma->sem_nsems * sizeof (struct sem); security_sem_free(sma); ipc_rcu_putref(sma); } @@ -576,7 +629,7 @@ static int semctl_nolock(struct ipc_namespace *ns, int semid, int semnum, seminfo.semmnu = SEMMNU; seminfo.semmap = SEMMAP; seminfo.semume = SEMUME; - mutex_lock(&sem_ids(ns).mutex); + down_read(&sem_ids(ns).rw_mutex); if (cmd == SEM_INFO) { seminfo.semusz = sem_ids(ns).in_use; seminfo.semaem = ns->used_sems; @@ -584,8 +637,8 @@ static int semctl_nolock(struct ipc_namespace *ns, int semid, int semnum, seminfo.semusz = SEMUSZ; seminfo.semaem = SEMAEM; } - max_id = sem_ids(ns).max_id; - mutex_unlock(&sem_ids(ns).mutex); + max_id = ipc_get_maxid(&sem_ids(ns)); + up_read(&sem_ids(ns).rw_mutex); if (copy_to_user (arg.__buf, &seminfo, sizeof(struct seminfo))) return -EFAULT; return (max_id < 0) ? 0: max_id; @@ -595,14 +648,9 @@ static int semctl_nolock(struct ipc_namespace *ns, int semid, int semnum, struct semid64_ds tbuf; int id; - if(semid >= sem_ids(ns).entries->size) - return -EINVAL; - - memset(&tbuf,0,sizeof(tbuf)); - sma = sem_lock(ns, semid); - if(sma == NULL) - return -EINVAL; + if (IS_ERR(sma)) + return PTR_ERR(sma); err = -EACCES; if (ipcperms (&sma->sem_perm, S_IRUGO)) @@ -612,7 +660,9 @@ static int semctl_nolock(struct ipc_namespace *ns, int semid, int semnum, if (err) goto out_unlock; - id = sem_buildid(ns, semid, sma->sem_perm.seq); + id = sma->sem_perm.id; + + memset(&tbuf, 0, sizeof(tbuf)); kernel_to_ipc64_perm(&sma->sem_perm, &tbuf.sem_perm); tbuf.sem_otime = sma->sem_otime; @@ -642,16 +692,12 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, ushort* sem_io = fast_sem_io; int nsems; - sma = sem_lock(ns, semid); - if(sma==NULL) - return -EINVAL; + sma = sem_lock_check(ns, semid); + if (IS_ERR(sma)) + return PTR_ERR(sma); nsems = sma->sem_nsems; - err=-EIDRM; - if (sem_checkid(ns,sma,semid)) - goto out_unlock; - err = -EACCES; if (ipcperms (&sma->sem_perm, (cmd==SETVAL||cmd==SETALL)?S_IWUGO:S_IRUGO)) goto out_unlock; @@ -795,7 +841,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, for (un = sma->undo; un; un = un->id_next) un->semadj[semnum] = 0; curr->semval = val; - curr->sempid = current->tgid; + curr->sempid = task_tgid_vnr(current); sma->sem_ctime = get_seconds(); /* maybe some queued-up processes were waiting for this */ update_queue(sma); @@ -863,14 +909,10 @@ static int semctl_down(struct ipc_namespace *ns, int semid, int semnum, if(copy_semid_from_user (&setbuf, arg.buf, version)) return -EFAULT; } - sma = sem_lock(ns, semid); - if(sma==NULL) - return -EINVAL; + sma = sem_lock_check_down(ns, semid); + if (IS_ERR(sma)) + return PTR_ERR(sma); - if (sem_checkid(ns,sma,semid)) { - err=-EIDRM; - goto out_unlock; - } ipcp = &sma->sem_perm; err = audit_ipc_obj(ipcp); @@ -894,7 +936,7 @@ static int semctl_down(struct ipc_namespace *ns, int semid, int semnum, switch(cmd){ case IPC_RMID: - freeary(ns, sma, semid); + freeary(ns, sma); err = 0; break; case IPC_SET: @@ -948,45 +990,15 @@ asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg) return err; case IPC_RMID: case IPC_SET: - mutex_lock(&sem_ids(ns).mutex); + down_write(&sem_ids(ns).rw_mutex); err = semctl_down(ns,semid,semnum,cmd,version,arg); - mutex_unlock(&sem_ids(ns).mutex); + up_write(&sem_ids(ns).rw_mutex); return err; default: return -EINVAL; } } -static inline void lock_semundo(void) -{ - struct sem_undo_list *undo_list; - - undo_list = current->sysvsem.undo_list; - if (undo_list) - spin_lock(&undo_list->lock); -} - -/* This code has an interaction with copy_semundo(). - * Consider; two tasks are sharing the undo_list. task1 - * acquires the undo_list lock in lock_semundo(). If task2 now - * exits before task1 releases the lock (by calling - * unlock_semundo()), then task1 will never call spin_unlock(). - * This leave the sem_undo_list in a locked state. If task1 now creats task3 - * and once again shares the sem_undo_list, the sem_undo_list will still be - * locked, and future SEM_UNDO operations will deadlock. This case is - * dealt with in copy_semundo() by having it reinitialize the spin lock when - * the refcnt goes from 1 to 2. - */ -static inline void unlock_semundo(void) -{ - struct sem_undo_list *undo_list; - - undo_list = current->sysvsem.undo_list; - if (undo_list) - spin_unlock(&undo_list->lock); -} - - /* If the task doesn't already have a undo_list, then allocate one * here. We guarantee there is only one thread using this undo list, * and current is THE ONE @@ -1047,22 +1059,17 @@ static struct sem_undo *find_undo(struct ipc_namespace *ns, int semid) if (error) return ERR_PTR(error); - lock_semundo(); + spin_lock(&ulp->lock); un = lookup_undo(ulp, semid); - unlock_semundo(); + spin_unlock(&ulp->lock); if (likely(un!=NULL)) goto out; /* no undo structure around - allocate one. */ - sma = sem_lock(ns, semid); - un = ERR_PTR(-EINVAL); - if(sma==NULL) - goto out; - un = ERR_PTR(-EIDRM); - if (sem_checkid(ns,sma,semid)) { - sem_unlock(sma); - goto out; - } + sma = sem_lock_check(ns, semid); + if (IS_ERR(sma)) + return ERR_PTR(PTR_ERR(sma)); + nsems = sma->sem_nsems; ipc_rcu_getref(sma); sem_unlock(sma); @@ -1077,10 +1084,10 @@ static struct sem_undo *find_undo(struct ipc_namespace *ns, int semid) new->semadj = (short *) &new[1]; new->semid = semid; - lock_semundo(); + spin_lock(&ulp->lock); un = lookup_undo(ulp, semid); if (un) { - unlock_semundo(); + spin_unlock(&ulp->lock); kfree(new); ipc_lock_by_ptr(&sma->sem_perm); ipc_rcu_putref(sma); @@ -1091,7 +1098,7 @@ static struct sem_undo *find_undo(struct ipc_namespace *ns, int semid) ipc_rcu_putref(sma); if (sma->sem_perm.deleted) { sem_unlock(sma); - unlock_semundo(); + spin_unlock(&ulp->lock); kfree(new); un = ERR_PTR(-EIDRM); goto out; @@ -1102,7 +1109,7 @@ static struct sem_undo *find_undo(struct ipc_namespace *ns, int semid) sma->undo = new; sem_unlock(sma); un = new; - unlock_semundo(); + spin_unlock(&ulp->lock); out: return un; } @@ -1168,15 +1175,14 @@ retry_undos: } else un = NULL; - sma = sem_lock(ns, semid); - error=-EINVAL; - if(sma==NULL) + sma = sem_lock_check(ns, semid); + if (IS_ERR(sma)) { + error = PTR_ERR(sma); goto out_free; - error = -EIDRM; - if (sem_checkid(ns,sma,semid)) - goto out_unlock_free; + } + /* - * semid identifies are not unique - find_undo may have + * semid identifiers are not unique - find_undo may have * allocated an undo structure, it was invalidated by an RMID * and now a new array with received the same id. Check and retry. */ @@ -1196,7 +1202,7 @@ retry_undos: if (error) goto out_unlock_free; - error = try_atomic_semop (sma, sops, nsops, un, current->tgid); + error = try_atomic_semop (sma, sops, nsops, un, task_tgid_vnr(current)); if (error <= 0) { if (alter && error == 0) update_queue (sma); @@ -1211,7 +1217,7 @@ retry_undos: queue.sops = sops; queue.nsops = nsops; queue.undo = un; - queue.pid = current->tgid; + queue.pid = task_tgid_vnr(current); queue.id = semid; queue.alter = alter; if (alter) @@ -1242,7 +1248,7 @@ retry_undos: } sma = sem_lock(ns, semid); - if(sma==NULL) { + if (IS_ERR(sma)) { BUG_ON(queue.prev != NULL); error = -EIDRM; goto out_free; @@ -1279,10 +1285,6 @@ asmlinkage long sys_semop (int semid, struct sembuf __user *tsops, unsigned nsop /* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between * parent and child tasks. - * - * See the notes above unlock_semundo() regarding the spin_lock_init() - * in this code. Initialize the undo_list->lock here instead of get_undo_list() - * because of the reasoning in the comment above unlock_semundo. */ int copy_semundo(unsigned long clone_flags, struct task_struct *tsk) @@ -1342,13 +1344,13 @@ void exit_sem(struct task_struct *tsk) if(semid == -1) continue; sma = sem_lock(ns, semid); - if (sma == NULL) + if (IS_ERR(sma)) continue; if (u->semid == -1) goto next_entry; - BUG_ON(sem_checkid(ns,sma,u->semid)); + BUG_ON(sem_checkid(sma, u->semid)); /* remove u from the sma->undo list */ for (unp = &sma->undo; (un = *unp); unp = &un->id_next) { @@ -1382,7 +1384,7 @@ found: semaphore->semval = 0; if (semaphore->semval > SEMVMX) semaphore->semval = SEMVMX; - semaphore->sempid = current->tgid; + semaphore->sempid = task_tgid_vnr(current); } } sma->sem_otime = get_seconds(); @@ -1402,7 +1404,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it) return seq_printf(s, "%10d %10d %4o %10lu %5u %5u %5u %5u %10lu %10lu\n", sma->sem_perm.key, - sma->sem_id, + sma->sem_perm.id, sma->sem_perm.mode, sma->sem_nsems, sma->sem_perm.uid, |