922 lines
24 KiB
C
922 lines
24 KiB
C
/* $OpenBSD: sysv_sem.c,v 1.63 2022/09/28 13:21:13 mbuhl Exp $ */
|
|
/* $NetBSD: sysv_sem.c,v 1.26 1996/02/09 19:00:25 christos Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2002,2003 Todd C. Miller <millert@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* Sponsored in part by the Defense Advanced Research Projects
|
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
|
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
|
*/
|
|
/*
|
|
* Implementation of SVID semaphores
|
|
*
|
|
* Author: Daniel Boulet
|
|
*
|
|
* This software is provided ``AS IS'' without any warranties of any kind.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/sem.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/pool.h>
|
|
|
|
#include <sys/mount.h>
|
|
#include <sys/syscallargs.h>
|
|
|
|
#ifdef SEM_DEBUG
|
|
#define DPRINTF(x) printf x
|
|
#else
|
|
#define DPRINTF(x)
|
|
#endif
|
|
|
|
int semtot = 0;
|
|
int semutot = 0;
|
|
struct semid_ds **sema; /* semaphore id list */
|
|
SLIST_HEAD(, sem_undo) semu_list; /* list of undo structures */
|
|
struct pool sema_pool; /* pool for struct semid_ds */
|
|
struct pool semu_pool; /* pool for struct sem_undo (SEMUSZ) */
|
|
unsigned short *semseqs; /* array of sem sequence numbers */
|
|
|
|
struct sem_undo *semu_alloc(struct process *);
|
|
int semundo_adjust(struct proc *, struct sem_undo **, int, int, int);
|
|
void semundo_clear(int, int);
|
|
|
|
void
|
|
seminit(void)
|
|
{
|
|
|
|
pool_init(&sema_pool, sizeof(struct semid_ds), 0, 0, PR_WAITOK,
|
|
"semapl", NULL);
|
|
pool_init(&semu_pool, SEMUSZ, 0, 0, PR_WAITOK, "semupl", NULL);
|
|
sema = mallocarray(seminfo.semmni, sizeof(struct semid_ds *),
|
|
M_SEM, M_WAITOK|M_ZERO);
|
|
semseqs = mallocarray(seminfo.semmni, sizeof(unsigned short),
|
|
M_SEM, M_WAITOK|M_ZERO);
|
|
SLIST_INIT(&semu_list);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new sem_undo structure for a process
|
|
* (returns ptr to structure or NULL if no more room)
|
|
*/
|
|
struct sem_undo *
|
|
semu_alloc(struct process *pr)
|
|
{
|
|
struct sem_undo *suptr, *sutmp;
|
|
|
|
if (semutot == seminfo.semmnu)
|
|
return (NULL); /* no space */
|
|
|
|
/*
|
|
* Allocate a semu w/o waiting if possible.
|
|
* If we do have to wait, we must check to verify that a semu
|
|
* with un_proc == pr has not been allocated in the meantime.
|
|
*/
|
|
semutot++;
|
|
if ((suptr = pool_get(&semu_pool, PR_NOWAIT)) == NULL) {
|
|
sutmp = pool_get(&semu_pool, PR_WAITOK);
|
|
SLIST_FOREACH(suptr, &semu_list, un_next) {
|
|
if (suptr->un_proc == pr) {
|
|
pool_put(&semu_pool, sutmp);
|
|
semutot--;
|
|
return (suptr);
|
|
}
|
|
}
|
|
suptr = sutmp;
|
|
}
|
|
suptr->un_cnt = 0;
|
|
suptr->un_proc = pr;
|
|
SLIST_INSERT_HEAD(&semu_list, suptr, un_next);
|
|
return (suptr);
|
|
}
|
|
|
|
/*
|
|
* Adjust a particular entry for a particular proc
|
|
*/
|
|
int
|
|
semundo_adjust(struct proc *p, struct sem_undo **supptr, int semid, int semnum,
|
|
int adjval)
|
|
{
|
|
struct process *pr = p->p_p;
|
|
struct sem_undo *suptr;
|
|
struct undo *sunptr;
|
|
int i;
|
|
|
|
/*
|
|
* Look for and remember the sem_undo if the caller doesn't provide it.
|
|
*/
|
|
suptr = *supptr;
|
|
if (suptr == NULL) {
|
|
SLIST_FOREACH(suptr, &semu_list, un_next) {
|
|
if (suptr->un_proc == pr) {
|
|
*supptr = suptr;
|
|
break;
|
|
}
|
|
}
|
|
if (suptr == NULL) {
|
|
if (adjval == 0)
|
|
return (0);
|
|
suptr = semu_alloc(p->p_p);
|
|
if (suptr == NULL)
|
|
return (ENOSPC);
|
|
*supptr = suptr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for the requested entry and adjust it
|
|
* (delete if adjval becomes 0).
|
|
*/
|
|
sunptr = &suptr->un_ent[0];
|
|
for (i = 0; i < suptr->un_cnt; i++, sunptr++) {
|
|
if (sunptr->un_id != semid || sunptr->un_num != semnum)
|
|
continue;
|
|
if (adjval == 0)
|
|
sunptr->un_adjval = 0;
|
|
else
|
|
sunptr->un_adjval += adjval;
|
|
if (sunptr->un_adjval != 0)
|
|
return (0);
|
|
|
|
if (--suptr->un_cnt == 0) {
|
|
*supptr = NULL;
|
|
SLIST_REMOVE(&semu_list, suptr, sem_undo, un_next);
|
|
pool_put(&semu_pool, suptr);
|
|
semutot--;
|
|
} else if (i < suptr->un_cnt)
|
|
suptr->un_ent[i] =
|
|
suptr->un_ent[suptr->un_cnt];
|
|
return (0);
|
|
}
|
|
|
|
/* Didn't find the right entry - create it */
|
|
if (adjval == 0)
|
|
return (0);
|
|
if (suptr->un_cnt == SEMUME)
|
|
return (EINVAL);
|
|
|
|
sunptr = &suptr->un_ent[suptr->un_cnt];
|
|
suptr->un_cnt++;
|
|
sunptr->un_adjval = adjval;
|
|
sunptr->un_id = semid;
|
|
sunptr->un_num = semnum;
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
semundo_clear(int semid, int semnum)
|
|
{
|
|
struct sem_undo *suptr = SLIST_FIRST(&semu_list);
|
|
struct sem_undo *suprev = NULL;
|
|
struct undo *sunptr;
|
|
int i;
|
|
|
|
while (suptr != NULL) {
|
|
sunptr = &suptr->un_ent[0];
|
|
for (i = 0; i < suptr->un_cnt; i++, sunptr++) {
|
|
if (sunptr->un_id == semid) {
|
|
if (semnum == -1 || sunptr->un_num == semnum) {
|
|
suptr->un_cnt--;
|
|
if (i < suptr->un_cnt) {
|
|
suptr->un_ent[i] =
|
|
suptr->un_ent[suptr->un_cnt];
|
|
i--, sunptr--;
|
|
}
|
|
}
|
|
if (semnum != -1)
|
|
break;
|
|
}
|
|
}
|
|
if (suptr->un_cnt == 0) {
|
|
struct sem_undo *sutmp = suptr;
|
|
|
|
if (suptr == SLIST_FIRST(&semu_list))
|
|
SLIST_REMOVE_HEAD(&semu_list, un_next);
|
|
else
|
|
SLIST_REMOVE_AFTER(suprev, un_next);
|
|
suptr = SLIST_NEXT(suptr, un_next);
|
|
pool_put(&semu_pool, sutmp);
|
|
semutot--;
|
|
} else {
|
|
suprev = suptr;
|
|
suptr = SLIST_NEXT(suptr, un_next);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
sys___semctl(struct proc *p, void *v, register_t *retval)
|
|
{
|
|
struct sys___semctl_args /* {
|
|
syscallarg(int) semid;
|
|
syscallarg(int) semnum;
|
|
syscallarg(int) cmd;
|
|
syscallarg(union semun *) arg;
|
|
} */ *uap = v;
|
|
struct ucred *cred = p->p_ucred;
|
|
int semid = SCARG(uap, semid);
|
|
int semnum = SCARG(uap, semnum);
|
|
int cmd = SCARG(uap, cmd);
|
|
union semun arg, *uarg = SCARG(uap, arg);
|
|
struct semid_ds sbuf;
|
|
struct semid_ds *semaptr;
|
|
unsigned short *semval = NULL, nsems;
|
|
int i, ix, error;
|
|
|
|
switch (cmd) {
|
|
case IPC_SET:
|
|
case IPC_STAT:
|
|
case GETALL:
|
|
case SETVAL:
|
|
case SETALL:
|
|
if ((error = copyin(uarg, &arg, sizeof(union semun))))
|
|
return (error);
|
|
}
|
|
if (cmd == IPC_SET)
|
|
if ((error = copyin(arg.buf, &sbuf, sizeof(sbuf))))
|
|
return (error);
|
|
|
|
DPRINTF(("call to semctl(%d, %d, %d, %p)\n", semid, semnum, cmd, uarg));
|
|
|
|
ix = IPCID_TO_IX(semid);
|
|
if (ix < 0 || ix >= seminfo.semmni)
|
|
return (EINVAL);
|
|
|
|
again:
|
|
if ((semaptr = sema[ix]) == NULL ||
|
|
semaptr->sem_perm.seq != IPCID_TO_SEQ(semid))
|
|
return (EINVAL);
|
|
|
|
switch (cmd) {
|
|
case IPC_RMID:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_M)) != 0)
|
|
return (error);
|
|
semaptr->sem_perm.cuid = cred->cr_uid;
|
|
semaptr->sem_perm.uid = cred->cr_uid;
|
|
semtot -= semaptr->sem_nsems;
|
|
free(semaptr->sem_base, M_SEM,
|
|
semaptr->sem_nsems * sizeof(struct sem));
|
|
pool_put(&sema_pool, semaptr);
|
|
sema[ix] = NULL;
|
|
semundo_clear(ix, -1);
|
|
wakeup(&sema[ix]);
|
|
break;
|
|
|
|
case IPC_SET:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_M)))
|
|
return (error);
|
|
semaptr->sem_perm.uid = sbuf.sem_perm.uid;
|
|
semaptr->sem_perm.gid = sbuf.sem_perm.gid;
|
|
semaptr->sem_perm.mode = (semaptr->sem_perm.mode & ~0777) |
|
|
(sbuf.sem_perm.mode & 0777);
|
|
semaptr->sem_ctime = gettime();
|
|
break;
|
|
|
|
case IPC_STAT:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
return (error);
|
|
memcpy(&sbuf, semaptr, sizeof sbuf);
|
|
sbuf.sem_base = NULL;
|
|
error = copyout(&sbuf, arg.buf, sizeof(struct semid_ds));
|
|
break;
|
|
|
|
case GETNCNT:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
return (error);
|
|
if (semnum < 0 || semnum >= semaptr->sem_nsems)
|
|
return (EINVAL);
|
|
*retval = semaptr->sem_base[semnum].semncnt;
|
|
break;
|
|
|
|
case GETPID:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
return (error);
|
|
if (semnum < 0 || semnum >= semaptr->sem_nsems)
|
|
return (EINVAL);
|
|
*retval = semaptr->sem_base[semnum].sempid;
|
|
break;
|
|
|
|
case GETVAL:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
return (error);
|
|
if (semnum < 0 || semnum >= semaptr->sem_nsems)
|
|
return (EINVAL);
|
|
*retval = semaptr->sem_base[semnum].semval;
|
|
break;
|
|
|
|
case GETALL:
|
|
nsems = semaptr->sem_nsems;
|
|
semval = mallocarray(nsems, sizeof(arg.array[0]),
|
|
M_TEMP, M_WAITOK);
|
|
if (semaptr != sema[ix] ||
|
|
semaptr->sem_perm.seq != IPCID_TO_SEQ(semid) ||
|
|
semaptr->sem_nsems != nsems) {
|
|
free(semval, M_TEMP, nsems * sizeof(arg.array[0]));
|
|
goto again;
|
|
}
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
goto error;
|
|
for (i = 0; i < nsems; i++)
|
|
semval[i] = semaptr->sem_base[i].semval;
|
|
for (i = 0; i < nsems; i++) {
|
|
error = copyout(&semval[i], &arg.array[i],
|
|
sizeof(arg.array[0]));
|
|
if (error != 0)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case GETZCNT:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_R)))
|
|
return (error);
|
|
if (semnum < 0 || semnum >= semaptr->sem_nsems)
|
|
return (EINVAL);
|
|
*retval = semaptr->sem_base[semnum].semzcnt;
|
|
break;
|
|
|
|
case SETVAL:
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_W)))
|
|
return (error);
|
|
if (semnum < 0 || semnum >= semaptr->sem_nsems)
|
|
return (EINVAL);
|
|
if (arg.val > seminfo.semvmx)
|
|
return (ERANGE);
|
|
semaptr->sem_base[semnum].semval = arg.val;
|
|
semundo_clear(ix, semnum);
|
|
wakeup(&sema[ix]);
|
|
break;
|
|
|
|
case SETALL:
|
|
nsems = semaptr->sem_nsems;
|
|
semval = mallocarray(nsems, sizeof(arg.array[0]),
|
|
M_TEMP, M_WAITOK);
|
|
for (i = 0; i < nsems; i++) {
|
|
error = copyin(&arg.array[i], &semval[i],
|
|
sizeof(arg.array[0]));
|
|
if (error != 0)
|
|
goto error;
|
|
if (semval[i] > seminfo.semvmx) {
|
|
error = ERANGE;
|
|
goto error;
|
|
}
|
|
}
|
|
if (semaptr != sema[ix] ||
|
|
semaptr->sem_perm.seq != IPCID_TO_SEQ(semid) ||
|
|
semaptr->sem_nsems != nsems) {
|
|
free(semval, M_TEMP, nsems * sizeof(arg.array[0]));
|
|
goto again;
|
|
}
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_W)))
|
|
goto error;
|
|
for (i = 0; i < nsems; i++)
|
|
semaptr->sem_base[i].semval = semval[i];
|
|
semundo_clear(ix, -1);
|
|
wakeup(&sema[ix]);
|
|
break;
|
|
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
error:
|
|
free(semval, M_TEMP, nsems * sizeof(arg.array[0]));
|
|
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
sys_semget(struct proc *p, void *v, register_t *retval)
|
|
{
|
|
struct sys_semget_args /* {
|
|
syscallarg(key_t) key;
|
|
syscallarg(int) nsems;
|
|
syscallarg(int) semflg;
|
|
} */ *uap = v;
|
|
int semid, error;
|
|
int key = SCARG(uap, key);
|
|
int nsems = SCARG(uap, nsems);
|
|
int semflg = SCARG(uap, semflg);
|
|
struct semid_ds *semaptr, *semaptr_new = NULL;
|
|
struct ucred *cred = p->p_ucred;
|
|
|
|
DPRINTF(("semget(0x%x, %d, 0%o)\n", key, nsems, semflg));
|
|
|
|
/*
|
|
* Preallocate space for the new semaphore. If we are going
|
|
* to sleep, we want to sleep now to eliminate any race
|
|
* condition in allocating a semaphore with a specific key.
|
|
*/
|
|
if (key == IPC_PRIVATE || (semflg & IPC_CREAT)) {
|
|
if (nsems <= 0 || nsems > seminfo.semmsl) {
|
|
DPRINTF(("nsems out of range (0<%d<=%d)\n", nsems,
|
|
seminfo.semmsl));
|
|
return (EINVAL);
|
|
}
|
|
if (nsems > seminfo.semmns - semtot) {
|
|
DPRINTF(("not enough semaphores left (need %d, got %d)\n",
|
|
nsems, seminfo.semmns - semtot));
|
|
return (ENOSPC);
|
|
}
|
|
semaptr_new = pool_get(&sema_pool, PR_WAITOK | PR_ZERO);
|
|
semaptr_new->sem_base = mallocarray(nsems, sizeof(struct sem),
|
|
M_SEM, M_WAITOK|M_ZERO);
|
|
if (nsems > seminfo.semmns - semtot) {
|
|
error = ENOSPC;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (key != IPC_PRIVATE) {
|
|
for (semid = 0, semaptr = NULL; semid < seminfo.semmni; semid++) {
|
|
if ((semaptr = sema[semid]) != NULL &&
|
|
semaptr->sem_perm.key == key) {
|
|
DPRINTF(("found public key\n"));
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm,
|
|
semflg & 0700)))
|
|
goto error;
|
|
if (nsems > 0 && semaptr->sem_nsems < nsems) {
|
|
DPRINTF(("too small\n"));
|
|
error = EINVAL;
|
|
goto error;
|
|
}
|
|
if ((semflg & IPC_CREAT) && (semflg & IPC_EXCL)) {
|
|
DPRINTF(("not exclusive\n"));
|
|
error = EEXIST;
|
|
goto error;
|
|
}
|
|
if (semaptr_new != NULL) {
|
|
free(semaptr_new->sem_base, M_SEM,
|
|
nsems * sizeof(struct sem));
|
|
pool_put(&sema_pool, semaptr_new);
|
|
}
|
|
goto found;
|
|
}
|
|
}
|
|
}
|
|
|
|
DPRINTF(("need to allocate the semid_ds\n"));
|
|
if (key == IPC_PRIVATE || (semflg & IPC_CREAT)) {
|
|
for (semid = 0; semid < seminfo.semmni; semid++) {
|
|
if ((semaptr = sema[semid]) == NULL)
|
|
break;
|
|
}
|
|
if (semid == seminfo.semmni) {
|
|
DPRINTF(("no more semid_ds's available\n"));
|
|
error = ENOSPC;
|
|
goto error;
|
|
}
|
|
DPRINTF(("semid %d is available\n", semid));
|
|
semaptr_new->sem_perm.key = key;
|
|
semaptr_new->sem_perm.cuid = cred->cr_uid;
|
|
semaptr_new->sem_perm.uid = cred->cr_uid;
|
|
semaptr_new->sem_perm.cgid = cred->cr_gid;
|
|
semaptr_new->sem_perm.gid = cred->cr_gid;
|
|
semaptr_new->sem_perm.mode = (semflg & 0777);
|
|
semaptr_new->sem_perm.seq = semseqs[semid] =
|
|
(semseqs[semid] + 1) & 0x7fff;
|
|
semaptr_new->sem_nsems = nsems;
|
|
semaptr_new->sem_otime = 0;
|
|
semaptr_new->sem_ctime = gettime();
|
|
sema[semid] = semaptr_new;
|
|
semtot += nsems;
|
|
} else {
|
|
DPRINTF(("didn't find it and wasn't asked to create it\n"));
|
|
return (ENOENT);
|
|
}
|
|
|
|
found:
|
|
*retval = IXSEQ_TO_IPCID(semid, sema[semid]->sem_perm);
|
|
return (0);
|
|
error:
|
|
if (semaptr_new != NULL) {
|
|
free(semaptr_new->sem_base, M_SEM, nsems * sizeof(struct sem));
|
|
pool_put(&sema_pool, semaptr_new);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
sys_semop(struct proc *p, void *v, register_t *retval)
|
|
{
|
|
struct sys_semop_args /* {
|
|
syscallarg(int) semid;
|
|
syscallarg(struct sembuf *) sops;
|
|
syscallarg(size_t) nsops;
|
|
} */ *uap = v;
|
|
#define NSOPS 8
|
|
struct sembuf sopbuf[NSOPS];
|
|
int semid = SCARG(uap, semid);
|
|
size_t nsops = SCARG(uap, nsops);
|
|
struct sembuf *sops;
|
|
struct semid_ds *semaptr;
|
|
struct sembuf *sopptr = NULL;
|
|
struct sem *semptr = NULL;
|
|
struct sem_undo *suptr = NULL;
|
|
struct ucred *cred = p->p_ucred;
|
|
size_t i, j;
|
|
int do_wakeup, do_undos, error;
|
|
|
|
DPRINTF(("call to semop(%d, %p, %lu)\n", semid, SCARG(uap, sops),
|
|
(u_long)nsops));
|
|
|
|
semid = IPCID_TO_IX(semid); /* Convert back to zero origin */
|
|
|
|
if (semid < 0 || semid >= seminfo.semmni)
|
|
return (EINVAL);
|
|
|
|
if ((semaptr = sema[semid]) == NULL ||
|
|
semaptr->sem_perm.seq != IPCID_TO_SEQ(SCARG(uap, semid)))
|
|
return (EINVAL);
|
|
|
|
if ((error = ipcperm(cred, &semaptr->sem_perm, IPC_W))) {
|
|
DPRINTF(("error = %d from ipaccess\n", error));
|
|
return (error);
|
|
}
|
|
|
|
if (nsops == 0) {
|
|
*retval = 0;
|
|
return (0);
|
|
} else if (nsops > (size_t)seminfo.semopm) {
|
|
DPRINTF(("too many sops (max=%d, nsops=%lu)\n", seminfo.semopm,
|
|
(u_long)nsops));
|
|
return (E2BIG);
|
|
}
|
|
|
|
if (nsops <= NSOPS)
|
|
sops = sopbuf;
|
|
else
|
|
sops = mallocarray(nsops, sizeof(struct sembuf), M_SEM, M_WAITOK);
|
|
error = copyin(SCARG(uap, sops), sops, nsops * sizeof(struct sembuf));
|
|
if (error != 0) {
|
|
DPRINTF(("error = %d from copyin(%p, %p, %u)\n", error,
|
|
SCARG(uap, sops), &sops, nsops * sizeof(struct sembuf)));
|
|
goto done2;
|
|
}
|
|
|
|
/*
|
|
* Loop trying to satisfy the vector of requests.
|
|
* If we reach a point where we must wait, any requests already
|
|
* performed are rolled back and we go to sleep until some other
|
|
* process wakes us up. At this point, we start all over again.
|
|
*
|
|
* This ensures that from the perspective of other tasks, a set
|
|
* of requests is atomic (never partially satisfied).
|
|
*/
|
|
do_undos = 0;
|
|
|
|
for (;;) {
|
|
do_wakeup = 0;
|
|
|
|
for (i = 0; i < nsops; i++) {
|
|
sopptr = &sops[i];
|
|
|
|
if (sopptr->sem_num >= semaptr->sem_nsems) {
|
|
error = EFBIG;
|
|
goto done2;
|
|
}
|
|
|
|
semptr = &semaptr->sem_base[sopptr->sem_num];
|
|
|
|
DPRINTF(("semop: semaptr=%x, sem_base=%x, semptr=%x, sem[%d]=%d : op=%d, flag=%s\n",
|
|
semaptr, semaptr->sem_base, semptr,
|
|
sopptr->sem_num, semptr->semval, sopptr->sem_op,
|
|
(sopptr->sem_flg & IPC_NOWAIT) ? "nowait" : "wait"));
|
|
|
|
if (sopptr->sem_op < 0) {
|
|
if ((int)(semptr->semval +
|
|
sopptr->sem_op) < 0) {
|
|
DPRINTF(("semop: can't do it now\n"));
|
|
break;
|
|
} else {
|
|
semptr->semval += sopptr->sem_op;
|
|
if (semptr->semval == 0 &&
|
|
semptr->semzcnt > 0)
|
|
do_wakeup = 1;
|
|
}
|
|
if (sopptr->sem_flg & SEM_UNDO)
|
|
do_undos++;
|
|
} else if (sopptr->sem_op == 0) {
|
|
if (semptr->semval > 0) {
|
|
DPRINTF(("semop: not zero now\n"));
|
|
break;
|
|
}
|
|
} else {
|
|
if (semptr->semncnt > 0)
|
|
do_wakeup = 1;
|
|
semptr->semval += sopptr->sem_op;
|
|
if (sopptr->sem_flg & SEM_UNDO)
|
|
do_undos++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Did we get through the entire vector and can we undo it?
|
|
*/
|
|
if (i >= nsops && do_undos <= SEMUME)
|
|
goto done;
|
|
|
|
/*
|
|
* No ... rollback anything that we've already done
|
|
*/
|
|
DPRINTF(("semop: rollback 0 through %d\n", i - 1));
|
|
for (j = 0; j < i; j++)
|
|
semaptr->sem_base[sops[j].sem_num].semval -=
|
|
sops[j].sem_op;
|
|
|
|
/*
|
|
* Did we have too many SEM_UNDO's
|
|
*/
|
|
if (do_undos > SEMUME) {
|
|
error = ENOSPC;
|
|
goto done2;
|
|
}
|
|
|
|
/*
|
|
* If the request that we couldn't satisfy has the
|
|
* NOWAIT flag set then return with EAGAIN.
|
|
*/
|
|
if (sopptr->sem_flg & IPC_NOWAIT) {
|
|
error = EAGAIN;
|
|
goto done2;
|
|
}
|
|
|
|
if (sopptr->sem_op == 0)
|
|
semptr->semzcnt++;
|
|
else
|
|
semptr->semncnt++;
|
|
|
|
DPRINTF(("semop: good night!\n"));
|
|
error = tsleep_nsec(&sema[semid], PLOCK | PCATCH,
|
|
"semwait", INFSLP);
|
|
DPRINTF(("semop: good morning (error=%d)!\n", error));
|
|
|
|
suptr = NULL; /* sem_undo may have been reallocated */
|
|
|
|
/*
|
|
* Make sure that the semaphore still exists
|
|
*/
|
|
if (sema[semid] == NULL ||
|
|
semaptr->sem_perm.seq != IPCID_TO_SEQ(SCARG(uap, semid))) {
|
|
error = EIDRM;
|
|
goto done2;
|
|
}
|
|
|
|
/*
|
|
* The semaphore is still alive. Readjust the count of
|
|
* waiting processes.
|
|
*/
|
|
if (sopptr->sem_op == 0)
|
|
semptr->semzcnt--;
|
|
else
|
|
semptr->semncnt--;
|
|
|
|
/*
|
|
* Is it really morning, or was our sleep interrupted?
|
|
* (Delayed check of tsleep() return code because we
|
|
* need to decrement sem[nz]cnt either way.)
|
|
*/
|
|
if (error != 0) {
|
|
error = EINTR;
|
|
goto done2;
|
|
}
|
|
DPRINTF(("semop: good morning!\n"));
|
|
}
|
|
|
|
done:
|
|
/*
|
|
* Process any SEM_UNDO requests.
|
|
*/
|
|
if (do_undos) {
|
|
for (i = 0; i < nsops; i++) {
|
|
/*
|
|
* We only need to deal with SEM_UNDO's for non-zero
|
|
* op's.
|
|
*/
|
|
int adjval;
|
|
|
|
if ((sops[i].sem_flg & SEM_UNDO) == 0)
|
|
continue;
|
|
adjval = sops[i].sem_op;
|
|
if (adjval == 0)
|
|
continue;
|
|
error = semundo_adjust(p, &suptr, semid,
|
|
sops[i].sem_num, -adjval);
|
|
if (error == 0)
|
|
continue;
|
|
|
|
/*
|
|
* Uh-Oh! We ran out of either sem_undo's or undo's.
|
|
* Rollback the adjustments to this point and then
|
|
* rollback the semaphore ups and down so we can return
|
|
* with an error with all structures restored. We
|
|
* rollback the undo's in the exact reverse order that
|
|
* we applied them. This guarantees that we won't run
|
|
* out of space as we roll things back out.
|
|
*/
|
|
for (j = i; j > 0;) {
|
|
j--;
|
|
if ((sops[j].sem_flg & SEM_UNDO) == 0)
|
|
continue;
|
|
adjval = sops[j].sem_op;
|
|
if (adjval == 0)
|
|
continue;
|
|
if (semundo_adjust(p, &suptr, semid,
|
|
sops[j].sem_num, adjval) != 0)
|
|
panic("semop - can't undo undos");
|
|
}
|
|
|
|
for (j = 0; j < nsops; j++)
|
|
semaptr->sem_base[sops[j].sem_num].semval -=
|
|
sops[j].sem_op;
|
|
|
|
DPRINTF(("error = %d from semundo_adjust\n", error));
|
|
goto done2;
|
|
} /* loop through the sops */
|
|
} /* if (do_undos) */
|
|
|
|
/* We're definitely done - set the sempid's */
|
|
for (i = 0; i < nsops; i++) {
|
|
sopptr = &sops[i];
|
|
semptr = &semaptr->sem_base[sopptr->sem_num];
|
|
semptr->sempid = p->p_p->ps_pid;
|
|
}
|
|
|
|
semaptr->sem_otime = gettime();
|
|
|
|
/* Do a wakeup if any semaphore was up'd. */
|
|
if (do_wakeup) {
|
|
DPRINTF(("semop: doing wakeup\n"));
|
|
wakeup(&sema[semid]);
|
|
DPRINTF(("semop: back from wakeup\n"));
|
|
}
|
|
DPRINTF(("semop: done\n"));
|
|
*retval = 0;
|
|
done2:
|
|
if (sops != sopbuf)
|
|
free(sops, M_SEM, nsops * sizeof(struct sembuf));
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Go through the undo structures for this process and apply the adjustments to
|
|
* semaphores.
|
|
*/
|
|
void
|
|
semexit(struct process *pr)
|
|
{
|
|
struct sem_undo *suptr;
|
|
struct sem_undo **supptr;
|
|
|
|
/*
|
|
* Go through the chain of undo vectors looking for one associated with
|
|
* this process. Remember the pointer to the pointer to the element
|
|
* to dequeue it later.
|
|
*/
|
|
supptr = &SLIST_FIRST(&semu_list);
|
|
SLIST_FOREACH(suptr, &semu_list, un_next) {
|
|
if (suptr->un_proc == pr)
|
|
break;
|
|
supptr = &SLIST_NEXT(suptr, un_next);
|
|
}
|
|
|
|
/*
|
|
* If there is no undo vector, skip to the end.
|
|
*/
|
|
if (suptr == NULL)
|
|
return;
|
|
|
|
/*
|
|
* We now have an undo vector for this process.
|
|
*/
|
|
DPRINTF(("process @%p has undo structure with %d entries\n", pr,
|
|
suptr->un_cnt));
|
|
|
|
/*
|
|
* If there are any active undo elements then process them.
|
|
*/
|
|
if (suptr->un_cnt > 0) {
|
|
int ix;
|
|
|
|
for (ix = 0; ix < suptr->un_cnt; ix++) {
|
|
int semid = suptr->un_ent[ix].un_id;
|
|
int semnum = suptr->un_ent[ix].un_num;
|
|
int adjval = suptr->un_ent[ix].un_adjval;
|
|
struct semid_ds *semaptr;
|
|
|
|
if ((semaptr = sema[semid]) == NULL)
|
|
panic("semexit - semid not allocated");
|
|
if (semnum >= semaptr->sem_nsems)
|
|
panic("semexit - semnum out of range");
|
|
|
|
DPRINTF(("semexit: %p id=%d num=%d(adj=%d) ; sem=%d\n",
|
|
suptr->un_proc, suptr->un_ent[ix].un_id,
|
|
suptr->un_ent[ix].un_num,
|
|
suptr->un_ent[ix].un_adjval,
|
|
semaptr->sem_base[semnum].semval));
|
|
|
|
if (adjval < 0 &&
|
|
semaptr->sem_base[semnum].semval < -adjval)
|
|
semaptr->sem_base[semnum].semval = 0;
|
|
else
|
|
semaptr->sem_base[semnum].semval += adjval;
|
|
|
|
wakeup(&sema[semid]);
|
|
DPRINTF(("semexit: back from wakeup\n"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Deallocate the undo vector.
|
|
*/
|
|
DPRINTF(("removing vector\n"));
|
|
*supptr = SLIST_NEXT(suptr, un_next);
|
|
pool_put(&semu_pool, suptr);
|
|
semutot--;
|
|
}
|
|
|
|
/* Expand semsegs and semseqs arrays */
|
|
void
|
|
sema_reallocate(int val)
|
|
{
|
|
struct semid_ds **sema_new;
|
|
unsigned short *newseqs;
|
|
sema_new = mallocarray(val, sizeof(struct semid_ds *),
|
|
M_SEM, M_WAITOK|M_ZERO);
|
|
memcpy(sema_new, sema,
|
|
seminfo.semmni * sizeof(struct semid_ds *));
|
|
newseqs = mallocarray(val, sizeof(unsigned short), M_SEM,
|
|
M_WAITOK|M_ZERO);
|
|
memcpy(newseqs, semseqs,
|
|
seminfo.semmni * sizeof(unsigned short));
|
|
free(sema, M_SEM, seminfo.semmni * sizeof(struct semid_ds *));
|
|
free(semseqs, M_SEM, seminfo.semmni * sizeof(unsigned short));
|
|
sema = sema_new;
|
|
semseqs = newseqs;
|
|
seminfo.semmni = val;
|
|
}
|
|
|
|
const struct sysctl_bounded_args sysvsem_vars[] = {
|
|
{ KERN_SEMINFO_SEMUME, &seminfo.semume, SYSCTL_INT_READONLY },
|
|
{ KERN_SEMINFO_SEMUSZ, &seminfo.semusz, SYSCTL_INT_READONLY },
|
|
{ KERN_SEMINFO_SEMVMX, &seminfo.semvmx, SYSCTL_INT_READONLY },
|
|
{ KERN_SEMINFO_SEMAEM, &seminfo.semaem, SYSCTL_INT_READONLY },
|
|
{ KERN_SEMINFO_SEMOPM, &seminfo.semopm, 1, INT_MAX },
|
|
};
|
|
|
|
/*
|
|
* Userland access to struct seminfo.
|
|
*/
|
|
int
|
|
sysctl_sysvsem(int *name, u_int namelen, void *oldp, size_t *oldlenp,
|
|
void *newp, size_t newlen)
|
|
{
|
|
int error, val;
|
|
|
|
if (namelen != 1)
|
|
return (ENOTDIR); /* leaf-only */
|
|
|
|
switch (name[0]) {
|
|
case KERN_SEMINFO_SEMMNI:
|
|
val = seminfo.semmni;
|
|
error = sysctl_int_bounded(oldp, oldlenp, newp, newlen,
|
|
&val, val, 0xffff);
|
|
/* returns success and skips reallocation if val is unchanged */
|
|
if (error || val == seminfo.semmni)
|
|
return (error);
|
|
sema_reallocate(val);
|
|
return (0);
|
|
case KERN_SEMINFO_SEMMNS:
|
|
/* can't decrease semmns or go over 2^16 */
|
|
return (sysctl_int_bounded(oldp, oldlenp, newp, newlen,
|
|
&seminfo.semmns, seminfo.semmns, 0xffff));
|
|
case KERN_SEMINFO_SEMMNU:
|
|
/* can't decrease semmnu or go over 2^16 */
|
|
return (sysctl_int_bounded(oldp, oldlenp, newp, newlen,
|
|
&seminfo.semmnu, seminfo.semmnu, 0xffff));
|
|
case KERN_SEMINFO_SEMMSL:
|
|
/* can't decrease semmsl or go over 2^16 */
|
|
return (sysctl_int_bounded(oldp, oldlenp, newp, newlen,
|
|
&seminfo.semmsl, seminfo.semmsl, 0xffff));
|
|
default:
|
|
return (sysctl_bounded_arr(sysvsem_vars, nitems(sysvsem_vars),
|
|
name, namelen, oldp, oldlenp, newp, newlen));
|
|
}
|
|
/* NOTREACHED */
|
|
}
|