From: David van Moolenbroek Date: Thu, 17 Dec 2015 11:34:07 +0000 (+0000) Subject: IPC server: major fixes, test set for semaphores X-Git-Url: http://zhaoyanbai.com/repos/jquery-3.1.1.min.js?a=commitdiff_plain;h=refs%2Fchanges%2F62%2F3262%2F3;p=minix.git IPC server: major fixes, test set for semaphores - 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 --- diff --git a/distrib/sets/lists/minix-tests/mi b/distrib/sets/lists/minix-tests/mi index a41fcd7e5..2c1ccb440 100644 --- a/distrib/sets/lists/minix-tests/mi +++ b/distrib/sets/lists/minix-tests/mi @@ -187,6 +187,7 @@ ./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 diff --git a/minix/servers/ipc/inc.h b/minix/servers/ipc/inc.h index dde504393..e84a27d8e 100644 --- a/minix/servers/ipc/inc.h +++ b/minix/servers/ipc/inc.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/minix/servers/ipc/sem.c b/minix/servers/ipc/sem.c index 8e4fc82e8..1ab6c8a8e 100644 --- a/minix/servers/ipc/sem.c +++ b/minix/servers/ipc/sem.c @@ -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); } diff --git a/minix/servers/ipc/shm.c b/minix/servers/ipc/shm.c index 43aa6a8d8..1857714c2 100644 --- a/minix/servers/ipc/shm.c +++ b/minix/servers/ipc/shm.c @@ -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) diff --git a/minix/servers/ipc/utility.c b/minix/servers/ipc/utility.c index aebf4f081..04f7eac19 100644 --- a/minix/servers/ipc/utility.c +++ b/minix/servers/ipc/utility.c @@ -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)); } diff --git a/minix/tests/Makefile b/minix/tests/Makefile index 4e6e5363f..742eab00e 100644 --- a/minix/tests/Makefile +++ b/minix/tests/Makefile @@ -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 diff --git a/minix/tests/run b/minix/tests/run index 37e854146..499df1923 100755 --- a/minix/tests/run +++ b/minix/tests/run @@ -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 index 000000000..921827e07 --- /dev/null +++ b/minix/tests/test88.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); +}