#include "inc.h"
-struct waiting {
- endpoint_t who; /* who is waiting */
- int val; /* value he/she is waiting for */
-};
+struct sem_struct;
+
+/* IPC-server process table, currently used for semaphores only. */
+struct iproc {
+ struct sem_struct *ip_sem; /* affected semaphore set, or NULL */
+ struct sembuf *ip_sops; /* pending operations (malloc'ed) */
+ unsigned int ip_nsops; /* number of pending operations */
+ struct sembuf *ip_blkop; /* pointer to operation that blocked */
+ endpoint_t ip_endpt; /* process endpoint */
+ pid_t ip_pid; /* process PID */
+ TAILQ_ENTRY(iproc) ip_next; /* next waiting process */
+} iproc[NR_PROCS];
struct semaphore {
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
- struct waiting *zlist; /* process waiting for zero */
- struct waiting *nlist; /* process waiting for increase */
pid_t sempid; /* process that did last op */
};
+/*
+ * For the list of waiting processes, we use a doubly linked tail queue. In
+ * order to maintain a basic degree of fairness, we keep the pending processes
+ * in FCFS (well, at least first tested) order, which means we need to be able
+ * to add new processes at the end of the list. In order to remove waiting
+ * processes O(1) instead of O(n) we need a doubly linked list; in the common
+ * case we do have the element's predecessor, but STAILQ_REMOVE is O(n) anyway
+ * and NetBSD has no STAILQ_REMOVE_AFTER yet.
+ *
+ * We use one list per semaphore set: semop(2) affects only one semaphore set,
+ * but it may involve operations on multiple semaphores within the set. While
+ * it is possible to recheck only semaphores that were affected by a particular
+ * operation, and associate waiting lists to individual semaphores, the number
+ * of expected waiting processes is currently not high enough to justify the
+ * extra complexity of such an implementation.
+ */
struct sem_struct {
struct semid_ds semid_ds;
struct semaphore sems[SEMMSL];
+ TAILQ_HEAD(waiters, iproc) waiters;
};
static struct sem_struct sem_list[SEMMNI];
static unsigned int sem_list_nr = 0; /* highest in-use slot number plus one */
+/*
+ * Find a semaphore set by key. The given key must not be IPC_PRIVATE. Return
+ * a pointer to the semaphore set if found, or NULL otherwise.
+ */
static struct sem_struct *
sem_find_key(key_t key)
{
unsigned int i;
- if (key == IPC_PRIVATE)
- return NULL;
-
for (i = 0; i < sem_list_nr; i++) {
if (!(sem_list[i].semid_ds.sem_perm.mode & SEM_ALLOC))
continue;
return NULL;
}
+/*
+ * Find a semaphore set by identifier. Return a pointer to the semaphore set
+ * if found, or NULL otherwise.
+ */
static struct sem_struct *
sem_find_id(int id)
{
return sem;
}
+/*
+ * Implementation of the semget(2) system call.
+ */
int
do_semget(message * m)
{
nsems = m->m_lc_ipc_semget.nr;
flag = m->m_lc_ipc_semget.flag;
- if ((sem = sem_find_key(key)) != NULL) {
+ if (key != IPC_PRIVATE && (sem = sem_find_key(key)) != NULL) {
if ((flag & IPC_CREAT) && (flag & IPC_EXCL))
return EEXIST;
if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, flag))
return EINVAL;
i = sem - sem_list;
} else {
- if (!(flag & IPC_CREAT))
+ if (key != IPC_PRIVATE && !(flag & IPC_CREAT))
return ENOENT;
- if (nsems < 0 || nsems >= SEMMSL)
+ if (nsems <= 0 || nsems > SEMMSL)
return EINVAL;
/* Find a free entry. */
sem->semid_ds.sem_nsems = nsems;
sem->semid_ds.sem_otime = 0;
sem->semid_ds.sem_ctime = clock_time(NULL);
+ TAILQ_INIT(&sem->waiters);
assert(i <= sem_list_nr);
if (i == sem_list_nr)
return OK;
}
+/*
+ * Increase the proper suspension count (semncnt or semzcnt) of the semaphore
+ * on which the given process is blocked.
+ */
+static void
+inc_susp_count(struct iproc * ip)
+{
+ struct sembuf *blkop;
+ struct semaphore *sp;
+
+ blkop = ip->ip_blkop;
+ sp = &ip->ip_sem->sems[blkop->sem_num];
+
+ if (blkop->sem_op != 0) {
+ assert(sp->semncnt < USHRT_MAX);
+ sp->semncnt++;
+ } else {
+ assert(sp->semncnt < USHRT_MAX);
+ sp->semzcnt++;
+ }
+}
+
+/*
+ * Decrease the proper suspension count (semncnt or semzcnt) of the semaphore
+ * on which the given process is blocked.
+ */
+static void
+dec_susp_count(struct iproc * ip)
+{
+ struct sembuf *blkop;
+ struct semaphore *sp;
+
+ blkop = ip->ip_blkop;
+ sp = &ip->ip_sem->sems[blkop->sem_num];
+
+ if (blkop->sem_op != 0) {
+ assert(sp->semncnt > 0);
+ sp->semncnt--;
+ } else {
+ assert(sp->semzcnt > 0);
+ sp->semzcnt--;
+ }
+}
+
+/*
+ * Send a reply for a semop(2) call suspended earlier, thus waking up the
+ * process.
+ */
static void
send_reply(endpoint_t who, int ret)
{
ipc_sendnb(who, &m);
}
+/*
+ * Satisfy or cancel the semop(2) call on which the given process is blocked,
+ * and send the given reply code (OK or a negative error code) to wake it up,
+ * unless the given code is EDONTREPLY.
+ */
static void
-remove_semaphore(struct sem_struct * sem)
+complete_semop(struct iproc * ip, int code)
{
- int i, j, nr;
- struct semaphore *semaphore;
+ struct sem_struct *sem;
- nr = sem->semid_ds.sem_nsems;
+ sem = ip->ip_sem;
- /* Deal with processes waiting for this semaphore set. */
- for (i = 0; i < nr; i++) {
- semaphore = &sem->sems[i];
+ assert(sem != NULL);
- for (j = 0; j < semaphore->semzcnt; j++)
- send_reply(semaphore->zlist[j].who, EIDRM);
- for (j = 0; j < semaphore->semncnt; j++)
- send_reply(semaphore->nlist[j].who, EIDRM);
+ TAILQ_REMOVE(&sem->waiters, ip, ip_next);
- if (semaphore->zlist != NULL) {
- free(semaphore->zlist);
- semaphore->zlist = NULL;
- }
- if (semaphore->nlist != NULL) {
- free(semaphore->nlist);
- semaphore->nlist = NULL;
- }
+ dec_susp_count(ip);
+
+ assert(ip->ip_sops != NULL);
+ free(ip->ip_sops);
+
+ ip->ip_sops = NULL;
+ ip->ip_blkop = NULL;
+ ip->ip_sem = NULL;
+
+ if (code != EDONTREPLY)
+ send_reply(ip->ip_endpt, code);
+}
+
+/*
+ * Free up the given semaphore set. This includes cancelling any blocking
+ * semop(2) calls on any of its semaphores.
+ */
+static void
+remove_set(struct sem_struct * sem)
+{
+ struct iproc *ip;
+
+ /*
+ * Cancel all semop(2) operations on this semaphore set, with an EIDRM
+ * reply code.
+ */
+ while (!TAILQ_EMPTY(&sem->waiters)) {
+ ip = TAILQ_FIRST(&sem->waiters);
+
+ complete_semop(ip, EIDRM);
}
/* Mark the entry as free. */
sem_list_nr--;
}
-#if 0
-static void
-show_semaphore(void)
+/*
+ * Try to perform a set of semaphore operations, as given by semop(2), on a
+ * semaphore set. The entire action must be atomic, i.e., either succeed in
+ * its entirety or fail without making any changes. Return OK on success, in
+ * which case the PIDs of all affected semaphores will be updated to the given
+ * 'pid' value, and the semaphore set's sem_otime will be updated as well.
+ * Return SUSPEND if the call should be suspended, in which case 'blkop' will
+ * be set to a pointer to the operation causing the call to block. Return an
+ * error code if the call failed altogether.
+ */
+static int
+try_semop(struct sem_struct *sem, struct sembuf *sops, unsigned int nsops,
+ pid_t pid, struct sembuf ** blkop)
{
+ struct semaphore *sp;
+ struct sembuf *op;
unsigned int i;
- int j, k, nr;
-
- for (i = 0; i < sem_list_nr; i++) {
- if (!(sem_list[i].semid_ds.sem_perm.mode & SEM_ALLOC))
- continue;
-
- nr = sem_list[i].semid_ds.sem_nsems;
-
- printf("===== [%d] =====\n", i);
- for (j = 0; j < nr; j++) {
- struct semaphore *semaphore = &sem_list[i].sems[j];
+ int r;
- if (!semaphore->semzcnt && !semaphore->semncnt)
- continue;
+ /*
+ * The operation must be processed atomically. However, it must also
+ * be processed "in array order," which we assume to mean that while
+ * processing one operation, the changes of the previous operations
+ * must be taken into account. This is relevant for cases where the
+ * same semaphore is referenced by more than one operation, for example
+ * to perform an atomic increase-if-zero action on a single semaphore.
+ * As a result, we must optimistically modify semaphore values and roll
+ * back on suspension or failure afterwards.
+ */
+ r = OK;
+ op = NULL;
+ for (i = 0; i < nsops; i++) {
+ sp = &sem->sems[sops[i].sem_num];
+ op = &sops[i];
- printf(" (%d): ", semaphore->semval);
- if (semaphore->semzcnt) {
- printf("zero(");
- for (k = 0; k < semaphore->semzcnt; k++)
- printf("%d,", semaphore->zlist[k].who);
- printf(") ");
+ if (op->sem_op > 0) {
+ if (SEMVMX - sp->semval < op->sem_op) {
+ r = ERANGE;
+ break;
+ }
+ sp->semval += op->sem_op;
+ } else if (op->sem_op < 0) {
+ /*
+ * No SEMVMX check; if the process wants to deadlock
+ * itself by supplying -SEMVMX it is free to do so..
+ */
+ if ((int)sp->semval < -(int)op->sem_op) {
+ r = (op->sem_flg & IPC_NOWAIT) ? EAGAIN :
+ SUSPEND;
+ break;
}
- if (semaphore->semncnt) {
- printf("incr(");
- for (k = 0; k < semaphore->semncnt; k++)
- printf("%d-%d,",
- semaphore->nlist[k].who,
- semaphore->nlist[k].val);
- printf(")");
+ sp->semval += op->sem_op;
+ } else /* (op->sem_op == 0) */ {
+ if (sp->semval != 0) {
+ r = (op->sem_flg & IPC_NOWAIT) ? EAGAIN :
+ SUSPEND;
+ break;
}
- printf("\n");
}
}
- printf("\n");
-}
-#endif
-static void
-remove_process(endpoint_t endpt)
-{
- struct sem_struct *sem;
- struct semaphore *semaphore;
- endpoint_t who_waiting;
- unsigned int i;
- int j, k, nr;
-
- for (i = 0; i < sem_list_nr; i++) {
- sem = &sem_list[i];
- if (!(sem->semid_ds.sem_perm.mode & SEM_ALLOC))
- continue;
-
- nr = sem->semid_ds.sem_nsems;
- for (j = 0; j < nr; j++) {
- semaphore = &sem->sems[j];
-
- for (k = 0; k < semaphore->semzcnt; k++) {
- who_waiting = semaphore->zlist[k].who;
-
- if (who_waiting == endpt) {
- /* Remove this slot first. */
- memmove(semaphore->zlist + k,
- semaphore->zlist + k + 1,
- sizeof(struct waiting) *
- (semaphore->semzcnt - k - 1));
- semaphore->semzcnt--;
-
- /* Then send message to the process. */
- send_reply(who_waiting, EINTR);
+ /*
+ * If we did not go through all the operations, then either an error
+ * occurred or the user process is to be suspended. In that case we
+ * must roll back any progress we have made so far, and return the
+ * operation that caused the call to block.
+ */
+ if (i < nsops) {
+ assert(op != NULL);
+ *blkop = op;
- break;
- }
- }
+ /* Roll back all changes made so far. */
+ while (i-- > 0)
+ sem->sems[sops[i].sem_num].semval -= sops[i].sem_op;
- for (k = 0; k < semaphore->semncnt; k++) {
- who_waiting = semaphore->nlist[k].who;
+ assert(r != OK);
+ return r;
+ }
- if (who_waiting == endpt) {
- /* Remove it first. */
- memmove(semaphore->nlist + k,
- semaphore->nlist + k + 1,
- sizeof(struct waiting) *
- (semaphore->semncnt-k-1));
- semaphore->semncnt--;
+ /*
+ * The operation has completed successfully. Also update all affected
+ * semaphores' PID values, and the semaphore set's last-semop time.
+ * The caller must do everything else.
+ */
+ for (i = 0; i < nsops; i++)
+ sem->sems[sops[i].sem_num].sempid = pid;
- /* Send the message to the process. */
- send_reply(who_waiting, EINTR);
+ sem->semid_ds.sem_otime = clock_time(NULL);
- break;
- }
- }
- }
- }
+ return OK;
}
+/*
+ * Check whether any blocked operations can now be satisfied on any of the
+ * semaphores in the given semaphore set. Do this repeatedly as necessary, as
+ * any unblocked operation may in turn allow other operations to be resumed.
+ */
static void
-check_semaphore(struct sem_struct * sem)
+check_set(struct sem_struct * sem)
{
- int i, j, nr;
- struct semaphore *semaphore;
- endpoint_t who;
-
- nr = sem->semid_ds.sem_nsems;
-
- for (i = 0; i < nr; i++) {
- semaphore = &sem->sems[i];
-
- if (semaphore->zlist && !semaphore->semval) {
- /* Choose one process, policy: FIFO. */
- who = semaphore->zlist[0].who;
-
- memmove(semaphore->zlist, semaphore->zlist + 1,
- sizeof(struct waiting) * (semaphore->semzcnt - 1));
- semaphore->semzcnt--;
+ struct iproc *ip, *nextip;
+ struct sembuf *blkop;
+ int r, woken_up;
- send_reply(who, OK);
- }
-
- if (semaphore->nlist) {
- for (j = 0; j < semaphore->semncnt; j++) {
- if (semaphore->nlist[j].val <=
- semaphore->semval) {
- semaphore->semval -=
- semaphore->nlist[j].val;
- who = semaphore->nlist[j].who;
-
- memmove(semaphore->nlist + j,
- semaphore->nlist + j + 1,
- sizeof(struct waiting) *
- (semaphore->semncnt-j-1));
- semaphore->semncnt--;
-
- send_reply(who, OK);
- break;
- }
+ /*
+ * Go through all the waiting processes in FIFO order, which is our
+ * best attempt at providing at least some fairness. Keep trying as
+ * long as we woke up at least one process, which means we made actual
+ * progress.
+ */
+ do {
+ woken_up = FALSE;
+
+ TAILQ_FOREACH_SAFE(ip, &sem->waiters, ip_next, nextip) {
+ /* Retry the entire semop(2) operation, atomically. */
+ r = try_semop(ip->ip_sem, ip->ip_sops, ip->ip_nsops,
+ ip->ip_pid, &blkop);
+
+ if (r != SUSPEND) {
+ /* Success or failure. */
+ complete_semop(ip, r);
+
+ /* No changes are made on failure. */
+ if (r == OK)
+ woken_up = TRUE;
+ } else if (blkop != ip->ip_blkop) {
+ /*
+ * The process stays suspended, but it is now
+ * blocked on a different semaphore. As a
+ * result, we need to adjust the semaphores'
+ * suspension counts.
+ */
+ dec_susp_count(ip);
+
+ ip->ip_blkop = blkop;
+
+ inc_susp_count(ip);
}
}
- }
+ } while (woken_up);
}
+/*
+ * Implementation of the semctl(2) system call.
+ */
int
do_semctl(message * m)
{
+ static unsigned short valbuf[SEMMSL];
unsigned int i;
vir_bytes opt;
uid_t uid;
int r, id, num, cmd, val;
- unsigned short *buf;
struct semid_ds tmp_ds;
struct sem_struct *sem;
struct seminfo sinfo;
cmd = m->m_lc_ipc_semctl.cmd;
opt = m->m_lc_ipc_semctl.opt;
+ /*
+ * Look up the target semaphore set. The IPC_INFO and SEM_INFO
+ * commands have no associated semaphore set. The SEM_STAT command
+ * takes an array index into the semaphore set table. For all other
+ * commands, look up the semaphore set by its given identifier.
+ * */
switch (cmd) {
case IPC_INFO:
case SEM_INFO:
}
/*
- * IPC_SET and IPC_RMID have their own permission checks. IPC_INFO and
- * SEM_INFO are free for general use.
+ * Check if the caller has the appropriate permissions on the target
+ * semaphore set. SETVAL and SETALL require write permission. IPC_SET
+ * and IPC_RMID require ownership permission, and return EPERM instead
+ * of EACCES on failure. IPC_INFO and SEM_INFO are free for general
+ * use. All other calls require read permission.
*/
- if (sem != NULL && cmd != IPC_SET && cmd != IPC_RMID) {
- /* Check read permission. */
- if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, 0444))
+ switch (cmd) {
+ case SETVAL:
+ case SETALL:
+ assert(sem != NULL);
+ if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, IPC_W))
+ return EACCES;
+ break;
+ case IPC_SET:
+ case IPC_RMID:
+ assert(sem != NULL);
+ uid = getnuid(m->m_source);
+ if (uid != sem->semid_ds.sem_perm.cuid &&
+ uid != sem->semid_ds.sem_perm.uid && uid != 0)
+ return EPERM;
+ break;
+ case IPC_INFO:
+ case SEM_INFO:
+ break;
+ default:
+ assert(sem != NULL);
+ if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, IPC_R))
return EACCES;
}
IXSEQ_TO_IPCID(id, sem->semid_ds.sem_perm);
break;
case IPC_SET:
- uid = getnuid(m->m_source);
- if (uid != sem->semid_ds.sem_perm.cuid &&
- uid != sem->semid_ds.sem_perm.uid && uid != 0)
- return EPERM;
if ((r = sys_datacopy(m->m_source, opt, SELF,
(vir_bytes)&tmp_ds, sizeof(tmp_ds))) != OK)
return r;
sem->semid_ds.sem_ctime = clock_time(NULL);
break;
case IPC_RMID:
- uid = getnuid(m->m_source);
- if (uid != sem->semid_ds.sem_perm.cuid &&
- uid != sem->semid_ds.sem_perm.uid && uid != 0)
- return EPERM;
/*
* Awaken all processes blocked in semop(2) on any semaphore in
* this set, and remove the semaphore set itself.
*/
- remove_semaphore(sem);
+ remove_set(sem);
break;
case IPC_INFO:
case SEM_INFO:
m->m_lc_ipc_semctl.ret = 0;
break;
case GETALL:
- buf = malloc(sizeof(unsigned short) * sem->semid_ds.sem_nsems);
- if (buf == NULL)
- return ENOMEM;
+ assert(sem->semid_ds.sem_nsems <= __arraycount(valbuf));
for (i = 0; i < sem->semid_ds.sem_nsems; i++)
- buf[i] = sem->sems[i].semval;
- r = sys_datacopy(SELF, (vir_bytes)buf, m->m_source,
+ valbuf[i] = sem->sems[i].semval;
+ r = sys_datacopy(SELF, (vir_bytes)valbuf, m->m_source,
opt, sizeof(unsigned short) * sem->semid_ds.sem_nsems);
- free(buf);
if (r != OK)
- return EINVAL;
+ return r;
break;
case GETNCNT:
if (num < 0 || num >= sem->semid_ds.sem_nsems)
m->m_lc_ipc_semctl.ret = sem->sems[num].semzcnt;
break;
case SETALL:
- buf = malloc(sizeof(unsigned short) * sem->semid_ds.sem_nsems);
- if (buf == NULL)
- return ENOMEM;
- r = sys_datacopy(m->m_source, opt, SELF, (vir_bytes)buf,
+ assert(sem->semid_ds.sem_nsems <= __arraycount(valbuf));
+ r = sys_datacopy(m->m_source, opt, SELF, (vir_bytes)valbuf,
sizeof(unsigned short) * sem->semid_ds.sem_nsems);
- if (r != OK) {
- free(buf);
- return EINVAL;
- }
- for (i = 0; i < sem->semid_ds.sem_nsems; i++) {
- if (buf[i] > SEMVMX) {
- free(buf);
+ if (r != OK)
+ return r;
+ for (i = 0; i < sem->semid_ds.sem_nsems; i++)
+ if (valbuf[i] > SEMVMX)
return ERANGE;
- }
- }
#ifdef DEBUG_SEM
for (i = 0; i < sem->semid_ds.sem_nsems; i++)
- printf("SEMCTL: SETALL val: [%d] %d\n", i, buf[i]);
+ printf("SEMCTL: SETALL val: [%d] %d\n", i, valbuf[i]);
#endif
for (i = 0; i < sem->semid_ds.sem_nsems; i++)
- sem->sems[i].semval = buf[i];
- free(buf);
+ sem->sems[i].semval = valbuf[i];
+ sem->semid_ds.sem_ctime = clock_time(NULL);
/* Awaken any waiting parties if now possible. */
- check_semaphore(sem);
+ check_set(sem);
break;
case SETVAL:
val = (int)opt;
- /* Check write permission. */
- if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, 0222))
- return EACCES;
if (num < 0 || num >= sem->semid_ds.sem_nsems)
return EINVAL;
if (val < 0 || val > SEMVMX)
#endif
sem->semid_ds.sem_ctime = clock_time(NULL);
/* Awaken any waiting parties if now possible. */
- check_semaphore(sem);
+ check_set(sem);
break;
default:
return EINVAL;
return OK;
}
+/*
+ * Implementation of the semop(2) system call.
+ */
int
do_semop(message * m)
{
- unsigned int i, mask;
+ unsigned int i, mask, slot;
int id, r;
- struct sembuf *sops;
+ struct sembuf *sops, *blkop;
unsigned int nsops;
struct sem_struct *sem;
- struct semaphore *s;
- int op_n, val, no_reply;
+ struct iproc *ip;
+ pid_t pid;
id = m->m_lc_ipc_semop.id;
nsops = m->m_lc_ipc_semop.size;
if ((sem = sem_find_id(id)) == NULL)
return EINVAL;
- if (nsops <= 0)
- return EINVAL;
+ if (nsops == 0)
+ return OK; /* nothing to do */
if (nsops > SEMOPM)
return E2BIG;
/* Get the array from the user process. */
sops = malloc(sizeof(sops[0]) * nsops);
- if (!sops)
+ if (sops == NULL)
return ENOMEM;
r = sys_datacopy(m->m_source, (vir_bytes)m->m_lc_ipc_semop.ops, SELF,
(vir_bytes)sops, sizeof(sops[0]) * nsops);
printf("SEMOP: num:%d op:%d flg:%d\n",
sops[i].sem_num, sops[i].sem_op, sops[i].sem_flg);
#endif
- /* Check that all given semaphore numbers are within range. */
- r = EFBIG;
- for (i = 0; i < nsops; i++)
- if (sops[i].sem_num >= sem->semid_ds.sem_nsems)
- goto out_free;
-
- /* Check for permissions. */
- r = EACCES;
+ /*
+ * Check for permissions. We do this only once, even though the call
+ * might suspend and the semaphore set's permissions might be changed
+ * before the call resumes. The specification is not clear on this.
+ * Either way, perform the permission check before checking on the
+ * validity of semaphore numbers, since obtaining the semaphore set
+ * size itself requires read permission (except through sysctl(2)..).
+ */
mask = 0;
for (i = 0; i < nsops; i++) {
if (sops[i].sem_op != 0)
- mask |= 0222; /* check for write permission */
+ mask |= IPC_W; /* check for write permission */
else
- mask |= 0444; /* check for read permission */
+ mask |= IPC_R; /* check for read permission */
}
- if (mask && !check_perm(&sem->semid_ds.sem_perm, m->m_source, mask))
+ r = EACCES;
+ if (!check_perm(&sem->semid_ds.sem_perm, m->m_source, mask))
goto out_free;
- /* Check for nonblocking operations. */
- r = EAGAIN;
- for (i = 0; i < nsops; i++) {
- op_n = sops[i].sem_op;
- val = sem->sems[sops[i].sem_num].semval;
-
- if ((sops[i].sem_flg & IPC_NOWAIT) &&
- ((op_n == 0 && val != 0) || (op_n < 0 && -op_n > val)))
+ /* Check that all given semaphore numbers are within range. */
+ r = EFBIG;
+ for (i = 0; i < nsops; i++)
+ if (sops[i].sem_num >= sem->semid_ds.sem_nsems)
goto out_free;
- }
- /* There will be no errors left, so we can go ahead. */
- no_reply = 0;
+ /*
+ * Do not check if the same semaphore is referenced more than once
+ * (there was such a check here originally), because that is actually
+ * a valid case. The result is however that it is possible to
+ * construct a semop(2) request that will never complete, and thus,
+ * care must be taken that such requests do not create potential
+ * deadlock situations etc.
+ */
+
+ pid = getnpid(m->m_source);
+
+ /*
+ * We do not yet support SEM_UNDO at all, so we better not give the
+ * caller the impression that we do. For now, print a warning so that
+ * we know when an application actually fails for that reason.
+ */
for (i = 0; i < nsops; i++) {
- s = &sem->sems[sops[i].sem_num];
- op_n = sops[i].sem_op;
-
- s->sempid = getnpid(m->m_source);
-
- if (op_n > 0) {
- /* XXX missing ERANGE check */
- s->semval += sops[i].sem_op;
- } else if (op_n == 0) {
- if (s->semval) {
- /* Put the process to sleep. */
- s->semzcnt++;
- s->zlist = realloc(s->zlist,
- sizeof(struct waiting) * s->semzcnt);
- /* continuing if NULL would lead to disaster */
- if (s->zlist == NULL)
- panic("out of memory");
- s->zlist[s->semzcnt - 1].who = m->m_source;
- s->zlist[s->semzcnt - 1].val = op_n;
-
- no_reply++;
- }
- } else /* (op_n < 0) */ {
- if (s->semval >= -op_n)
- s->semval += op_n;
- else {
- /* Put the process to sleep. */
- s->semncnt++;
- s->nlist = realloc(s->nlist,
- sizeof(struct waiting) * s->semncnt);
- /* continuing if NULL would lead to disaster */
- if (s->nlist == NULL)
- panic("out of memory");
- s->nlist[s->semncnt - 1].who = m->m_source;
- s->nlist[s->semncnt - 1].val = -op_n;
-
- no_reply++;
- }
+ if (sops[i].sem_flg & SEM_UNDO) {
+ /* Print a warning only if this isn't the test set.. */
+ if (sops[i].sem_flg != SHRT_MAX)
+ printf("IPC: pid %d tried to use SEM_UNDO\n",
+ pid);
+ r = EINVAL;
+ goto out_free;
}
}
- r = no_reply ? SUSPEND : OK;
+ /* Try to perform the operation now. */
+ r = try_semop(sem, sops, nsops, pid, &blkop);
- /* Awaken any other waiting parties if now possible. */
- check_semaphore(sem);
+ if (r == SUSPEND) {
+ /*
+ * The operation ended up blocking on a particular semaphore
+ * operation. Save all details in the slot for the user
+ * process, and add it to the list of processes waiting for
+ * this semaphore set.
+ */
+ slot = _ENDPOINT_P(m->m_source);
+ assert(slot < __arraycount(iproc));
+
+ ip = &iproc[slot];
+ assert(ip->ip_sem == NULL); /* can't already be in use */
+
+ ip->ip_endpt = m->m_source;
+ ip->ip_pid = pid;
+ ip->ip_sem = sem;
+ ip->ip_sops = sops;
+ ip->ip_nsops = nsops;
+ ip->ip_blkop = blkop;
+
+ TAILQ_INSERT_TAIL(&sem->waiters, ip, ip_next);
+
+ inc_susp_count(ip);
+
+ return r;
+ }
out_free:
free(sops);
+ /* Awaken any other waiting parties if now possible. */
+ if (r == OK)
+ check_set(sem);
+
return r;
}
+/*
+ * Return TRUE iff no semaphore sets are allocated.
+ */
int
is_sem_nil(void)
{
}
/*
- * Check whether processes have terminated and/or are about to have a signal
- * caught, in which case any pending blocking operation must be cancelled.
+ * Check if the given endpoint is blocked on a semop(2) call. If so, cancel
+ * the call, because either it is interrupted by a signal or the process was
+ * killed. In the former case, unblock the process by replying with EINTR.
*/
void
-sem_process_event(endpoint_t endpt, int has_exited __unused)
+sem_process_event(endpoint_t endpt, int has_exited)
{
+ unsigned int slot;
+ struct iproc *ip;
+
+ slot = _ENDPOINT_P(endpt);
+ assert(slot < __arraycount(iproc));
+
+ ip = &iproc[slot];
+
+ /* Was the process blocked on a semop(2) call at all? */
+ if (ip->ip_sem == NULL)
+ return;
+
+ assert(ip->ip_endpt == endpt);
/*
- * As long as we do not support SEM_UNDO, it does not matter whether
- * the process has exited or has a signal delivered: in both cases, we
- * need to cancel any blocking semop(2) call.
+ * It was; cancel the semop(2) call. If the process is being removed
+ * because its call was interrupted by a signal, then we must wake it
+ * up with EINTR.
*/
- remove_process(endpt);
+ complete_semop(ip, has_exited ? EDONTREPLY : EINTR);
}
--- /dev/null
+/* Tests for System V IPC semaphores - by D.C. van Moolenbroek */
+/* This test must be run as root, as it includes permission checking tests. */
+#include <stdlib.h>
+#include <limits.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <signal.h>
+
+#include "common.h"
+
+#define ITERATIONS 3
+
+#define WAIT_USECS 100000 /* time for processes to get ready */
+
+#define KEY_A 0x73570001
+#define KEY_B (KEY_A + 1)
+#define KEY_C (KEY_A + 2)
+
+#define ROOT_USER "root" /* name of root */
+#define ROOT_GROUP "operator" /* name of root's group */
+#define NONROOT_USER "bin" /* name of any unprivileged user */
+#define NONROOT_GROUP "bin" /* name of any unprivileged group */
+
+enum {
+ DROP_NONE,
+ DROP_USER,
+ DROP_ALL,
+};
+
+enum {
+ SUGID_NONE,
+ SUGID_ROOT_USER,
+ SUGID_NONROOT_USER,
+ SUGID_ROOT_GROUP,
+ SUGID_NONROOT_GROUP,
+};
+
+struct link {
+ pid_t pid;
+ int sndfd;
+ int rcvfd;
+};
+
+/*
+ * Test semaphore properties. This is a macro, so that it prints useful line
+ * information if an error occurs.
+ */
+#define TEST_SEM(id, num, val, pid, ncnt, zcnt) do { \
+ if (semctl(id, num, GETVAL) != val) e(0); \
+ if (pid != -1 && semctl(id, num, GETPID) != pid) e(1); \
+ if (ncnt != -1 && semctl(id, num, GETNCNT) != ncnt) e(2); \
+ if (zcnt != -1 && semctl(id, num, GETZCNT) != zcnt) e(3); \
+} while (0);
+
+static int nr_signals = 0;
+
+static size_t page_size;
+static char *page_ptr;
+static void *bad_ptr;
+
+/*
+ * Spawn a child process, with a pair of pipes to talk to it bidirectionally.
+ * Drop user and group privileges in the child process if requested.
+ */
+static void
+spawn(struct link * link, void (* proc)(), int drop)
+{
+ struct passwd *pw;
+ struct group *gr;
+ int up[2], dn[2];
+
+ fflush(stdout);
+ fflush(stderr);
+
+ if (pipe(up) != 0) e(0);
+ if (pipe(dn) != 0) e(0);
+
+ link->pid = fork();
+
+ switch (link->pid) {
+ case 0:
+ close(up[1]);
+ close(dn[0]);
+
+ link->pid = getppid();
+ link->rcvfd = up[0];
+ link->sndfd = dn[1];
+
+ errct = 0;
+
+ switch (drop) {
+ case DROP_ALL:
+ if (setgroups(0, NULL) != 0) e(0);
+
+ if ((gr = getgrnam(NONROOT_GROUP)) == NULL) e(0);
+
+ if (setgid(gr->gr_gid) != 0) e(0);
+ if (setegid(gr->gr_gid) != 0) e(0);
+
+ /* FALLTHROUGH */
+ case DROP_USER:
+ if ((pw = getpwnam(NONROOT_USER)) == NULL) e(0);
+
+ /* FIXME: this may rely on a MINIXism. */
+ if (setuid(pw->pw_uid) != 0) e(0);
+ if (seteuid(pw->pw_uid) != 0) e(0);
+ }
+
+ proc(link);
+
+ /* Close our pipe FDs on exit, so that we can make zombies. */
+ exit(errct);
+ case -1:
+ e(0);
+ break;
+ }
+
+ close(up[0]);
+ close(dn[1]);
+
+ link->sndfd = up[1];
+ link->rcvfd = dn[0];
+}
+
+/*
+ * Wait for a child process to terminate, and clean up.
+ */
+static void
+collect(struct link * link)
+{
+ int status;
+
+ close(link->sndfd);
+ close(link->rcvfd);
+
+ if (waitpid(link->pid, &status, 0) != link->pid) e(0);
+
+ if (!WIFEXITED(status)) e(0);
+ else errct += WEXITSTATUS(status);
+}
+
+/*
+ * Forcibly terminate a child process, and clean up.
+ */
+static void
+terminate(struct link * link)
+{
+ int status;
+
+ if (kill(link->pid, SIGKILL) != 0) e(0);
+
+ close(link->sndfd);
+ close(link->rcvfd);
+
+ if (waitpid(link->pid, &status, 0) <= 0) e(0);
+
+ if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) != SIGKILL) e(0);
+ } else {
+ if (!WIFEXITED(status)) e(0);
+ else errct += WEXITSTATUS(status);
+ }
+}
+
+/*
+ * Send an integer value to the child or parent.
+ */
+static void
+snd(struct link * link, int val)
+{
+
+ if (write(link->sndfd, (void *)&val, sizeof(val)) != sizeof(val)) e(0);
+}
+
+/*
+ * Receive an integer value from the child or parent, or -1 on EOF.
+ */
+static int
+rcv(struct link * link)
+{
+ int r, val;
+
+ if ((r = read(link->rcvfd, (void *)&val, sizeof(val))) == 0)
+ return -1;
+
+ if (r != sizeof(val)) e(0);
+
+ return val;
+}
+
+/*
+ * Child procedure that creates semaphore sets.
+ */
+static void
+test_perm_child(struct link * parent)
+{
+ struct passwd *pw;
+ struct group *gr;
+ struct semid_ds semds;
+ uid_t uid;
+ gid_t gid;
+ int mask, rmask, sugid, id[3];
+
+ /*
+ * Repeatedly create a number of semaphores with the masks provided by
+ * the parent process.
+ */
+ while ((mask = rcv(parent)) != -1) {
+ rmask = rcv(parent);
+ sugid = rcv(parent);
+
+ /*
+ * Create the semaphores. For KEY_A, if we are going to set
+ * the mode through IPC_SET anyway, start with a zero mask to
+ * check that the replaced mode is used (thus testing IPC_SET).
+ */
+ if ((id[0] = semget(KEY_A, 3,
+ IPC_CREAT | IPC_EXCL |
+ ((sugid == SUGID_NONE) ? mask : 0))) == -1) e(0);
+ if ((id[1] = semget(KEY_B, 3,
+ IPC_CREAT | IPC_EXCL | mask | rmask)) == -1) e(0);
+ if ((id[2] = semget(KEY_C, 3,
+ IPC_CREAT | IPC_EXCL | rmask)) == -1) e(0);
+
+ uid = geteuid();
+ gid = getegid();
+ if (sugid != SUGID_NONE) {
+ switch (sugid) {
+ case SUGID_ROOT_USER:
+ if ((pw = getpwnam(ROOT_USER)) == NULL) e(0);
+ uid = pw->pw_uid;
+ break;
+ case SUGID_NONROOT_USER:
+ if ((pw = getpwnam(NONROOT_USER)) == NULL)
+ e(0);
+ uid = pw->pw_uid;
+ break;
+ case SUGID_ROOT_GROUP:
+ if ((gr = getgrnam(ROOT_GROUP)) == NULL) e(0);
+ gid = gr->gr_gid;
+ break;
+ case SUGID_NONROOT_GROUP:
+ if ((gr = getgrnam(NONROOT_GROUP)) == NULL)
+ e(0);
+ gid = gr->gr_gid;
+ break;
+ }
+
+ semds.sem_perm.uid = uid;
+ semds.sem_perm.gid = gid;
+ semds.sem_perm.mode = mask;
+ if (semctl(id[0], 0, IPC_SET, &semds) != 0) e(0);
+ semds.sem_perm.mode = mask | rmask;
+ if (semctl(id[1], 0, IPC_SET, &semds) != 0) e(0);
+ semds.sem_perm.mode = rmask;
+ if (semctl(id[2], 0, IPC_SET, &semds) != 0) e(0);
+ }
+
+ /* Do a quick test to confirm the right privileges. */
+ if (mask & IPC_R) {
+ if (semctl(id[0], 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | mask)) e(0);
+ if (semds.sem_perm.uid != uid) e(0);
+ if (semds.sem_perm.gid != gid) e(0);
+ if (semds.sem_perm.cuid != geteuid()) e(0);
+ if (semds.sem_perm.cgid != getegid()) e(0);
+ }
+
+ snd(parent, id[0]);
+ snd(parent, id[1]);
+ snd(parent, id[2]);
+
+ /* The other child process runs here. */
+
+ if (rcv(parent) != 0) e(0);
+
+ /*
+ * For owner tests, the other child may already have removed
+ * the semaphore sets, so ignore return values here.
+ */
+ (void)semctl(id[0], 0, IPC_RMID);
+ (void)semctl(id[1], 0, IPC_RMID);
+ (void)semctl(id[2], 0, IPC_RMID);
+ }
+}
+
+/*
+ * Perform a permission test. The given procedure will be called for various
+ * access masks, which it can use to determine whether operations on three
+ * created semaphore sets should succeed or fail. The first two semaphore sets
+ * are created with appropriate privileges, the third one is not. If the
+ * 'owner_test' variable is set, the test will change slightly so as to allow
+ * testing of operations that require a matching uid/cuid.
+ */
+static void
+test_perm(void (* proc)(struct link *), int owner_test)
+{
+ struct link child1, child2;
+ int n, shift, bit, mask, rmask, drop1, drop2, sugid, id[3];
+
+ for (n = 0; n < 7; n++) {
+ /*
+ * Child 1 creates the semaphores, and child 2 opens them.
+ * For shift 6 (0700), child 1 drops its privileges to match
+ * child 2's (n=0). For shift 3 (0070), child 2 drops its user
+ * privileges (n=3). For shift 0 (0007), child 2 drops its
+ * group in addition to its user privileges (n=6). Also try
+ * with differing uid/cuid (n=1,2) and gid/cgid (n=4,5), where
+ * the current ownership (n=1,4) or the creator's ownership
+ * (n=2,5) is tested.
+ */
+ switch (n) {
+ case 0:
+ shift = 6;
+ drop1 = DROP_ALL;
+ drop2 = DROP_ALL;
+ sugid = SUGID_NONE;
+ break;
+ case 1:
+ shift = 6;
+ drop1 = DROP_NONE;
+ drop2 = DROP_ALL;
+ sugid = SUGID_NONROOT_USER;
+ break;
+ case 2:
+ shift = 6;
+ drop1 = DROP_USER;
+ drop2 = DROP_ALL;
+ sugid = SUGID_ROOT_USER;
+ break;
+ case 3:
+ shift = 3;
+ drop1 = DROP_NONE;
+ drop2 = DROP_USER;
+ sugid = SUGID_NONE;
+ break;
+ case 4:
+ shift = 3;
+ drop1 = DROP_NONE;
+ drop2 = DROP_ALL;
+ sugid = SUGID_NONROOT_GROUP;
+ break;
+ case 5:
+ /* The root group has no special privileges. */
+ shift = 3;
+ drop1 = DROP_NONE;
+ drop2 = DROP_USER;
+ sugid = SUGID_NONROOT_GROUP;
+ break;
+ case 6:
+ shift = 0;
+ drop1 = DROP_NONE;
+ drop2 = DROP_ALL;
+ sugid = SUGID_NONE;
+ break;
+ }
+
+ spawn(&child1, test_perm_child, drop1);
+ spawn(&child2, proc, drop2);
+
+ for (bit = 0; bit <= 7; bit++) {
+ mask = bit << shift;
+ rmask = 0777 & ~(7 << shift);
+
+ snd(&child1, mask);
+ snd(&child1, rmask);
+ snd(&child1, sugid);
+ id[0] = rcv(&child1);
+ id[1] = rcv(&child1);
+ id[2] = rcv(&child1);
+
+ snd(&child2, (owner_test) ? shift : bit);
+ snd(&child2, id[0]);
+ snd(&child2, id[1]);
+ snd(&child2, id[2]);
+ if (rcv(&child2) != 0) e(0);
+
+ snd(&child1, 0);
+ }
+
+ /* We use a bitmask of -1 to terminate the children. */
+ snd(&child1, -1);
+ snd(&child2, -1);
+
+ collect(&child1);
+ collect(&child2);
+ }
+}
+
+/*
+ * Test semget(2) permission checks. Please note that the checks are advisory:
+ * nothing keeps a process from opening a semaphore set with fewer privileges
+ * than required by the operations the process subsequently issues on the set.
+ */
+static void
+test88a_perm(struct link * parent)
+{
+ int r, tbit, bit, mask, id[3];
+
+ while ((tbit = rcv(parent)) != -1) {
+ id[0] = rcv(parent);
+ id[1] = rcv(parent);
+ id[2] = rcv(parent);
+
+ /*
+ * We skip setting lower bits, as it is not clear what effect
+ * that should have. We assume that zero bits should result in
+ * failure.
+ */
+ for (bit = 0; bit <= 7; bit++) {
+ mask = bit << 6;
+
+ /*
+ * Opening semaphore set A must succeed iff the given
+ * bits are all set in the relevant three-bit section
+ * of the creation mask.
+ */
+ r = semget(KEY_A, 0, mask);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if ((bit != 0 && (bit & tbit) == bit) != (r != -1))
+ e(0);
+ if (r != -1 && r != id[0]) e(0);
+
+ /*
+ * Same for semaphore set B, which was created with all
+ * irrelevant mode bits inverted.
+ */
+ r = semget(KEY_B, 0, mask);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if ((bit != 0 && (bit & tbit) == bit) != (r != -1))
+ e(0);
+ if (r != -1 && r != id[1]) e(0);
+
+ /*
+ * Semaphore set C was created with only irrelevant
+ * mode bits set, so opening it must always fail.
+ */
+ if (semget(KEY_C, 0, mask) != -1) e(0);
+ if (errno != EACCES) e(0);
+ }
+
+ snd(parent, 0);
+ }
+}
+
+/*
+ * Test the basic semget(2) functionality.
+ */
+static void
+test88a(void)
+{
+ struct seminfo seminfo;
+ struct semid_ds semds;
+ time_t now;
+ unsigned int i, j;
+ int id[3], *idp;
+
+ subtest = 0;
+
+ /*
+ * The key IPC_PRIVATE must always yield a new semaphore set identifier
+ * regardless of whether IPC_CREAT and IPC_EXCL are supplied.
+ */
+ if ((id[0] = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) < 0) e(0);
+
+ if ((id[1] = semget(IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | 0600)) < 0)
+ e(0);
+
+ if ((id[2] = semget(IPC_PRIVATE, 1, 0600)) < 0) e(0);
+
+ if (id[0] == id[1]) e(0);
+ if (id[1] == id[2]) e(0);
+ if (id[0] == id[2]) e(0);
+
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+ if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
+ if (semctl(id[2], 0, IPC_RMID) != 0) e(0);
+
+ /* Remove any leftovers from previous test runs. */
+ if ((id[0] = semget(KEY_A, 0, 0600)) >= 0 &&
+ semctl(id[0], 0, IPC_RMID) == -1) e(0);
+ if ((id[0] = semget(KEY_B, 0, 0600)) >= 0 &&
+ semctl(id[0], 0, IPC_RMID) == -1) e(0);
+
+ /*
+ * For non-IPC_PRIVATE keys, open(2)-like semantics apply with respect
+ * to IPC_CREAT and IPC_EXCL flags. The behavior of supplying IPC_EXCL
+ * without IPC_CREAT is undefined, so we do not test for that here.
+ */
+ if (semget(KEY_A, 1, 0600) != -1) e(0);
+ if (errno != ENOENT);
+
+ if ((id[0] = semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0600)) < 0) e(0);
+
+ if (semget(KEY_B, 1, 0600) != -1) e(0);
+ if (errno != ENOENT);
+
+ if ((id[1] = semget(KEY_B, 1, IPC_CREAT | 0600)) < 0) e(0);
+
+ if (id[0] == id[1]) e(0);
+
+ if ((id[2] = semget(KEY_A, 1, 0600)) < 0) e(0);
+ if (id[2] != id[0]) e(0);
+
+ if ((id[2] = semget(KEY_B, 1, IPC_CREAT | 0600)) < 0) e(0);
+ if (id[2] != id[2]) e(0);
+
+ if (semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0600) != -1) e(0);
+ if (errno != EEXIST) e(0);
+
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+ if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Check that we get the right error when we run out of semaphore sets.
+ * It is possible that other processes in the system are using sets
+ * right now, so see if we can anywhere from three (the number we had
+ * already) to SEMMNI semaphore sets, and check for ENOSPC after that.
+ */
+ if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
+ if (seminfo.semmni < 3 || seminfo.semmni > USHRT_MAX) e(0);
+
+ if ((idp = malloc(sizeof(int) * (seminfo.semmni + 1))) == NULL) e(0);
+
+ for (i = 0; i < seminfo.semmni + 1; i++) {
+ if ((idp[i] = semget(KEY_A + i, 1, IPC_CREAT | 0600)) < 0)
+ break;
+
+ /* Ensure that there are no ID collisions. O(n**2). */
+ for (j = 0; j < i; j++)
+ if (idp[i] == idp[j]) e(0);
+ }
+
+ if (errno != ENOSPC) e(0);
+ if (i < 3) e(0);
+ if (i == seminfo.semmni + 1) e(0);
+
+ while (i-- > 0)
+ if (semctl(idp[i], 0, IPC_RMID) != 0) e(0);
+
+ free(idp);
+
+ /*
+ * The given number of semaphores must be within bounds.
+ */
+ if (semget(KEY_A, -1, IPC_CREAT | 0600) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semget(KEY_A, 0, IPC_CREAT | 0600) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (seminfo.semmsl < 3 || seminfo.semmsl > USHRT_MAX) e(0);
+ if (semget(KEY_A, seminfo.semmsl + 1, IPC_CREAT | 0600) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if ((id[0] = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0600)) < 0)
+ e(0);
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+
+ if ((id[0] = semget(KEY_A, 2, IPC_CREAT | 0600)) < 0) e(0);
+
+ if ((id[1] = semget(KEY_A, 0, 0600)) < 0) e(0);
+ if (id[0] != id[1]) e(0);
+
+ if ((id[1] = semget(KEY_A, 1, 0600)) < 0) e(0);
+ if (id[0] != id[1]) e(0);
+
+ if ((id[1] = semget(KEY_A, 2, 0600)) < 0) e(0);
+ if (id[0] != id[1]) e(0);
+
+ if ((id[1] = semget(KEY_A, 3, 0600)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if ((id[1] = semget(KEY_A, seminfo.semmsl + 1, 0600)) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Verify that the initial values for the semaphore set are as
+ * expected.
+ */
+ time(&now);
+ if (seminfo.semmns < 3 + seminfo.semmsl) e(0);
+ if ((id[0] = semget(IPC_PRIVATE, 3, IPC_CREAT | IPC_EXCL | 0642)) < 0)
+ e(0);
+ if ((id[1] = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0613)) < 0)
+ e(0);
+
+ if (semctl(id[0], 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.uid != geteuid()) e(0);
+ if (semds.sem_perm.gid != getegid()) e(0);
+ if (semds.sem_perm.cuid != geteuid()) e(0);
+ if (semds.sem_perm.cgid != getegid()) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | 0642)) e(0);
+ if (semds.sem_perm._key != IPC_PRIVATE) e(0);
+ if (semds.sem_nsems != 3) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
+
+ for (i = 0; i < semds.sem_nsems; i++)
+ TEST_SEM(id[0], i, 0, 0, 0, 0);
+
+ if (semctl(id[1], 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.uid != geteuid()) e(0);
+ if (semds.sem_perm.gid != getegid()) e(0);
+ if (semds.sem_perm.cuid != geteuid()) e(0);
+ if (semds.sem_perm.cgid != getegid()) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | 0613)) e(0);
+ if (semds.sem_perm._key != KEY_A) e(0);
+ if (semds.sem_nsems != seminfo.semmsl) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
+
+ for (i = 0; i < semds.sem_nsems; i++)
+ TEST_SEM(id[1], i, 0, 0, 0, 0);
+
+ if (semctl(id[1], 0, IPC_RMID) != 0) e(0);
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Finally, perform a number of permission-related checks. Since the
+ * main test program is running with superuser privileges, most of the
+ * permission tests use an unprivileged child process.
+ */
+ /* The superuser can always open and destroy a semaphore set. */
+ if ((id[0] = semget(KEY_A, 1, IPC_CREAT | IPC_EXCL | 0000)) < 0) e(0);
+
+ if ((id[1] = semget(KEY_A, 0, 0600)) < 0) e(0);
+ if (id[0] != id[1]) e(0);
+
+ if ((id[1] = semget(KEY_A, 0, 0000)) < 0) e(0);
+ if (id[0] != id[1]) e(0);
+
+ if (semctl(id[0], 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * When an unprivileged process tries to open a semaphore set, the
+ * given upper three permission bits from the mode (0700) are tested
+ * against the appropriate permission bits from the semaphore set.
+ */
+ test_perm(test88a_perm, 0 /*owner_test*/);
+}
+
+/*
+ * Test semop(2) permission checks.
+ */
+static void
+test88b_perm(struct link * parent)
+{
+ struct sembuf sops[2];
+ size_t nsops;
+ int i, r, tbit, bit, id[3];
+
+ while ((tbit = rcv(parent)) != -1) {
+ id[0] = rcv(parent);
+ id[1] = rcv(parent);
+ id[2] = rcv(parent);
+
+ /*
+ * This loop is designed such that failure of any bit-based
+ * subset will not result in subsequent operations blocking.
+ */
+ for (i = 0; i < 8; i++) {
+ memset(sops, 0, sizeof(sops));
+
+ switch (i) {
+ case 0:
+ nsops = 1;
+ bit = 4;
+ break;
+ case 1:
+ sops[0].sem_op = 1;
+ nsops = 1;
+ bit = 2;
+ break;
+ case 2:
+ sops[0].sem_op = -1;
+ nsops = 1;
+ bit = 2;
+ break;
+ case 3:
+ sops[1].sem_op = 1;
+ nsops = 2;
+ bit = 6;
+ break;
+ case 4:
+ sops[0].sem_num = 1;
+ sops[1].sem_op = -1;
+ nsops = 2;
+ bit = 6;
+ break;
+ case 5:
+ sops[1].sem_num = 1;
+ nsops = 2;
+ bit = 4;
+ break;
+ case 6:
+ /*
+ * Two operations on the same semaphore. As
+ * such, this verifies that operations are
+ * processed in array order.
+ */
+ sops[0].sem_op = 1;
+ sops[1].sem_op = -1;
+ nsops = 2;
+ bit = 2;
+ break;
+ case 7:
+ /*
+ * Test the order of checks. Since IPC_STAT
+ * requires read permission, it is reasonable
+ * that the check against sem_nsems be done
+ * only after the permission check as well.
+ * For this test we rewrite EFBIG to OK below.
+ */
+ sops[0].sem_num = USHRT_MAX;
+ nsops = 2;
+ bit = 4;
+ break;
+ }
+
+ r = semop(id[0], sops, nsops);
+ if (i == 7 && r == -1 && errno == EFBIG) r = 0;
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ r = semop(id[1], sops, nsops);
+ if (i == 7 && r == -1 && errno == EFBIG) r = 0;
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ if (semop(id[2], sops, nsops) != -1) e(0);
+ if (errno != EACCES) e(0);
+ }
+
+ snd(parent, 0);
+ }
+}
+
+/*
+ * Signal handler.
+ */
+static void
+got_signal(int sig)
+{
+
+ if (sig != SIGHUP) e(0);
+ if (nr_signals != 0) e(0);
+ nr_signals++;
+}
+
+/*
+ * Child process for semop(2) tests, mainly testing blocking operations.
+ */
+static void
+test88b_child(struct link * parent)
+{
+ struct sembuf sops[5];
+ struct sigaction act;
+ int id;
+
+ id = rcv(parent);
+
+ memset(sops, 0, sizeof(sops));
+ if (semop(id, sops, 1) != 0) e(0);
+
+ if (rcv(parent) != 1) e(0);
+
+ sops[0].sem_op = -3;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ if (rcv(parent) != 2) e(0);
+
+ sops[0].sem_num = 2;
+ sops[0].sem_op = 2;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = -1;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 1;
+ if (semop(id, sops, 3) != 0) e(0);
+
+ if (rcv(parent) != 3) e(0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 0;
+ sops[3].sem_num = 2;
+ sops[3].sem_op = 0;
+ sops[4].sem_num = 2;
+ sops[4].sem_op = 1;
+ if (semop(id, sops, 5) != 0) e(0);
+
+ if (rcv(parent) != 4) e(0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = -2;
+ sops[1].sem_num = 2;
+ sops[1].sem_op = 0;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ if (rcv(parent) != 5) e(0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = -1;
+ sops[1].sem_flg = IPC_NOWAIT;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ if (rcv(parent) != 6) e(0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 0;
+ sops[1].sem_flg = IPC_NOWAIT;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != EAGAIN) e(0);
+
+ if (rcv(parent) != 7) e(0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ sops[1].sem_flg = 0;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ if (rcv(parent) != 8) e(0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 2;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = got_signal;
+ sigfillset(&act.sa_mask);
+ if (sigaction(SIGHUP, &act, NULL) != 0) e(0);
+
+ if (rcv(parent) != 9) e(0);
+
+ memset(sops, 0, sizeof(sops));
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 1;
+ sops[2].sem_num = 1;
+ sops[2].sem_op = 0;
+ if (semop(id, sops, 3) != -1)
+ if (errno != EINTR) e(0);
+ if (nr_signals != 1) e(0);
+
+ TEST_SEM(id, 0, 0, parent->pid, 0, 0);
+ TEST_SEM(id, 1, 1, parent->pid, 0, 0);
+
+ if (rcv(parent) != 10) e(0);
+
+ memset(sops, 0, sizeof(sops));
+ sops[0].sem_op = -3;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != EIDRM) e(0);
+
+ id = rcv(parent);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ if (rcv(parent) != 11) e(0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 0;
+ sops[1].sem_op = -1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ id = rcv(parent);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 0;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ snd(parent, errct);
+ if (rcv(parent) != 12) e(0);
+
+ /* The child will be killed during this call. It should not return. */
+ sops[0].sem_num = 1;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 3;
+ (void)semop(id, sops, 2);
+
+ e(0);
+}
+
+/*
+ * Test the basic semop(2) functionality.
+ */
+static void
+test88b(void)
+{
+ struct seminfo seminfo;
+ struct semid_ds semds;
+ struct sembuf *sops, *sops2;
+ size_t size;
+ struct link child;
+ time_t now;
+ unsigned short val[2];
+ int id;
+
+ subtest = 1;
+
+ /* Allocate a buffer for operations. */
+ if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
+
+ if (seminfo.semopm < 3 || seminfo.semopm > USHRT_MAX) e(0);
+
+ size = sizeof(sops[0]) * (seminfo.semopm + 1);
+ if ((sops = malloc(size)) == NULL) e(0);
+ memset(sops, 0, size);
+
+ /* Do a few first tests with a set containing one semaphore. */
+ if ((id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600)) == -1) e(0);
+
+ /* If no operations are given, the call should succeed. */
+ if (semop(id, NULL, 0) != 0) e(0);
+
+ /*
+ * If any operations are given, the pointer must be valid. Moreover,
+ * partially valid buffers must never be processed partially.
+ */
+ if (semop(id, NULL, 1) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semop(id, bad_ptr, 1) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ memset(page_ptr, 0, page_size);
+ sops2 = ((struct sembuf *)bad_ptr) - 1;
+ sops2->sem_op = 1;
+ if (semop(id, sops2, 2) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ /*
+ * A new semaphore set is initialized to an all-zeroes state, and a
+ * zeroed operation tests for a zeroed semaphore. This should pass.
+ */
+ time(&now);
+ if (semop(id, sops, 1) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
+
+ /* Test the limit on the number of operations. */
+ if (semop(id, sops, seminfo.semopm) != 0) e(0);
+
+ if (semop(id, sops, seminfo.semopm + 1) != -1) e(0);
+ if (errno != E2BIG) e(0);
+
+ if (semop(id, sops, SIZE_MAX) != -1) e(0);
+ if (errno != E2BIG) e(0);
+
+ /* Test the range check on the semaphore numbers. */
+ sops[1].sem_num = 1;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != EFBIG) e(0);
+
+ sops[1].sem_num = USHRT_MAX;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != EFBIG) e(0);
+
+ /*
+ * Test nonblocking operations on a single semaphore, starting with
+ * value limit and overflow cases.
+ */
+ if (seminfo.semvmx < 3 || seminfo.semvmx > SHRT_MAX) e(0);
+
+ sops[0].sem_flg = IPC_NOWAIT;
+
+ /* This block does not trigger on MINIX3. */
+ if (seminfo.semvmx < SHRT_MAX) {
+ sops[0].sem_op = seminfo.semvmx + 1;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+ if (semctl(id, 0, GETVAL) != 0) e(0);
+ }
+
+ sops[0].sem_op = seminfo.semvmx;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
+
+ /* As of writing, the proper checks for this is missing on NetBSD. */
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+ if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
+
+ sops[0].sem_op = seminfo.semvmx;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+ if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
+
+ sops[0].sem_op = SHRT_MAX;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+ if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
+
+ /* This block does trigger on MINIX3. */
+ if (seminfo.semvmx < -(int)SHRT_MIN) {
+ sops[0].sem_op = -seminfo.semvmx - 1;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != EAGAIN) e(0);
+ if (semctl(id, 0, GETVAL) != seminfo.semvmx) e(0);
+ }
+
+ sops[0].sem_op = -seminfo.semvmx;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != 0) e(0);
+
+ /*
+ * Test basic nonblocking operations on a single semaphore.
+ */
+ sops[0].sem_op = 0;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ sops[0].sem_op = 2;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != 2) e(0);
+
+ sops[0].sem_op = 0;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != EAGAIN) e(0);
+
+ sops[0].sem_op = -3;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != EAGAIN) e(0);
+
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != 3) e(0);
+
+ sops[0].sem_op = -1;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != 2) e(0);
+
+ sops[0].sem_op = 0;
+ if (semop(id, sops, 1) != -1) e(0);
+ if (errno != EAGAIN) e(0);
+
+ sops[0].sem_op = -2;
+ if (semop(id, sops, 1) != 0) e(0);
+ if (semctl(id, 0, GETVAL) != 0) e(0);
+
+ sops[0].sem_op = 0;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ /* Make sure that not too much data is being read in. */
+ sops2->sem_op = 0;
+ sops2--;
+ if (semop(id, sops2, 2) != 0) e(0);
+
+ /* Even if no operations are given, the identifier must be valid. */
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ if (semop(id, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semop(-1, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semop(INT_MIN, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ memset(&semds, 0, sizeof(semds));
+ id = IXSEQ_TO_IPCID(seminfo.semmni, semds.sem_perm);
+ if (semop(id, NULL, 0) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /*
+ * Test permission checks. As part of this, test basic nonblocking
+ * multi-operation calls, including operation processing in array order
+ * and the order of (permission vs other) checks.
+ */
+ test_perm(test88b_perm, 0 /*owner_test*/);
+
+ /*
+ * Test blocking operations, starting with a single blocking operation.
+ */
+ if ((id = semget(IPC_PRIVATE, 3, 0600)) == -1) e(0);
+
+ memset(sops, 0, sizeof(sops[0]));
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+
+ spawn(&child, test88b_child, DROP_NONE);
+
+ snd(&child, id);
+
+ /*
+ * In various places, we have to sleep in order to allow the child to
+ * get itself blocked in a semop(2) call.
+ */
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 1);
+
+ sops[0].sem_op = -1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+
+ snd(&child, 1);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, getpid(), 1, 0);
+
+ /* This should cause a (fruitless) retry of the blocking operation. */
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 2, getpid(), 1, 0);
+
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+
+ /*
+ * Test blocking operations, verifying the correct operation of
+ * multiple (partially) blocking operations and atomicity.
+ */
+ memset(sops, 0, sizeof(sops[0]) * 2);
+ if (semop(id, sops, 1) != 0) e(0);
+
+ /* One blocking operation. */
+ snd(&child, 2);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 0, 0, 1, 0);
+ TEST_SEM(id, 2, 0, 0, 0, 0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, child.pid, 0, 0);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+ TEST_SEM(id, 2, 2, child.pid, 0, 0);
+
+ /* Two blocking operations in one call, resolved at once. */
+ snd(&child, 3);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, child.pid, 0, 1);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+ TEST_SEM(id, 2, 2, child.pid, 0, 0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 2;
+ sops[1].sem_op = -2;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 1, child.pid, 0, 0);
+ TEST_SEM(id, 2, 1, child.pid, 0, 0);
+
+ /* Two blocking operations in one call, resolved one by one. */
+ snd(&child, 4);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 1, child.pid, 1, 0);
+ TEST_SEM(id, 2, 1, child.pid, 0, 0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = 1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 2, getpid(), 0, 0);
+ TEST_SEM(id, 2, 1, child.pid, 0, 1);
+
+ sops[0].sem_num = 2;
+ sops[0].sem_op = -1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+ TEST_SEM(id, 2, 0, child.pid, 0, 0);
+
+ /* One blocking op followed by a nonblocking one, cleared at once. */
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 0;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ snd(&child, 5);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, getpid(), 1, 0);
+ TEST_SEM(id, 1, 0, getpid(), 0, 0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+
+ /* One blocking op followed by a nonblocking one, only one cleared. */
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ snd(&child, 6);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+ TEST_SEM(id, 1, 1, getpid(), 0, 1);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = -1;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+ TEST_SEM(id, 1, 0, getpid(), 0, 0);
+
+ /*
+ * Ensure that all semaphore numbers are checked immediately, which
+ * given the earlier test results also implies that permissions are
+ * checked immediately (so we don't have to recheck that too). We do
+ * not check whether permissions are rechecked after a blocking
+ * operation, because the specification does not describe the intended
+ * behavior on this point.
+ */
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 4;
+ sops[1].sem_op = 0;
+ if (semop(id, sops, 2) != -1) e(0);
+ if (errno != EFBIG) e(0);
+
+ /*
+ * Ensure that semaphore value overflow is detected properly, at the
+ * moment that the operation is actually processed.
+ */
+ sops[0].sem_num = 1;
+ sops[0].sem_op = seminfo.semvmx;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ snd(&child, 7);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 1);
+ TEST_SEM(id, 1, seminfo.semvmx, getpid(), 0, 0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = -1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, seminfo.semvmx, child.pid, 0, 0);
+
+ sops[0].sem_num = 1;
+ sops[0].sem_op = -2;
+ if (semop(id, sops, 1) != 0) e(0);
+
+ snd(&child, 8);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, child.pid, 1, 0);
+ TEST_SEM(id, 1, seminfo.semvmx - 2, getpid(), 0, 0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+ TEST_SEM(id, 1, seminfo.semvmx - 1, getpid(), 0, 0);
+
+ sops[0].sem_num = 0;
+ sops[0].sem_op = seminfo.semvmx - 1;
+ sops[1].sem_num = 0;
+ sops[1].sem_op = seminfo.semvmx - 1;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 2;
+ /*
+ * With the current SEMVMX, the sum of the values is now USHRT_MAX-1,
+ * which if processed could result in a zero semaphore value. That
+ * should not happen. Looking at you, NetBSD.
+ */
+ if (semop(id, sops, 3) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ TEST_SEM(id, 0, 1, getpid(), 0, 0);
+
+ /*
+ * Check that a blocking semop(2) call fails with EINTR if a signal is
+ * caught by the process after the call has blocked.
+ */
+ if (semctl(id, 1, SETVAL, 0) != 0) e(0);
+ sops[0].sem_num = 0;
+ sops[0].sem_op = -1;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 1;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 1, getpid(), 0, 0);
+
+ snd(&child, 9);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 1, getpid(), 0, 1);
+
+ kill(child.pid, SIGHUP);
+ /*
+ * Kills are not guaranteed to be delivered immediately to processes
+ * other than the caller of kill(2), so let the child perform checks.
+ */
+
+ /*
+ * Check that a blocking semop(2) call fails with EIDRM if the
+ * semaphore set is removed after the call has blocked.
+ */
+ snd(&child, 10);
+
+ usleep(WAIT_USECS);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Check if sem_otime is updated correctly. Instead of sleeping for
+ * whole seconds so as to be able to detect differences, use SETVAL,
+ * which does not update sem_otime at all. This doubles as a first
+ * test to see if SETVAL correctly wakes up a blocked semop(2) call.
+ */
+ if ((id = semget(IPC_PRIVATE, 2, 0600)) == -1) e(0);
+
+ snd(&child, id);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ if (semctl(id, 1, SETVAL, seminfo.semvmx) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ if (semctl(id, 0, SETVAL, 1) != 0) e(0);
+ TEST_SEM(id, 0, 1, 0, 0, 0);
+ TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 0);
+
+ if (semctl(id, 0, SETVAL, 0) != 0) e(0);
+
+ snd(&child, 11);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ TEST_SEM(id, 1, seminfo.semvmx, 0, 0, 1);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ if (semctl(id, 1, SETVAL, 0) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ time(&now);
+ if (semctl(id, 0, SETVAL, 2) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, child.pid, 0, 0);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Perform a similar test for SETALL, ensuring that it causes an
+ * ongoing semop(2) to behave correctly.
+ */
+ if ((id = semget(IPC_PRIVATE, 2, 0600)) == -1) e(0);
+
+ snd(&child, id);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+
+ val[0] = 1;
+ val[1] = 1;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, 0, 0, 0);
+ TEST_SEM(id, 1, 1, 0, 0, 1);
+
+ val[0] = 0;
+ val[1] = 1;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, 1, 0, 0, 0);
+
+ val[0] = 1;
+ val[1] = 1;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, 0, 0, 0);
+ TEST_SEM(id, 1, 1, 0, 0, 1);
+
+ val[0] = 0;
+ val[1] = 0;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 1, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ time(&now);
+ val[0] = 1;
+ val[1] = 0;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, child.pid, 0, 0);
+ TEST_SEM(id, 1, 0, child.pid, 0, 0);
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime < now || semds.sem_otime >= now + 10) e(0);
+
+ /*
+ * Finally, ensure that if the child is killed, its blocked semop(2)
+ * call is properly cancelled.
+ */
+ sops[0].sem_num = 0;
+ sops[0].sem_op = 0;
+ sops[1].sem_num = 1;
+ sops[1].sem_op = 0;
+ if (semop(id, sops, 2) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 0, getpid(), 0, 0);
+
+ /* We'll be terminating the child, so let it report its errors now. */
+ if (rcv(&child) != 0) e(0);
+
+ snd(&child, 12);
+
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 0, getpid(), 1, 0);
+
+ terminate(&child);
+
+ TEST_SEM(id, 0, 0, getpid(), 0, 0);
+ TEST_SEM(id, 1, 0, getpid(), 0, 0);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ free(sops);
+}
+
+/*
+ * Test semctl(2) permission checks, part 1: regular commands.
+ */
+static void
+test88c_perm1(struct link * parent)
+{
+ static const int cmds[] = { GETVAL, GETPID, GETNCNT, GETZCNT };
+ struct semid_ds semds;
+ struct seminfo seminfo;
+ unsigned short val[3];
+ int i, r, tbit, bit, id[3], cmd;
+ void *ptr;
+
+ while ((tbit = rcv(parent)) != -1) {
+ id[0] = rcv(parent);
+ id[1] = rcv(parent);
+ id[2] = rcv(parent);
+
+ /* First the read-only, no-argument cases. */
+ bit = 4;
+ for (i = 0; i < __arraycount(cmds); i++) {
+ r = semctl(id[0], 0, cmds[i]);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ r = semctl(id[1], 0, cmds[i]);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ if (semctl(id[2], 0, cmds[i]) != -1) e(0);
+ if (errno != EACCES) e(0);
+ }
+
+ /*
+ * Then SETVAL, which requires write permission and is the only
+ * one that takes an integer argument.
+ */
+ bit = 2;
+ r = semctl(id[0], 0, SETVAL, 0);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ r = semctl(id[1], 0, SETVAL, 0);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ if (semctl(id[2], 0, SETVAL, 0) != -1) e(0);
+ if (errno != EACCES) e(0);
+
+ /*
+ * Finally the commands that require read or write permission
+ * and take a pointer as argument.
+ */
+ memset(val, 0, sizeof(val));
+
+ for (i = 0; i < 4; i++) {
+ switch (i) {
+ case 0:
+ cmd = GETALL;
+ ptr = val;
+ bit = 4;
+ break;
+ case 1:
+ cmd = SETALL;
+ ptr = val;
+ bit = 2;
+ break;
+ case 2:
+ cmd = IPC_STAT;
+ ptr = &semds;
+ bit = 4;
+ break;
+ }
+
+ r = semctl(id[0], 0, cmd, ptr);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ r = semctl(id[1], 0, cmd, ptr);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ if (semctl(id[2], 0, cmd, ptr) != -1) e(0);
+ if (errno != EACCES) e(0);
+ }
+
+ /*
+ * I was hoping to avoid this, but otherwise we have to make
+ * the other child iterate through all semaphore sets to find
+ * the right index for each of the identifiers. As noted in
+ * the IPC server itself as well, duplicating these macros is
+ * not a big deal since the split is firmly hardcoded through
+ * the exposure of IXSEQ_TO_IPCID to userland.
+ */
+#ifndef IPCID_TO_IX
+#define IPCID_TO_IX(id) ((id) & 0xffff)
+#endif
+
+ bit = 4;
+
+ r = semctl(IPCID_TO_IX(id[0]), 0, SEM_STAT, &semds);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ r = semctl(IPCID_TO_IX(id[1]), 0, SEM_STAT, &semds);
+ if (r < 0 && (r != -1 || errno != EACCES)) e(0);
+ if (((bit & tbit) == bit) != (r != -1)) e(0);
+
+ if (semctl(IPCID_TO_IX(id[2]), 0, SEM_STAT, &semds) != -1)
+ e(0);
+ if (errno != EACCES) e(0);
+
+ /*
+ * IPC_INFO and SEM_INFO should always succeed. They do not
+ * even take a semaphore set identifier.
+ */
+ if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
+ if (semctl(0, 0, SEM_INFO, &seminfo) == -1) e(0);
+
+ snd(parent, 0);
+ }
+}
+
+/*
+ * Test semctl(2) permission checks, part 2: the IPC_SET command.
+ */
+static void
+test88c_perm2(struct link * parent)
+{
+ struct semid_ds semds;
+ int r, shift, id[3];
+
+ while ((shift = rcv(parent)) != -1) {
+ id[0] = rcv(parent);
+ id[1] = rcv(parent);
+ id[2] = rcv(parent);
+
+ /*
+ * Test IPC_SET. Ideally, we would set the permissions to what
+ * they currently are, but we do not actually know what they
+ * are, and IPC_STAT requires read permission which we may not
+ * have! However, no matter what we do, we cannot prevent the
+ * other child from being able to remove the semaphore sets
+ * afterwards. So, we just set the permissions to all-zeroes;
+ * even though those values are meaningful (mode 0000, uid 0,
+ * gid 0) they could be anything: the API will accept anything.
+ * This does mean we need to test IPC_RMID permissions from
+ * another procedure, because we may now be locking ourselves
+ * out. The System V IPC interface is pretty strange that way.
+ */
+ memset(&semds, 0, sizeof(semds));
+
+ r = semctl(id[0], 0, IPC_SET, &semds);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ r = semctl(id[1], 0, IPC_SET, &semds);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ /* For once, this too should succeed. */
+ r = semctl(id[2], 0, IPC_SET, &semds);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ snd(parent, 0);
+ }
+}
+
+/*
+ * Test semctl(2) permission checks, part 3: the IPC_RMID command.
+ */
+static void
+test88c_perm3(struct link * parent)
+{
+ int r, shift, id[3];
+
+ while ((shift = rcv(parent)) != -1) {
+ id[0] = rcv(parent);
+ id[1] = rcv(parent);
+ id[2] = rcv(parent);
+
+ r = semctl(id[0], 0, IPC_RMID);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ r = semctl(id[1], 0, IPC_RMID);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ /* Okay, twice then. */
+ r = semctl(id[2], 0, IPC_RMID);
+ if (r < 0 && (r != -1 || errno != EPERM)) e(0);
+ if ((shift == 6) != (r != -1)) e(0);
+
+ snd(parent, 0);
+ }
+}
+
+/*
+ * Test the basic semctl(2) functionality.
+ */
+static void
+test88c(void)
+{
+ static const int cmds[] = { GETVAL, GETPID, GETNCNT, GETZCNT };
+ struct seminfo seminfo;
+ struct semid_ds semds, osemds;
+ unsigned short val[4], seen[2];
+ char statbuf[sizeof(struct semid_ds) + 1];
+ unsigned int i, j;
+ time_t now;
+ int r, id, id2, badid1, badid2, cmd;
+
+ subtest = 2;
+
+ if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
+
+ /*
+ * Start with permission checks on the commands. IPC_SET and IPC_RMID
+ * are special: they check for ownership (uid/cuid) and return EPERM
+ * rather than EACCES on permission failure.
+ */
+ test_perm(test88c_perm1, 0 /*owner_test*/);
+ test_perm(test88c_perm2, 1 /*owner_test*/);
+ test_perm(test88c_perm3, 1 /*owner_test*/);
+
+ /* Create identifiers known to be invalid. */
+ if ((badid1 = semget(IPC_PRIVATE, 1, 0600)) < 0) e(0);
+
+ if (semctl(badid1, 0, IPC_RMID) != 0) e(0);
+
+ memset(&semds, 0, sizeof(semds));
+ badid2 = IXSEQ_TO_IPCID(seminfo.semmni, semds.sem_perm);
+
+ if ((id = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600)) < 0) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime == 0) e(0);
+
+ /* In this case we can't avoid sleeping for longer periods.. */
+ while (time(&now) == semds.sem_ctime)
+ usleep(250000);
+
+ /*
+ * Test the simple GET commands. The actual functionality of these
+ * commands have already been tested thoroughly as part of the
+ * semop(2) part of the test set, so we do not repeat that here.
+ */
+ for (i = 0; i < __arraycount(cmds); i++) {
+ for (j = 0; j < 3; j++)
+ if (semctl(id, j, cmds[i]) != 0) e(0);
+
+ if (semctl(badid1, 0, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, -1, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 3, cmds[i]) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /* These commands should not update ctime or otime. */
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+ }
+
+ /*
+ * Test the GETALL command.
+ */
+ /*
+ * Contrary to what the Open Group specification suggests, actual
+ * implementations agree that the semnum parameter is to be ignored for
+ * calls not involving a specific semaphore in the set.
+ */
+ for (j = 0; j < 5; j++) {
+ for (i = 0; i < __arraycount(val); i++)
+ val[i] = USHRT_MAX;
+
+ if (semctl(id, (int)j - 1, GETALL, val) != 0) e(0);
+ for (i = 0; i < 3; i++)
+ if (val[i] != 0) e(0);
+ if (val[i] != USHRT_MAX) e(0);
+ }
+
+ for (i = 0; i < __arraycount(val); i++)
+ val[i] = USHRT_MAX;
+
+ if (semctl(badid1, 0, GETALL, val) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, GETALL, val) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, GETALL, val) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, GETALL, val) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ for (i = 0; i < __arraycount(val); i++)
+ if (val[i] != USHRT_MAX) e(0);
+
+ if (semctl(id, 0, GETALL, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, GETALL, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, GETALL, ((unsigned short *)bad_ptr) - 2) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, GETALL, ((unsigned short *)bad_ptr) - 3) != 0) e(0);
+
+ /* Still no change in either otime or ctime. */
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+
+ /*
+ * Test the IPC_STAT command. This is the last command we are testing
+ * here that does not affect sem_ctime, so in order to avoid extra
+ * sleep times, we test this command first now.
+ */
+ /*
+ * The basic IPC_STAT functionality has already been tested heavily as
+ * part of the semget(2) and permission tests, so we do not repeat that
+ * here.
+ */
+ memset(statbuf, 0x5a, sizeof(statbuf));
+
+ if (semctl(badid1, 0, IPC_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, IPC_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, IPC_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, IPC_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ for (i = 0; i < sizeof(statbuf); i++)
+ if (statbuf[i] != 0x5a) e(0);
+
+ if (semctl(id, 0, IPC_STAT, statbuf) != 0) e(0);
+
+ if (statbuf[sizeof(statbuf) - 1] != 0x5a) e(0);
+
+ if (semctl(id, 0, IPC_STAT, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, IPC_STAT, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, IPC_STAT, ((struct semid_ds *)bad_ptr) - 1) != 0)
+ e(0);
+
+ if (semctl(id, -1, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+
+ /*
+ * Test SEM_STAT.
+ */
+ if ((id2 = semget(KEY_A, seminfo.semmsl, IPC_CREAT | 0642)) < 0) e(0);
+
+ memset(statbuf, 0x5a, sizeof(statbuf));
+
+ if (semctl(-1, 0, SEM_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(seminfo.semmni, 0, SEM_STAT, statbuf) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ for (i = 0; i < sizeof(statbuf); i++)
+ if (statbuf[i] != 0x5a) e(0);
+
+ memset(seen, 0, sizeof(seen));
+
+ for (i = 0; i < seminfo.semmni; i++) {
+ errno = 0;
+ if ((r = semctl(i, i / 2 - 1, SEM_STAT, statbuf)) == -1) {
+ if (errno != EINVAL) e(0);
+ continue;
+ }
+ if (r < 0) e(0);
+ memcpy(&semds, statbuf, sizeof(semds));
+ if (!(semds.sem_perm.mode & SEM_ALLOC)) e(0);
+ if (semds.sem_ctime == 0) e(0);
+ if (IXSEQ_TO_IPCID(i, semds.sem_perm) != r) e(0);
+ if (r == id) {
+ seen[0]++;
+ if (semds.sem_perm.mode != (SEM_ALLOC | 0600)) e(0);
+ if (semds.sem_perm.uid != geteuid()) e(0);
+ if (semds.sem_perm.gid != getegid()) e(0);
+ if (semds.sem_perm.cuid != semds.sem_perm.uid) e(0);
+ if (semds.sem_perm.cgid != semds.sem_perm.gid) e(0);
+ if (semds.sem_perm._key != IPC_PRIVATE) e(0);
+ if (semds.sem_nsems != 3) e(0);
+ if (semds.sem_otime != 0) e(0);
+
+ /* This is here because we need a valid index. */
+ if (semctl(i, 0, SEM_STAT, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(i, 0, SEM_STAT, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+ } else if (r == id2) {
+ seen[1]++;
+ if (semds.sem_perm.mode != (SEM_ALLOC | 0642)) e(0);
+ if (semds.sem_perm.uid != geteuid()) e(0);
+ if (semds.sem_perm.gid != getegid()) e(0);
+ if (semds.sem_perm.cuid != semds.sem_perm.uid) e(0);
+ if (semds.sem_perm.cgid != semds.sem_perm.gid) e(0);
+ if (semds.sem_perm._key != KEY_A) e(0);
+ if (semds.sem_nsems != seminfo.semmsl) e(0);
+ }
+ }
+
+ if (seen[0] != 1) e(0);
+ if (seen[1] != 1) e(0);
+
+ if (statbuf[sizeof(statbuf) - 1] != 0x5a) e(0);
+
+ if (semctl(id, 5, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+
+ /*
+ * Test SETVAL. We start with all the failure cases, so as to be able
+ * to check that sem_ctime is not changed in those cases.
+ */
+ if (semctl(badid1, 0, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, -1, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 3, SETVAL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, SETVAL, -1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ if (semctl(id, 0, SETVAL, seminfo.semvmx + 1) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+
+ /* Alright, there we go.. */
+ if (semctl(id, 1, SETVAL, 0) != 0) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
+
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+
+ if (semctl(id, 2, SETVAL, seminfo.semvmx) != 0) e(0);
+
+ TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
+
+ if (semctl(id, 0, SETVAL, 1) != 0) e(0);
+
+ TEST_SEM(id, 0, 1, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
+
+ if (semctl(id, 0, GETALL, val) != 0) e(0);
+ if (val[0] != 1) e(0);
+ if (val[1] != 0) e(0);
+ if (val[2] != seminfo.semvmx) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+
+ while (time(&now) == semds.sem_ctime)
+ usleep(250000);
+
+ /*
+ * Test SETALL. Same idea: failure cases first.
+ */
+ if (semctl(badid1, 0, SETALL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, SETALL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, SETALL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, SETALL, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ val[0] = seminfo.semvmx + 1;
+ val[1] = 0;
+ val[2] = 0;
+ if (semctl(id, 0, SETALL, val) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ val[0] = 0;
+ val[1] = 1;
+ val[2] = seminfo.semvmx + 1;
+ if (semctl(id, 0, SETALL, val) != -1) e(0);
+ if (errno != ERANGE) e(0);
+
+ if (semctl(id, 0, SETALL, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, SETALL, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, SETALL, ((unsigned short *)bad_ptr) - 2) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ TEST_SEM(id, 0, 1, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime >= now) e(0);
+
+ val[0] = seminfo.semvmx;
+ val[1] = 0;
+ val[2] = 0;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_otime != 0) e(0);
+ if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
+
+ TEST_SEM(id, 0, seminfo.semvmx, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ TEST_SEM(id, 2, 0, 0, 0, 0);
+
+ val[0] = 0;
+ val[1] = 1;
+ val[2] = seminfo.semvmx;
+ if (semctl(id, INT_MAX, SETALL, val) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ TEST_SEM(id, 1, 1, 0, 0, 0);
+ TEST_SEM(id, 2, seminfo.semvmx, 0, 0, 0);
+
+ memset(page_ptr, 0, page_size);
+ if (semctl(id, 0, SETALL, ((unsigned short *)bad_ptr) - 3) != 0) e(0);
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, 0, 0);
+ TEST_SEM(id, 2, 0, 0, 0, 0);
+
+ while (time(&now) == semds.sem_ctime)
+ usleep(250000);
+
+ /*
+ * Test IPC_SET. Its core functionality has already been tested
+ * thoroughly as part of the permission tests.
+ */
+ if (semctl(badid1, 0, IPC_SET, &semds) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, IPC_SET, &semds) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, IPC_SET, &semds) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, IPC_SET, &semds) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, IPC_SET, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, IPC_SET, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &osemds) != 0) e(0);
+ if (osemds.sem_otime != 0) e(0);
+ if (osemds.sem_ctime >= now) e(0);
+
+ /*
+ * Only mode, uid, gid may be set. While the given mode is sanitized
+ * in our implementation (see below; the open group specification
+ * leaves this undefined), the uid and gid are not (we do not test this
+ * exhaustively). The other given fields must be ignored. The ctime
+ * field will be updated.
+ */
+ memset(&semds, 0x5b, sizeof(semds));
+ semds.sem_perm.mode = 0712;
+ semds.sem_perm.uid = UID_MAX;
+ semds.sem_perm.gid = GID_MAX - 1;
+ if (semctl(id, 0, IPC_SET, &semds) != 0) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | 0712)) e(0);
+ if (semds.sem_perm.uid != UID_MAX) e(0);
+ if (semds.sem_perm.gid != GID_MAX - 1) e(0);
+ if (semds.sem_perm.cuid != osemds.sem_perm.cuid) e(0);
+ if (semds.sem_perm.cgid != osemds.sem_perm.cgid) e(0);
+ if (semds.sem_perm._seq != osemds.sem_perm._seq) e(0);
+ if (semds.sem_perm._key != osemds.sem_perm._key) e(0);
+ if (semds.sem_nsems != osemds.sem_nsems) e(0);
+ if (semds.sem_otime != osemds.sem_otime) e(0);
+ if (semds.sem_ctime < now || semds.sem_ctime >= now + 10) e(0);
+
+ /* It should be possible to set any mode, but mask 0777 is applied. */
+ semds.sem_perm.uid = osemds.sem_perm.uid;
+ semds.sem_perm.gid = osemds.sem_perm.gid;
+ for (i = 0; i < 0777; i++) {
+ semds.sem_perm.mode = i;
+ if (semctl(id, i / 2 - 1, IPC_SET, &semds) != 0) e(0);
+
+ if (semctl(id, i / 2 - 2, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | i)) e(0);
+
+ semds.sem_perm.mode = ~0777 | i;
+ if (semctl(id, i / 2 - 3, IPC_SET, &semds) != 0) e(0);
+
+ if (semctl(id, i / 2 - 4, IPC_STAT, &semds) != 0) e(0);
+ if (semds.sem_perm.mode != (SEM_ALLOC | i)) e(0);
+ }
+ if (semds.sem_perm.uid != osemds.sem_perm.uid) e(0);
+ if (semds.sem_perm.gid != osemds.sem_perm.gid) e(0);
+
+ if (semctl(id, 0, IPC_SET, ((struct semid_ds *)bad_ptr) - 1) != 0)
+ e(0);
+
+ /*
+ * Test IPC_RMID. Its basic functionality has already been tested
+ * multiple times over, so there is not much left to do here.
+ */
+ if (semctl(badid1, 0, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(badid2, 0, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(-1, 0, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(INT_MIN, 0, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ if (semctl(id, 0, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, IPC_STAT, &semds) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id2, 1, IPC_RMID) != 0) e(0);
+
+ if (semctl(id2, 1, IPC_RMID) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ /*
+ * Test IPC_INFO and SEM_INFO. Right now, for all practical purposes,
+ * these identifiers behave pretty much the same.
+ */
+ if ((id = semget(IPC_PRIVATE, 3, 0600)) == -1) e(0);
+ if ((id2 = semget(IPC_PRIVATE, 1, 0600)) == -1) e(0);
+
+ for (i = 0; i <= 1; i++) {
+ cmd = (i == 0) ? IPC_INFO : SEM_INFO;
+
+ memset(&seminfo, 0xff, sizeof(seminfo));
+
+ if ((r = semctl(0, 0, cmd, &seminfo)) == -1) e(0);
+
+ /*
+ * These commands return the index of the highest in-use slot
+ * in the semaphore set table. Bad idea of course, because
+ * that means the value 0 has two potential meanings. Since we
+ * cannot guarantee that no other running application is using
+ * semaphores, we settle for "at least" tests based on the two
+ * semaphore sets we just created.
+ */
+ if (r < 1 || r >= seminfo.semmni) e(0);
+
+ /*
+ * Many of these checks are rather basic because of missing
+ * SEM_UNDO support. The only difference between IPC_INFO and
+ * SEM_INFO is the meaning of the semusz and semaem fields.
+ */
+ if (seminfo.semmap < 0) e(0);
+ if (seminfo.semmni < 3 || seminfo.semmni > USHRT_MAX) e(0);
+ if (seminfo.semmns < 3 || seminfo.semmns > USHRT_MAX) e(0);
+ if (seminfo.semmnu < 0) e(0);
+ if (seminfo.semmsl < 3 || seminfo.semmsl > USHRT_MAX) e(0);
+ if (seminfo.semopm < 3 || seminfo.semopm > USHRT_MAX) e(0);
+ if (seminfo.semume < 0) e(0);
+ if (cmd == SEM_INFO) {
+ if (seminfo.semusz < 2) e(0);
+ } else
+ if (seminfo.semusz < 0) e(0);
+ if (seminfo.semvmx < 3 || seminfo.semvmx > SHRT_MAX) e(0);
+ if (cmd == SEM_INFO) {
+ if (seminfo.semaem < 4) e(0);
+ } else
+ if (seminfo.semaem < 0) e(0);
+
+ if (semctl(INT_MAX, -1, cmd, &seminfo) == -1) e(0);
+ if (semctl(-1, INT_MAX, cmd, &seminfo) == -1) e(0);
+
+ if (semctl(0, 0, cmd, NULL) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(0, 0, cmd, bad_ptr) != -1) e(0);
+ if (errno != EFAULT) e(0);
+
+ if (semctl(0, 0, cmd, ((struct seminfo *)bad_ptr) - 1) == -1)
+ e(0);
+ }
+
+ if (semctl(id2, 0, IPC_RMID) != 0) e(0);
+
+ /*
+ * Finally, test invalid commands. Well, hopefully invalid commands,
+ * anyway.
+ */
+ if (semctl(id, 0, INT_MIN) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, INT_MAX) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+}
+
+/*
+ * Test SEM_UNDO support. Right now this functionality is missing altogether.
+ * For now, we test that any attempt to use SEM_UNDO fails.
+ */
+static void
+test88d(void)
+{
+ struct sembuf sop;
+ int id;
+
+ subtest = 3;
+
+ if ((id = semget(IPC_PRIVATE, 1, 0600)) == -1) e(0);
+
+ /*
+ * Use an all-ones (but positive) flag field. This will include
+ * SEM_UNDO, but also tell the IPC server to report no warning.
+ */
+ if (!(SHRT_MAX & SEM_UNDO)) e(0);
+ sop.sem_num = 0;
+ sop.sem_op = 1;
+ sop.sem_flg = SHRT_MAX;
+ if (semop(id, &sop, 1) != -1) e(0);
+ if (errno != EINVAL) e(0);
+
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+}
+
+enum {
+ RESUME_SEMOP, /* use semop() to resume blocked parties */
+ RESUME_SETVAL, /* use semctl(SETVAL) to resume blocked parties */
+ RESUME_SETALL, /* use semctl(SETALL) to resume blocked parties */
+ NR_RESUMES
+};
+
+enum {
+ MATCH_FIRST, /* first match completes, blocks second match */
+ MATCH_SECOND, /* first match does not complete, second match does */
+ MATCH_KILL, /* second match completes after first is aborted */
+ MATCH_BOTH, /* first and second match both complete */
+ MATCH_CASCADE, /* completed match in turn causes another match */
+ MATCH_ALL, /* a combination of the last two */
+ NR_MATCHES
+};
+
+/*
+ * Auxiliary child procedure. The auxiliary children will deadlock until the
+ * semaphore set is removed.
+ */
+static void
+test88e_childaux(struct link * parent)
+{
+ struct sembuf sops[3];
+ struct seminfo seminfo;
+ int child, id, num;
+
+ child = rcv(parent);
+ id = rcv(parent);
+ num = rcv(parent);
+
+ memset(sops, 0, sizeof(sops));
+
+ /* These operations are guaranteed to never return successfully. */
+ switch (child) {
+ case 1:
+ sops[0].sem_num = num;
+ sops[0].sem_op = 1;
+ sops[1].sem_num = num;
+ sops[1].sem_op = 0;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 1;
+ break;
+ case 2:
+ if (semctl(0, 0, IPC_INFO, &seminfo) == -1) e(0);
+ sops[0].sem_num = num;
+ sops[0].sem_op = -seminfo.semvmx;
+ sops[1].sem_num = num;
+ sops[1].sem_op = -seminfo.semvmx;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 1;
+ break;
+ default:
+ e(0);
+ }
+
+ snd(parent, 0);
+
+ if (semop(id, sops, 3) != -1) e(0);
+ if (errno != EIDRM) e(0);
+}
+
+/*
+ * First child procedure.
+ */
+static void
+test88e_child1(struct link * parent)
+{
+ struct sembuf sops[3];
+ size_t nsops;
+ int match, id, expect;
+
+ match = rcv(parent);
+ id = rcv(parent);
+
+ /* Start off with some defaults, then refine by match type. */
+ memset(sops, 0, sizeof(sops));
+ sops[0].sem_num = 2;
+ sops[0].sem_op = -1;
+ nsops = 2;
+ expect = 0;
+ switch (match) {
+ case MATCH_FIRST:
+ sops[1].sem_num = 3;
+ sops[1].sem_op = 1;
+ break;
+ case MATCH_SECOND:
+ sops[1].sem_num = 3;
+ sops[1].sem_op = -1;
+ sops[2].sem_num = 0;
+ sops[2].sem_op = 1;
+ nsops = 3;
+ expect = -1;
+ break;
+ case MATCH_KILL:
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 1;
+ expect = INT_MIN;
+ break;
+ case MATCH_BOTH:
+ case MATCH_CASCADE:
+ case MATCH_ALL:
+ sops[1].sem_num = 3;
+ sops[1].sem_op = 1;
+ break;
+ default:
+ e(0);
+ }
+
+ snd(parent, 0);
+
+ if (semop(id, sops, nsops) != expect) e(0);
+ if (expect == -1 && errno != EIDRM) e(0);
+}
+
+/*
+ * Second child procedure.
+ */
+static void
+test88e_child2(struct link * parent)
+{
+ struct sembuf sops[2];
+ size_t nsops;
+ int match, id, expect;
+
+ match = rcv(parent);
+ id = rcv(parent);
+
+ /* Start off with some defaults, then refine by match type. */
+ memset(sops, 0, sizeof(sops));
+ sops[0].sem_num = 2;
+ sops[0].sem_op = -1;
+ nsops = 2;
+ expect = 0;
+ switch (match) {
+ case MATCH_FIRST:
+ sops[1].sem_num = 0;
+ sops[1].sem_op = 1;
+ expect = -1;
+ break;
+ case MATCH_SECOND:
+ case MATCH_KILL:
+ nsops = 1;
+ break;
+ case MATCH_BOTH:
+ case MATCH_ALL:
+ sops[1].sem_num = 3;
+ sops[1].sem_op = 1;
+ break;
+ case MATCH_CASCADE:
+ sops[0].sem_num = 3;
+ nsops = 1;
+ break;
+ default:
+ e(0);
+ }
+
+ snd(parent, 0);
+
+ if (semop(id, sops, nsops) != expect) e(0);
+ if (expect == -1 && errno != EIDRM) e(0);
+}
+
+/*
+ * Third child procedure.
+ */
+static void
+test88e_child3(struct link * parent)
+{
+ struct sembuf sops[1];
+ size_t nsops;
+ int match, id;
+
+ match = rcv(parent);
+ id = rcv(parent);
+
+ /* Things are a bit simpler here. */
+ memset(sops, 0, sizeof(sops));
+ nsops = 1;
+ switch (match) {
+ case MATCH_ALL:
+ sops[0].sem_num = 3;
+ sops[0].sem_op = -2;
+ break;
+ default:
+ e(0);
+ }
+
+ snd(parent, 0);
+
+ if (semop(id, sops, nsops) != 0) e(0);
+}
+
+/*
+ * Perform one test for operations affecting multiple processes.
+ */
+static void
+sub88e(unsigned int match, unsigned int resume, unsigned int aux)
+{
+ struct link aux1, aux2, child1, child2, child3;
+ struct sembuf sop;
+ unsigned short val[4];
+ int id, inc, aux_zcnt, aux_ncnt;
+
+ /*
+ * For this test we use one single semaphore set, with four semaphores.
+ * The first semaphore is increased in the case that an operation that
+ * should never complete does complete, and thus should stay zero.
+ * Depending on 'aux', the second or third semaphore is used by the
+ * auxiliary children (if any, also depending on 'aux') to deadlock on.
+ * The third and higher semaphores are used in the main operations.
+ */
+ if ((id = semget(IPC_PRIVATE, __arraycount(val), 0666)) == -1) e(0);
+
+ aux_zcnt = aux_ncnt = 0;
+
+ /* Start the first auxiliary child if desired, before all others. */
+ if (aux & 1) {
+ spawn(&aux1, test88e_childaux, DROP_ALL);
+
+ snd(&aux1, 1);
+ snd(&aux1, id);
+ snd(&aux1, (aux & 4) ? 2 : 1);
+
+ if (rcv(&aux1) != 0) e(0);
+
+ if (aux & 4)
+ aux_zcnt++;
+ }
+
+ /* Start and configure all children for this specific match test. */
+ spawn(&child1, test88e_child1, DROP_ALL);
+
+ snd(&child1, match);
+ snd(&child1, id);
+
+ if (rcv(&child1) != 0) e(0);
+
+ /*
+ * For fairness tests, we must ensure that the first child blocks on
+ * the semaphore before the second child does.
+ */
+ switch (match) {
+ case MATCH_FIRST:
+ case MATCH_SECOND:
+ case MATCH_KILL:
+ usleep(WAIT_USECS);
+ break;
+ }
+
+ spawn(&child2, test88e_child2, DROP_NONE);
+
+ snd(&child2, match);
+ snd(&child2, id);
+
+ if (rcv(&child2) != 0) e(0);
+
+ if (match == MATCH_ALL) {
+ spawn(&child3, test88e_child3, DROP_USER);
+
+ snd(&child3, match);
+ snd(&child3, id);
+
+ if (rcv(&child3) != 0) e(0);
+ }
+
+ /* Start the second auxiliary child if desired, after all others. */
+ if (aux & 2) {
+ spawn(&aux2, test88e_childaux, DROP_NONE);
+
+ snd(&aux2, 2);
+ snd(&aux2, id);
+ snd(&aux2, (aux & 4) ? 2 : 1);
+
+ if (rcv(&aux2) != 0) e(0);
+
+ if (aux & 4)
+ aux_ncnt++;
+ }
+
+ usleep(WAIT_USECS);
+
+ /*
+ * Test semaphore values and determine the value with which to increase
+ * the third semaphore. For MATCH_KILL, also kill the first child.
+ */
+ inc = 1;
+ switch (match) {
+ case MATCH_FIRST:
+ case MATCH_SECOND:
+ TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 0, 0);
+ break;
+ case MATCH_KILL:
+ TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
+
+ terminate(&child1);
+
+ /* As stated before, non-self kills need not be instant. */
+ usleep(WAIT_USECS);
+
+ TEST_SEM(id, 2, 0, 0, 1 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 0, 0);
+ break;
+ case MATCH_BOTH:
+ TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 0, 0);
+ inc = 2;
+ break;
+ case MATCH_CASCADE:
+ TEST_SEM(id, 2, 0, 0, 1 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 1, 0);
+ break;
+ case MATCH_ALL:
+ TEST_SEM(id, 2, 0, 0, 2 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 1, 0);
+ inc = 2;
+ break;
+ default:
+ e(0);
+ }
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, -1, -1);
+
+ /* Resume the appropriate set of children. */
+ switch (resume) {
+ case RESUME_SEMOP:
+ memset(&sop, 0, sizeof(sop));
+ sop.sem_num = 2;
+ sop.sem_op = inc;
+ if (semop(id, &sop, 1) != 0) e(0);
+ break;
+ case RESUME_SETVAL:
+ if (semctl(id, 2, SETVAL, inc) != 0) e(0);
+ break;
+ case RESUME_SETALL:
+ memset(val, 0, sizeof(val));
+ val[2] = inc;
+ if (semctl(id, 0, SETALL, val) != 0) e(0);
+ break;
+ default:
+ e(0);
+ }
+
+ /*
+ * See if the right children were indeed resumed, and retest the
+ * semaphore values.
+ */
+ switch (match) {
+ case MATCH_FIRST:
+ TEST_SEM(id, 2, 0, child1.pid, 1 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 1, child1.pid, 0, 0);
+ collect(&child1);
+ break;
+ case MATCH_SECOND:
+ TEST_SEM(id, 2, 0, child2.pid, 1 + aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 0, 0);
+ collect(&child2);
+ break;
+ case MATCH_KILL:
+ TEST_SEM(id, 2, 0, child2.pid, aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, 0, 0, 0);
+ collect(&child2);
+ break;
+ case MATCH_BOTH:
+ /*
+ * The children are not ordered in this case, so we do not know
+ * which one gets access to the semaphores last.
+ */
+ TEST_SEM(id, 2, 0, -1, aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 2, -1, 0, 0);
+ collect(&child1);
+ collect(&child2);
+ break;
+ case MATCH_CASCADE:
+ TEST_SEM(id, 2, 0, child1.pid, aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, child2.pid, 0, 0);
+ collect(&child1);
+ collect(&child2);
+ break;
+ case MATCH_ALL:
+ TEST_SEM(id, 2, 0, -1, aux_ncnt, aux_zcnt);
+ TEST_SEM(id, 3, 0, child3.pid, 0, 0);
+ collect(&child1);
+ collect(&child2);
+ collect(&child3);
+ break;
+ default:
+ e(0);
+ }
+
+ TEST_SEM(id, 0, 0, 0, 0, 0);
+ TEST_SEM(id, 1, 0, 0, -1, -1);
+
+ /* Remove the semaphore set. This should unblock remaining callers. */
+ if (semctl(id, 0, IPC_RMID) != 0) e(0);
+
+ /* Wait for the children that were not resumed, but should be now. */
+ switch (match) {
+ case MATCH_FIRST:
+ collect(&child2);
+ break;
+ case MATCH_SECOND:
+ collect(&child1);
+ break;
+ case MATCH_KILL:
+ case MATCH_BOTH:
+ case MATCH_CASCADE:
+ case MATCH_ALL:
+ break;
+ default:
+ e(0);
+ }
+
+ /* Wait for the auxiliary children as well. */
+ if (aux & 1)
+ collect(&aux1);
+ if (aux & 2)
+ collect(&aux2);
+}
+
+/*
+ * Test operations affecting multiple processes, ensuring the following points:
+ * 1) an operation resumes all possible waiters; 2) a resumed operation in turn
+ * correctly resumes other now-unblocked operations; 3) a basic level of FIFO
+ * fairness is provided between blocked parties; 4) all the previous points are
+ * unaffected by additional waiters that are not being resumed; 5) identifier
+ * removal properly resumes all affected waiters.
+ */
+static void
+test88e(void)
+{
+ unsigned int resume, match, aux;
+
+ subtest = 4;
+
+ for (match = 0; match < NR_MATCHES; match++)
+ for (resume = 0; resume < NR_RESUMES; resume++)
+ for (aux = 1; aux <= 8; aux++) /* 0 and 4 are equal */
+ sub88e(match, resume, aux);
+}
+
+/*
+ * Initialize the test.
+ */
+static void
+test88_init(void)
+{
+ struct group *gr;
+
+ /* Start with full root privileges. */
+ setuid(geteuid());
+
+ if ((gr = getgrnam(ROOT_GROUP)) == NULL) e(0);
+
+ setgid(gr->gr_gid);
+ setegid(gr->gr_gid);
+
+ page_size = getpagesize();
+ page_ptr = mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ if (page_ptr == MAP_FAILED) e(0);
+ bad_ptr = page_ptr + page_size;
+ if (munmap(bad_ptr, page_size) != 0) e(0);
+}
+
+/*
+ * Test program for SysV IPC semaphores.
+ */
+int
+main(int argc, char ** argv)
+{
+ int i, m;
+
+ start(88);
+
+ test88_init();
+
+ if (argc == 2)
+ m = atoi(argv[1]);
+ else
+ m = 0xFF;
+
+ for (i = 0; i < ITERATIONS; i++) {
+ if (m & 0x01) test88a();
+ if (m & 0x02) test88b();
+ if (m & 0x04) test88c();
+ if (m & 0x08) test88d();
+ if (m & 0x10) test88e();
+ }
+
+ quit();
+}