]> Zhao Yanbai Git Server - minix.git/commitdiff
IPC server: major fixes, test set for semaphores 62/3262/3
authorDavid van Moolenbroek <david@minix3.org>
Thu, 17 Dec 2015 11:34:07 +0000 (11:34 +0000)
committerLionel Sambuc <lionel.sambuc@gmail.com>
Sat, 16 Jan 2016 13:04:11 +0000 (14:04 +0100)
- rewrite the semop(2) implementation so that it now conforms to the
  specification, including atomicity, support for blocking more than
  once, range checks, but also basic fairness support;
- fix permissions checking;
- fix missing time adjustments;
- fix off-by-one errors and other bugs;
- do not allocate dynamic memory for GETALL/SETALL;
- add test88, which properly tests the semaphore functionality.

Change-Id: I85f0d3408c0d6bba41cfb4c91a34c8b46b2a5959

distrib/sets/lists/minix-tests/mi
minix/servers/ipc/inc.h
minix/servers/ipc/sem.c
minix/servers/ipc/shm.c
minix/servers/ipc/utility.c
minix/tests/Makefile
minix/tests/run
minix/tests/test88.c [new file with mode: 0644]

index a41fcd7e5e01818e7ed894664bb4c283a9d4542f..2c1ccb4404168d61f28642ab998fc3fe5afcd812 100644 (file)
 ./usr/tests/minix-posix/test85                          minix-tests
 ./usr/tests/minix-posix/test86                          minix-tests
 ./usr/tests/minix-posix/test87                          minix-tests
+./usr/tests/minix-posix/test88                          minix-tests
 ./usr/tests/minix-posix/test9                           minix-tests
 ./usr/tests/minix-posix/testinterp                      minix-tests
 ./usr/tests/minix-posix/testisofs                       minix-tests
index dde5043934b1761b4cbb6675a82345496e7fa773..e84a27d8e0b5571052b87d7eea954777f1835728 100644 (file)
@@ -16,6 +16,7 @@
 #include <sys/shm.h>
 #include <sys/sem.h>
 #include <sys/stat.h>
+#include <sys/queue.h>
 #include <sys/mman.h>
 #include <machine/param.h>
 #include <machine/vm.h>
index 8e4fc82e8aeadf44ae9099c63b9bc556d1caf316..1ab6c8a8e6f9bcb7ed472c8cad033e9bd4c40af8 100644 (file)
@@ -1,35 +1,59 @@
 #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;
@@ -40,6 +64,10 @@ sem_find_key(key_t key)
        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)
 {
@@ -58,6 +86,9 @@ sem_find_id(int id)
        return sem;
 }
 
+/*
+ * Implementation of the semget(2) system call.
+ */
 int
 do_semget(message * m)
 {
@@ -70,7 +101,7 @@ 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))
@@ -79,9 +110,9 @@ do_semget(message * m)
                        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. */
@@ -105,6 +136,7 @@ do_semget(message * m)
                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)
@@ -115,6 +147,54 @@ do_semget(message * m)
        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)
 {
@@ -126,31 +206,52 @@ 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. */
@@ -165,158 +266,159 @@ remove_semaphore(struct sem_struct * sem)
                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;
@@ -326,6 +428,12 @@ do_semctl(message * m)
        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:
@@ -345,12 +453,33 @@ do_semctl(message * m)
        }
 
        /*
-        * 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;
        }
 
@@ -365,10 +494,6 @@ do_semctl(message * m)
                            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;
@@ -380,15 +505,11 @@ do_semctl(message * m)
                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:
@@ -429,16 +550,13 @@ do_semctl(message * m)
                        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)
@@ -461,36 +579,26 @@ do_semctl(message * m)
                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)
@@ -501,7 +609,7 @@ do_semctl(message * m)
 #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;
@@ -510,16 +618,19 @@ do_semctl(message * m)
        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;
@@ -527,14 +638,14 @@ do_semop(message * m)
        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);
@@ -546,90 +657,101 @@ do_semop(message * m)
                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)
 {
@@ -638,17 +760,31 @@ 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);
 }
index 43aa6a8d8af7049c9825b8bd5925fb1e9a07ade4..1857714c2b7fc24934d99741f530938d44f120cb 100644 (file)
@@ -129,7 +129,7 @@ do_shmget(message * m)
 int
 do_shmat(message * m)
 {
-       int id, flag;
+       int id, flag, mask;
        vir_bytes addr;
        void *ret;
        struct shm_struct *shm;
@@ -148,11 +148,12 @@ do_shmat(message * m)
        if ((shm = shm_find_id(id)) == NULL)
                return EINVAL;
 
+       mask = 0;
        if (flag & SHM_RDONLY)
-               flag = 0444;
+               mask = IPC_R;
        else
-               flag = 0666;
-       if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, flag))
+               mask = IPC_R | IPC_W;
+       if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, mask))
                return EACCES;
 
        ret = vm_remap(m->m_source, sef_self(), (void *)addr,
@@ -285,7 +286,7 @@ do_shmctl(message * m)
        case IPC_STAT:
        case SHM_STAT:
                /* Check whether the caller has read permission. */
-               if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, 0444))
+               if (!check_perm(&shm->shmid_ds.shm_perm, m->m_source, IPC_R))
                        return EACCES;
                if ((r = sys_datacopy(SELF, (vir_bytes)&shm->shmid_ds,
                    m->m_source, buf, sizeof(shm->shmid_ds))) != OK)
index aebf4f08129b037506e67f2879dbfe57ea56cc21..04f7eac19cd4ef7ab29395e5a65fe57fcd8857be 100644 (file)
@@ -4,31 +4,29 @@ int
 check_perm(struct ipc_perm * req, endpoint_t who, int mode)
 {
        int req_mode;
-       int cur_mode;
        uid_t uid;
        gid_t gid;
 
        uid = getnuid(who);
        gid = getngid(who);
-       mode &= 0666;
+       mode &= 0700;
 
        /* Root is allowed to do anything. */
        if (uid == 0)
-               return 1;
+               return TRUE;
 
        if (uid == req->uid || uid == req->cuid) {
                /* Same user. */
-               req_mode = (req->mode >> 6) & 0x7;
-               cur_mode = (mode >> 6) & 0x7;
+               req_mode = req->mode & 0700;
        } else if (gid == req->gid || gid == req->cgid) {
                /* Same group. */
-               req_mode = (req->mode >> 3) & 0x7;
-               cur_mode = (mode >> 3) & 0x7;
+               req_mode = req->mode & 0070;
+               mode >>= 3;
        } else {
                /* Other user and group. */
-               req_mode = req->mode & 0x7;
-               cur_mode = mode & 0x7;
+               req_mode = req->mode & 0007;
+               mode >>= 6;
        }
 
-       return (cur_mode && ((cur_mode & req_mode) == cur_mode));
+       return (mode && ((mode & req_mode) == mode));
 }
index 4e6e5363fd91df9345bccce011d7ae091f325f61..742eab00e5313438043cf22e4ae066e618a6f0d9 100644 (file)
@@ -59,7 +59,7 @@ MINIX_TESTS= \
 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
 41 42 43 44 45 46    48 49 50    52 53 54 55 56    58 59 60 \
 61       64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \
-81 82 83 84 85 86 87
+81 82 83 84 85 86 87 88
 
 FILES += t84_h_nonexec.sh
 
index 37e8541461f1055027ab45ec74e71087bace0840..499df19237b35a765db849141d5e1605a236cecb 100755 (executable)
@@ -22,7 +22,7 @@ export USENETWORK             # set to "yes" for test48+82 to use the network
 
 # Programs that require setuid
 setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \
-        test69 test73 test74 test78 test83 test85 test87"
+        test69 test73 test74 test78 test83 test85 test87 test88"
 # Scripts that require to be run as root
 rootscripts="testisofs testvnd testrelpol"
 
@@ -30,7 +30,7 @@ alltests="1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 \
          21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
          41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 \
          61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 \
-         81 82 83 84 85 86 87 sh1 sh2 interp mfs isofs vnd"
+         81 82 83 84 85 86 87 88 sh1 sh2 interp mfs isofs vnd"
 tests_no=`expr 0`
 
 # If root, make sure the setuid tests have the correct permissions
diff --git a/minix/tests/test88.c b/minix/tests/test88.c
new file mode 100644 (file)
index 0000000..921827e
--- /dev/null
@@ -0,0 +1,2819 @@
+/* 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();
+}