Files
Arquivotheca.Solaris-2.5/uts/common/syscall/sem.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

1148 lines
28 KiB
C
Executable File

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* Copyright (c) 1994 Sun Microsystems, Inc. */
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
/* The copyright notice above does not evidence any */
/* actual or intended publication of such source code. */
#pragma ident "@(#)sem.c 1.52 95/08/08 SMI" /* from S5R4 1.17 */
/*
* Inter-Process Communication Semaphore Facility.
*/
#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/map.h>
#include <sys/kmem.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/cpuvar.h>
#include <sys/debug.h>
#include <sys/var.h>
#include <sys/cmn_err.h>
/*
* Protects all sem data structures.
*/
static kmutex_t sem_lock;
static kmutex_t *sema_locks;
/*
* ATTENTION: All ye who enter here
* As an optimization, all global data items are declared in space.c
* When this module get unloaded, the data stays around. The data
* is only allocated on first load.
*
* XXX Unloading disabled - there's more to leaving state
* lying around in the system than not freeing global data.
*/
/*
* The following variables seminfo_* are there so that the
* elements of the data structure seminfo can be tuned
* (if necessary) using the /etc/system file syntax for
* tuning of integer data types.
*/
int seminfo_semmap = 10; /* # of entries in semaphore map */
int seminfo_semmni = 10; /* # of semaphore identifiers */
int seminfo_semmns = 60; /* # of semaphores in system */
int seminfo_semmnu = 30; /* # of undo structures in system */
int seminfo_semmsl = 25; /* max # of semaphores per id */
int seminfo_semopm = 10; /* max # of operations per semop call */
int seminfo_semume = 10; /* max # of undo entries per process */
int seminfo_semusz = 96; /* size in bytes of undo structure */
int seminfo_semvmx = 32767; /* semaphore maximum value */
int seminfo_semaem = 16384; /* adjust on exit max value */
/*
* Each semid is made up of:
* 16 bit index into sema array
* 16 bit sequence number
*/
#define SEMA_INDEX_MAX 0xffff
#define SEMA_INDEX_MASK 0xffff
#define SEMA_SEQ_MAX 0x7fff
#define SEMA_SEQ_MASK 0x7fff
#define SEMA_SEQ_SHIFT 16
/* define macro to round up to integer size from machdep.c in sun4c/os */
#define INTSZ(X) howmany((X), sizeof (int))
/*
* Private functions
*/
static int semconv(int);
static kmutex_t *semconv_lock(int, struct semid_ds **);
static int semsys(int opcode, int a0, int a1, int a2, int a3);
#include <sys/modctl.h>
#include <sys/syscall.h>
static struct sysent ipcsem_sysent = {
5,
SE_NOUNLOAD | SE_ARGC,
semsys
};
/*
* Module linkage information for the kernel.
*/
static struct modlsys modlsys = {
&mod_syscallops, "System V semaphore facility", &ipcsem_sysent
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlsys, NULL
};
char _depends_on[] = "misc/ipc"; /* ipcaccess, ipcget */
int
_init(void)
{
int retval;
register int i;
u_longlong_t mavail;
/*
* seminfo_* are inited to default values above.
* These values can be tuned if need be using the
* integer tuning capabilities in the /etc/system file.
*/
seminfo.semmap = seminfo_semmap;
if ((seminfo.semmni = seminfo_semmni) > SEMA_INDEX_MAX) {
seminfo.semmni = SEMA_INDEX_MAX;
cmn_err(CE_WARN, "seminit: seminfo_semmni too large. Using %d",
SEMA_INDEX_MAX);
}
seminfo.semmns = seminfo_semmns;
seminfo.semmnu = seminfo_semmnu;
seminfo.semmsl = seminfo_semmsl;
seminfo.semopm = seminfo_semopm;
seminfo.semume = seminfo_semume;
seminfo.semvmx = seminfo_semvmx;
seminfo.semaem = seminfo_semaem;
seminfo.semusz = sizeof (struct sem_undo) +
seminfo.semume * sizeof (struct undo);
/*
* Don't use more than 25% of the available kernel memory
*/
mavail = kmem_maxavail() / 4;
if (((u_longlong_t)seminfo.semmns * sizeof (struct sem) +
(u_longlong_t)seminfo.semmni * sizeof (struct semid_ds) +
(u_longlong_t)seminfo.semmni * sizeof (kmutex_t) +
(u_longlong_t)v.v_proc * sizeof (struct sem_undo *) +
INTSZ((u_longlong_t)seminfo.semusz * seminfo.semmnu) * sizeof (int))
> mavail) {
cmn_err(CE_WARN,
"semsys: can't load module, too much memory requested");
return (ENOMEM);
}
ASSERT(semmap == NULL);
semmap = rmallocmap(seminfo.semmap);
if (semmap == NULL) {
cmn_err(CE_WARN,
"semsys: can't load module, too much rmap requested");
return (ENOMEM);
}
rmfree(semmap, (size_t)seminfo.semmns, (ulong_t)1);
mutex_init(&sem_lock, "Sys V semaphore", MUTEX_DEFAULT, NULL);
ASSERT(sem == NULL);
ASSERT(sema == NULL);
ASSERT(sem_undo == NULL);
ASSERT(semu == NULL);
ASSERT(sema_locks == NULL);
sem = kmem_zalloc(seminfo.semmns * sizeof (struct sem), KM_SLEEP);
sema = kmem_zalloc(seminfo.semmni * sizeof (struct semid_ds), KM_SLEEP);
sem_undo = kmem_zalloc(v.v_proc * sizeof (struct sem_undo *), KM_SLEEP);
semu = kmem_zalloc((INTSZ(seminfo.semusz * seminfo.semmnu) *
sizeof (int)), KM_SLEEP);
sema_locks = kmem_zalloc(seminfo.semmni * sizeof (kmutex_t), KM_SLEEP);
/*
* link all free undo structures together
*/
ASSERT(semfup == NULL);
semfup = (struct sem_undo *)semu;
for (i = 0; i < seminfo.semmnu - 1; i++) {
semfup->un_np =
(struct sem_undo *)((uint)semfup + seminfo.semusz);
semfup = semfup->un_np;
}
semfup->un_np = NULL;
semfup = (struct sem_undo *)semu;
for (i = 0; i < seminfo.semmni; i++)
mutex_init(&sema_locks[i], "sema locks", MUTEX_DEFAULT, NULL);
if ((retval = mod_install(&modlinkage)) == 0)
return (0);
for (i = 0; i < seminfo.semmni; i++)
mutex_destroy(&sema_locks[i]);
kmem_free(sem, seminfo.semmns * sizeof (struct sem));
kmem_free(sema, seminfo.semmni * sizeof (struct semid_ds));
kmem_free(sem_undo, v.v_proc * sizeof (struct sem_undo *));
kmem_free(semu, (INTSZ(seminfo.semusz * seminfo.semmnu) *
sizeof (int)));
kmem_free(sema_locks, seminfo.semmni * sizeof (kmutex_t));
sem = NULL;
sema = NULL;
sem_undo = NULL;
semu = NULL;
sema_locks = NULL;
semfup = NULL;
rmfreemap(semmap);
semmap = NULL;
mutex_destroy(&sem_lock);
return (retval);
}
/*
* See comments in shm.c
*/
int
_fini(void)
{
return (EBUSY);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* Argument vectors for the various flavors of semsys().
*/
#define SEMCTL 0
#define SEMGET 1
#define SEMOP 2
/*
* semaoe - Create or update adjust on exit entry.
*/
static int
semaoe(
short val, /* operation value to be adjusted on exit */
int id, /* semid */
ushort num) /* semaphore number */
{
register struct undo *uup; /* ptr to entry to update */
register struct undo *uup2; /* ptr to move entry */
register struct sem_undo *up; /* ptr to process undo struct */
register struct sem_undo *up2; /* ptr to undo list */
register int i; /* loop control */
register int found; /* matching entry found flag */
register proc_t *p;
if (val == 0)
return (0);
if (val > seminfo.semaem || val < -seminfo.semaem)
return (ERANGE);
/*
* If this process doesn't yet have any undo structures,
* get a free one and initialize it.
*/
mutex_enter(&sem_lock);
p = curproc;
if ((up = sem_undo[p->p_slot]) == NULL)
if ((up = semfup) == NULL) {
mutex_exit(&sem_lock);
return (ENOSPC);
} else {
semfup = up->un_np;
up->un_np = NULL;
sem_undo[p->p_slot] = up;
}
/*
* Check for an existing undo structure for this semaphore.
*/
for (uup = up->un_ent, found = i = 0; i < up->un_cnt; i++) {
if (uup->un_id < id ||
(uup->un_id == id && uup->un_num < num)) {
uup++;
continue;
}
if (uup->un_id == id && uup->un_num == num)
found = 1;
break;
}
/*
* If this is a new undo structure, link it into the active undo list.
* Then look for the undo entry insertion point, keeping the entries
* sorted by id; if necessary, shuffle remaining entries up.
*/
if (!found) {
if (up->un_cnt >= seminfo.semume) {
mutex_exit(&sem_lock);
return (EINVAL);
}
if (up->un_cnt == 0) {
up->un_np = semunp;
semunp = up;
}
uup2 = &up->un_ent[up->un_cnt++];
while (uup2-- > uup)
*(uup2 + 1) = *uup2;
/* Insert a new undo entry, then done */
uup->un_id = id;
uup->un_num = num;
uup->un_aoe = -val;
mutex_exit(&sem_lock);
return (0);
}
/*
* If an entry already existed for this semaphore, adjust it.
*/
uup->un_aoe -= val;
if (uup->un_aoe > seminfo.semaem || uup->un_aoe < -seminfo.semaem) {
uup->un_aoe += val;
mutex_exit(&sem_lock);
return (ERANGE);
}
/*
* If new adjust-on-exit value is zero, remove this undo entry
* from the list. If new undo entry count is zero, remove this
* undo structure from the active undo list; however, retain this
* structure for the current process until semexit().
*/
if (uup->un_aoe == 0) {
uup2 = &up->un_ent[--(up->un_cnt)];
while (uup++ < uup2)
*(uup - 1) = *uup;
if (up->un_cnt == 0) {
/* Remove process from undo list. */
if (semunp == up)
semunp = up->un_np;
else {
for (up2 = semunp; up2 != NULL;
up2 = up2->un_np) {
if (up2->un_np == up) {
up2->un_np = up->un_np;
break;
}
}
}
up->un_np = NULL;
}
}
mutex_exit(&sem_lock);
return (0);
}
/*
* semunrm - Undo entry remover.
*
* This routine is called to clear all undo entries for a set of semaphores
* that are being removed from the system or are being reset by SETVAL or
* SETVALS commands to semctl.
*/
static void
semunrm(
int id, /* semid */
ushort low, /* lowest semaphore being changed */
ushort high) /* highest semaphore being changed */
{
register struct sem_undo *pp; /* ptr to predecessor to p */
register struct sem_undo *p; /* ptr to current entry */
register struct undo *up; /* ptr to undo entry */
register int i; /* loop control */
register int j; /* loop control */
pp = NULL;
mutex_enter(&sem_lock);
p = semunp;
while (p != NULL) {
/* Search through current structure for matching entries. */
for (up = p->un_ent, i = 0; i < p->un_cnt; /* CSTYLED */) {
if (id < up->un_id)
break;
if (id > up->un_id || low > up->un_num) {
up++;
i++;
continue;
}
if (high < up->un_num)
break;
/* squeeze out one entry */
for (j = i; ++j < p->un_cnt; /* CSTYLED */)
p->un_ent[j - 1] = p->un_ent[j];
p->un_cnt--;
}
/* Reset pointers for next round. */
if (p->un_cnt == 0) {
/* Remove from linked list. */
if (pp == NULL) {
semunp = p->un_np;
p->un_np = NULL;
p = semunp;
} else {
pp->un_np = p->un_np;
p->un_np = NULL;
p = pp->un_np;
}
} else {
pp = p;
p = p->un_np;
}
}
mutex_exit(&sem_lock);
}
/*
* semundo - Undo work done up to finding an operation that can't be done.
*/
static void
semundo(
register struct sembuf *op, /* first operation that was done ptr */
register int n, /* # of operations that were done */
register int id, /* semaphore id */
register struct semid_ds *sp) /* semaphore data structure ptr */
{
register struct sem *semp; /* semaphore ptr */
for (op += n - 1; n--; op--) {
if (op->sem_op == 0)
continue;
semp = sp->sem_base + op->sem_num;
semp->semval -= op->sem_op;
if (op->sem_flg & SEM_UNDO)
(void) semaoe(-op->sem_op, id, op->sem_num);
}
}
/*
* semconv_lock - Convert user supplied semid into a ptr to the associated
* semaphore header.
*
* Return the associated semaphore lock (held). NULL on errors.
*/
static kmutex_t *
semconv_lock(
register int s, /* semid */
struct semid_ds **spp) /* semaphore header to be returned */
{
register struct semid_ds *sp; /* ptr to associated header */
int index;
int sequence;
index = s & SEMA_INDEX_MASK;
if (index >= seminfo.semmni)
return (NULL);
sequence = (s >> SEMA_SEQ_SHIFT) & SEMA_SEQ_MASK;
sp = &sema[index];
mutex_enter(&sema_locks[index]);
if ((sp->sem_perm.mode & IPC_ALLOC) == 0 ||
sequence != sp->sem_perm.seq) {
mutex_exit(&sema_locks[index]);
return (NULL);
}
*spp = sp;
return (&sema_locks[index]);
}
/*
* Return 0 if semaphore really exists, EINVAL otherwise.
*
* We assume the semid_ds is already locked.
*/
static int
semconv(register int s) /* semid */
{
register struct semid_ds *sp; /* ptr to associated header */
ulong sequence;
int index;
sequence = (s >> SEMA_SEQ_SHIFT) & SEMA_SEQ_MASK;
index = s & SEMA_INDEX_MASK;
if (index >= seminfo.semmni)
return (EINVAL);
sp = &sema[index];
ASSERT(MUTEX_HELD(&sema_locks[s & SEMA_INDEX_MASK]));
if ((sp->sem_perm.mode & IPC_ALLOC) == 0 ||
sequence != sp->sem_perm.seq) {
return (EINVAL);
}
return (0);
}
/*
* semctl - Semctl system call.
*/
static int
semctl(int semid, uint semnum, int cmd, int arg)
{
struct semid_ds *sp; /* ptr to semaphore header */
register struct sem *p; /* ptr to semaphore */
register unsigned int i; /* loop control */
register int error = 0;
register int retval = 0;
caddr_t base;
register struct cred *cr;
kmutex_t *lock;
cr = CRED();
if ((lock = semconv_lock(semid, &sp)) == NULL)
return (set_errno(EINVAL));
switch (cmd) {
/* Remove semaphore set. */
case IPC_O_RMID:
case IPC_RMID:
if (cr->cr_uid != sp->sem_perm.uid &&
cr->cr_uid != sp->sem_perm.cuid && !suser(cr)) {
mutex_exit(lock);
return (set_errno(EPERM));
}
semunrm(semid, 0, sp->sem_nsems);
for (i = sp->sem_nsems, p = sp->sem_base; i--; p++) {
p->semval = p->sempid = 0;
if (p->semncnt) {
cv_broadcast(&p->semncnt_cv);
p->semncnt = 0;
}
if (p->semzcnt) {
cv_broadcast(&p->semzcnt_cv);
p->semzcnt = 0;
}
}
rmfree(semmap, (long)sp->sem_nsems,
(ulong)(sp->sem_base-sem) + 1);
if (((semid >> SEMA_SEQ_SHIFT) & SEMA_SEQ_MASK) ==
SEMA_SEQ_MAX)
sp->sem_perm.seq = 0;
else
sp->sem_perm.seq++;
sp->sem_perm.mode = 0;
mutex_exit(lock);
return (0);
/* Set ownership and permissions. */
case IPC_SET: {
struct semid_ds id;
if (cr->cr_uid != sp->sem_perm.uid &&
cr->cr_uid != sp->sem_perm.cuid && !suser(cr)) {
mutex_exit(lock);
return (set_errno(EPERM));
}
if (copyin((caddr_t)arg, (caddr_t)&id, sizeof (id))) {
mutex_exit(lock);
return (set_errno(EFAULT));
}
if (id.sem_perm.uid < 0 || id.sem_perm.uid > MAXUID ||
id.sem_perm.gid < 0 || id.sem_perm.gid > MAXUID) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
sp->sem_perm.uid = id.sem_perm.uid;
sp->sem_perm.gid = id.sem_perm.gid;
sp->sem_perm.mode = id.sem_perm.mode & 0777 | IPC_ALLOC;
sp->sem_ctime = hrestime.tv_sec;
mutex_exit(lock);
return (0);
}
case IPC_O_SET: {
struct o_semid_ds id;
if (cr->cr_uid != sp->sem_perm.uid &&
cr->cr_uid != sp->sem_perm.cuid && !suser(cr)) {
mutex_exit(lock);
return (set_errno(EPERM));
}
if (copyin((caddr_t)arg, (caddr_t)&id, sizeof (id))) {
mutex_exit(lock);
return (set_errno(EFAULT));
}
/* XXX Iff MAXUID > USHRT_MAX this becomes a bit silly */
if ((u_long)id.sem_perm.uid > MAXUID ||
(u_long)id.sem_perm.gid > MAXUID) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
sp->sem_perm.uid = id.sem_perm.uid;
sp->sem_perm.gid = id.sem_perm.gid;
sp->sem_perm.mode = id.sem_perm.mode & 0777 | IPC_ALLOC;
sp->sem_ctime = hrestime.tv_sec;
mutex_exit(lock);
return (0);
}
/* Get semaphore data structure. */
case IPC_O_STAT: {
struct o_semid_ds id;
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
/*
* copy expanded semid_ds structure to an SVR3 semid_ds version.
* Check whether SVR4 values are too large to store into an SVR3
* semid_ds structure.
*/
if ((unsigned)sp->sem_perm.uid > USHRT_MAX ||
(unsigned)sp->sem_perm.gid > USHRT_MAX ||
(unsigned)sp->sem_perm.cuid > USHRT_MAX ||
(unsigned)sp->sem_perm.cgid > USHRT_MAX ||
(unsigned)sp->sem_perm.seq > USHRT_MAX) {
mutex_exit(lock);
return (set_errno(EOVERFLOW));
}
id.sem_perm.uid = (o_uid_t)sp->sem_perm.uid;
id.sem_perm.gid = (o_gid_t)sp->sem_perm.gid;
id.sem_perm.cuid = (o_uid_t)sp->sem_perm.cuid;
id.sem_perm.cgid = (o_gid_t)sp->sem_perm.cgid;
id.sem_perm.mode = (o_mode_t)sp->sem_perm.mode;
id.sem_perm.seq = (ushort)sp->sem_perm.seq;
id.sem_perm.key = sp->sem_perm.key;
id.sem_base = NULL; /* kernel addr */
id.sem_nsems = sp->sem_nsems;
id.sem_otime = sp->sem_otime;
id.sem_ctime = sp->sem_ctime;
mutex_exit(lock);
if (copyout((caddr_t)&id, (caddr_t)arg, sizeof (id)))
return (set_errno(EFAULT));
return (0);
}
case IPC_STAT:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (copyout((caddr_t)sp, (caddr_t)arg, sizeof (*sp))) {
mutex_exit(lock);
return (set_errno(EFAULT));
}
mutex_exit(lock);
return (0);
/* Get # of processes sleeping for greater semval. */
case GETNCNT:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (semnum >= sp->sem_nsems) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
retval = (sp->sem_base + semnum)->semncnt;
mutex_exit(lock);
return (retval);
/* Get pid of last process to operate on semaphore. */
case GETPID:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (semnum >= sp->sem_nsems) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
retval = (sp->sem_base + semnum)->sempid;
mutex_exit(lock);
return (retval);
/* Get semval of one semaphore. */
case GETVAL:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (semnum >= sp->sem_nsems) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
retval = (sp->sem_base + semnum)->semval;
mutex_exit(lock);
return (retval);
/* Get all semvals in set. */
case GETALL:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
base = (caddr_t)arg;
for (i = sp->sem_nsems, p = sp->sem_base; i--; p++) {
if (copyout((caddr_t)&p->semval, base,
sizeof (p->semval))) {
mutex_exit(lock);
return (set_errno(EFAULT));
}
base += sizeof (p->semval);
}
mutex_exit(lock);
return (0);
/* Get # of processes sleeping for semval to become zero. */
case GETZCNT:
if (error = ipcaccess(&sp->sem_perm, SEM_R, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (semnum >= sp->sem_nsems) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
retval = (sp->sem_base + semnum)->semzcnt;
mutex_exit(lock);
return (retval);
/* Set semval of one semaphore. */
case SETVAL:
if (error = ipcaccess(&sp->sem_perm, SEM_A, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
if (semnum >= sp->sem_nsems) {
mutex_exit(lock);
return (set_errno(EINVAL));
}
if ((unsigned)arg > seminfo.semvmx) {
mutex_exit(lock);
return (set_errno(ERANGE));
}
p = sp->sem_base + semnum;
if ((p->semval = (ushort)arg) != 0) {
if (p->semncnt) {
p->semncnt = 0;
cv_broadcast(&p->semncnt_cv);
}
} else if (p->semzcnt) {
p->semzcnt = 0;
cv_broadcast(&p->semzcnt_cv);
}
p->sempid = curproc->p_pid;
semunrm(semid, semnum, semnum);
mutex_exit(lock);
return (0);
/* Set semvals of all semaphores in set. */
case SETALL: {
register unsigned short *vals;
unsigned vsize;
if (error = ipcaccess(&sp->sem_perm, SEM_A, cr)) {
mutex_exit(lock);
return (set_errno(error));
}
/* allocate space to hold all semaphore values */
vsize = sp->sem_nsems * sizeof (short);
vals = kmem_alloc(vsize, KM_SLEEP);
/* XXX - correct call? */
if (copyin((caddr_t)arg, (caddr_t)vals, vsize)) {
error = set_errno(EFAULT);
goto seterr;
}
for (i = 0; i < sp->sem_nsems; /* CSTYLED */)
if (vals[i++] > (unsigned)seminfo.semvmx) {
error = set_errno(ERANGE);
goto seterr;
}
semunrm(semid, 0, sp->sem_nsems);
for (i = 0, p = sp->sem_base; i < sp->sem_nsems;
(p++)->sempid = curproc->p_pid) {
if ((p->semval = vals[i++]) != 0) {
if (p->semncnt) {
p->semncnt = 0;
cv_broadcast(&p->semncnt_cv);
}
} else if (p->semzcnt) {
p->semzcnt = 0;
cv_broadcast(&p->semzcnt_cv);
}
}
seterr:
mutex_exit(lock);
kmem_free(vals, vsize);
return (error);
}
default:
mutex_exit(lock);
return (set_errno(EINVAL));
}
/* NOTREACHED */
}
/*
* semexit - Called by exit() to clean up on process exit.
*/
void
semexit(void)
{
register struct sem_undo *up; /* process undo struct ptr */
register struct sem_undo *p; /* undo struct ptr */
struct semid_ds *sp; /* semid being undone ptr */
register int i; /* loop control */
register long v; /* adjusted value */
register struct sem *semp; /* semaphore ptr */
if (sem_undo == NULL || (up = sem_undo[curproc->p_slot]) == NULL)
return;
if (up->un_cnt == 0) {
mutex_enter(&sem_lock);
up->un_np = semfup;
semfup = up;
sem_undo[curproc->p_slot] = NULL;
mutex_exit(&sem_lock);
return;
}
for (i = up->un_cnt; i--; /* CSTYLED */) {
kmutex_t *lock;
if ((lock = semconv_lock(up->un_ent[i].un_id, &sp)) == NULL)
continue;
v = (long)(semp = sp->sem_base + up->un_ent[i].un_num)->semval +
up->un_ent[i].un_aoe;
if (v < 0 || v > seminfo.semvmx) {
mutex_exit(lock);
continue;
}
semp->semval = (ushort) v;
if (v == 0 && semp->semzcnt) {
semp->semzcnt = 0;
cv_broadcast(&semp->semzcnt_cv);
}
if (up->un_ent[i].un_aoe > 0 && semp->semncnt) {
semp->semncnt = 0;
cv_broadcast(&semp->semncnt_cv);
}
mutex_exit(lock);
}
mutex_enter(&sem_lock);
up->un_cnt = 0;
if (semunp == up) {
semunp = up->un_np;
} else {
for (p = semunp; p != NULL; p = p->un_np) {
if (p->un_np == up) {
p->un_np = up->un_np;
break;
}
}
}
up->un_np = semfup;
semfup = up;
sem_undo[curproc->p_slot] = NULL;
mutex_exit(&sem_lock);
}
/*
* semget - Semget system call.
*/
static int
semget(key_t key, int nsems, int semflg)
{
struct semid_ds *sp; /* semaphore header ptr */
register ulong i; /* temp */
int s; /* ipcget status return */
register int error;
if (error = ipcget(key, semflg, (struct ipc_perm *)sema,
seminfo.semmni, sizeof (*sp), &s, (struct ipc_perm **)&sp))
return (set_errno(error));
if (s) {
/* This is a new semaphore set. Finish initialization. */
if (nsems <= 0 || nsems > seminfo.semmsl) {
sp->sem_perm.mode = 0;
return (set_errno(EINVAL));
}
if ((i = rmalloc(semmap, (long)nsems)) == NULL) {
sp->sem_perm.mode = 0;
return (set_errno(ENOSPC));
}
/* point to first semaphore */
sp->sem_base = sem + (i - 1);
sp->sem_binary = 1;
sp->sem_nsems = (ushort_t)nsems;
sp->sem_ctime = hrestime.tv_sec;
sp->sem_otime = 0;
{
/* init reserve area */
int i;
for (i = 0; i < 4; i++)
sp->sem_perm.pad[i] = 0;
sp->sem_pad1 = 0;
sp->sem_pad2 = 0;
for (i = 0; i < 3; i++)
sp->sem_pad3[i] = 0;
}
} else if (nsems && sp->sem_nsems < (unsigned)nsems)
return (set_errno(EINVAL));
/* sem_perm.seq incremented by semctl() when semaphore group released */
return ((sp->sem_perm.seq << SEMA_SEQ_SHIFT) + (sp - sema));
}
/*
* semop - Semop system call.
*/
static int
semop(int semid, struct sembuf *sops, uint nsops)
{
register struct sembuf *op; /* ptr to operation */
register int i; /* loop control */
struct semid_ds *sp; /* ptr to associated header */
register struct sem *semp; /* ptr to semaphore */
int again, error = 0;
struct sembuf *uops; /* ptr to copy of user ops */
struct sembuf x_sem; /* avoid kmem_alloc's */
kmutex_t *lock;
int binary_type;
CPU_STAT_ADD(CPU, cpu_sysinfo.sema, 1); /* bump semaphore op count */
if (nsops > seminfo.semopm)
return (set_errno(E2BIG));
/* allocate space to hold all semaphore ops */
if (nsops == 1)
uops = &x_sem;
else if (nsops == 0)
return (0);
else
uops = kmem_alloc(nsops * sizeof (*uops), KM_SLEEP);
if (copyin((caddr_t)sops, (caddr_t)uops, nsops * sizeof (*op))) {
error = EFAULT;
goto semoperr2;
}
if ((lock = semconv_lock(semid, &sp)) == NULL) {
error = EINVAL;
goto semoperr2;
}
/* Verify that sem #s are in range and permissions are granted. */
for (i = 0, op = uops; i++ < nsops; op++) {
if (error = ipcaccess(&sp->sem_perm,
op->sem_op ? SEM_A : SEM_R, CRED()))
goto semoperr;
if (op->sem_num >= sp->sem_nsems) {
error = EFBIG;
goto semoperr;
}
}
again = 0;
check:
/*
* Loop waiting for the operations to be satisfied atomically.
* Actually, do the operations and undo them if a wait is needed
* or an error is detected.
*/
if (again) {
/* Verify that the semaphores haven't been removed. */
if (semconv(semid) != 0) {
error = EIDRM;
goto semoperr;
}
}
again = 1;
/* Determine if the semaphore is a simple binary semaphore */
binary_type = sp->sem_binary && (sp->sem_nsems == 1);
for (i = 0, op = uops; i < nsops; i++, op++) {
semp = sp->sem_base + op->sem_num;
if (op->sem_op > 0) { /* i.e. sema_v */
int signal = 0;
if (op->sem_op + (long)semp->semval > seminfo.semvmx ||
((op->sem_flg & SEM_UNDO) &&
(error = semaoe(op->sem_op, semid,
op->sem_num)))) {
if (i)
semundo(uops, i, semid, sp);
if (error == 0)
error = ERANGE; /* XXX - not in ours */
goto semoperr;
}
semp->semval += op->sem_op;
/*
* If we are only incrementing the semaphore value
* by one on a binary semaphore, we can cv_signal.
*/
if (op->sem_op == 1 && binary_type)
signal = 1;
if (semp->semncnt) {
if (signal) {
semp->semncnt -= 1;
cv_signal(&semp->semncnt_cv);
} else {
semp->semncnt = 0;
cv_broadcast(&semp->semncnt_cv);
}
}
if (semp->semzcnt && !semp->semval) {
if (signal) {
semp->semzcnt -= 1;
cv_signal(&semp->semzcnt_cv);
} else {
semp->semzcnt = 0;
cv_broadcast(&semp->semzcnt_cv);
}
}
continue;
}
if (op->sem_op < 0) { /* i.e. sema_p */
if (semp->semval >= (unsigned)(-op->sem_op)) {
if ((op->sem_flg & SEM_UNDO) &&
(error = semaoe(op->sem_op, semid,
op->sem_num))) {
if (i)
semundo(uops, i, semid, sp);
goto semoperr;
}
semp->semval += op->sem_op;
if (semp->semzcnt && !semp->semval) {
semp->semzcnt = 0;
cv_broadcast(&semp->semzcnt_cv);
}
continue;
}
if (i)
semundo(uops, i, semid, sp);
if (op->sem_flg & IPC_NOWAIT) {
error = EAGAIN;
goto semoperr;
}
semp->semncnt++;
/*
* Mark the semaphore set as not a binary type
* if we are decrementing the value by more than 1.
*
* V operations will resort to cv_broadcast
* for this set because there are too many weird
* cases that have to be caught.
*/
if (op->sem_op < -1)
sp->sem_binary = 0;
if (!cv_wait_sig(&semp->semncnt_cv, lock)) {
if ((semp->semncnt)-- <= 1) {
semp->semncnt = 0;
cv_broadcast(&semp->semncnt_cv);
}
error = EINTR;
goto semoperr;
}
goto check;
}
if (semp->semval) {
if (i)
semundo(uops, i, semid, sp);
if (op->sem_flg & IPC_NOWAIT) {
error = EAGAIN;
goto semoperr;
}
semp->semzcnt++;
if (!cv_wait_sig(&semp->semzcnt_cv, lock)) {
if ((semp->semzcnt)-- <= 1) {
semp->semzcnt = 0;
cv_broadcast(&semp->semzcnt_cv);
}
error = EINTR;
goto semoperr;
}
goto check;
}
}
/* All operations succeeded. Update sempid for accessed semaphores. */
for (i = 0, op = uops; i++ < nsops;
(sp->sem_base + (op++)->sem_num)->sempid = curproc->p_pid)
;
sp->sem_otime = hrestime.tv_sec;
mutex_exit(lock);
/* Before leaving, deallocate the buffer that held the user semops */
if (nsops != 1)
kmem_free(uops, sizeof (*uops) * nsops);
return (0);
/*
* Error return labels.
*/
semoperr:
mutex_exit(lock);
semoperr2:
/* Before leaving, deallocate the buffer that held the user semops */
if (nsops != 1)
kmem_free(uops, sizeof (*uops) * nsops);
return (set_errno(error));
}
/*
* semsys - System entry point for semctl, semget, and semop system calls.
*/
static int
semsys(int opcode, int a1, int a2, int a3, int a4)
{
register int error;
switch (opcode) {
case SEMCTL:
error = semctl(a1, (uint)a2, a3, a4);
break;
case SEMGET:
mutex_enter(&sem_lock);
error = semget((key_t)a1, a2, a3);
mutex_exit(&sem_lock);
break;
case SEMOP:
error = semop(a1, (struct sembuf *)a2, (uint)a3);
break;
default:
error = set_errno(EINVAL);
break;
}
return (error);
}