/* @(#)ipc_sem.c 1.1 94/10/31 SMI; from S5R3.1 10.10 */ /* Copyright (c) 1984 AT&T */ /* All Rights Reserved */ /* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ /* The copyright notice above does not evidence any */ /* actual or intended publication of such source code. */ /* * Inter-Process Communication Semaphore Facility. */ #include #include #include #include #include #include #include #include #include struct sem_undo *semunp; /* ptr to head of undo chain */ struct sem_undo *semfup; /* ptr to head of free undo chain */ struct semid_ds *semconv(); struct ipc_perm *ipcget(); /* * semaoe - Create or update adjust on exit entry. */ semaoe(val, id, num) short val; /* operation value to be adjusted on exit */ int id; /* semid */ short num; /* semaphore # */ { register struct undo *uup, /* ptr to entry to update */ *uup2; /* ptr to move entry */ register struct sem_undo *up, /* ptr to process undo struct */ *up2; /* ptr to undo list */ register int i, /* loop control */ found; /* matching entry found flag */ if (val == 0) return (0); if (val > seminfo.semaem || val < -seminfo.semaem) { u.u_error = ERANGE; return (1); } /* * If this process doesn't yet have any undo structures, * get a free one and initialize it */ if ((up = sem_undo[u.u_procp - proc]) == NULL) if (up = semfup) { semfup = up->un_np; up->un_np = NULL; sem_undo[u.u_procp - proc] = up; } else { u.u_error = ENOSPC; return (1); } /* * Check for an existing undo structure for this semaphore */ for (uup = up->un_ent, found = i = 0; i < up->un_cnt; i++) { if (uup->un_id < id || (uup->un_id == id && uup->un_num < num)) { uup++; continue; } if (uup->un_id == id && uup->un_num == num) found = 1; break; } /* * If this is a new undo structure, link it into the active undo list * Then look for the undo entry insertion point, keeping the entries * sorted by id; if necessary, shuffle remaining entries up */ if (!found) { if (up->un_cnt >= seminfo.semume) { u.u_error = EINVAL; return (1); } if (up->un_cnt == 0) { up->un_np = semunp; semunp = up; } uup2 = &up->un_ent[up->un_cnt++]; while (uup2-- > uup) *(uup2 + 1) = *uup2; /* Insert a new undo entry, then done */ uup->un_id = id; uup->un_num = num; uup->un_aoe = -val; return (0); } /* * If an entry already existed for this semaphore, adjust it */ uup->un_aoe -= val; if (uup->un_aoe > seminfo.semaem || uup->un_aoe < -seminfo.semaem) { u.u_error = ERANGE; uup->un_aoe += val; return (1); } /* * If new adjust-on-exit value is zero, remove this undo entry * from the list. If new undo entry count is zero, remove this * undo structure from the active undo list; however, retain this * structure for the current process until semexit() */ if (uup->un_aoe == 0) { uup2 = &up->un_ent[--(up->un_cnt)]; while (uup++ < uup2) *(uup - 1) = *uup; if (up->un_cnt == 0) { /* Remove process from undo list. */ if (semunp == up) semunp = up->un_np; else for (up2 = semunp; up2 != NULL; up2 = up2->un_np) if (up2->un_np == up) { up2->un_np = up->un_np; break; } up->un_np = NULL; } } return (0); } /* * semconv - Convert user supplied semid into a ptr to the associated * semaphore header. */ struct semid_ds * semconv(s) register int s; /* semid */ { register struct semid_ds *sp; /* ptr to associated header */ if (s < 0) { u.u_error = EINVAL; return (NULL); } sp = &sema[s % seminfo.semmni]; if ((sp->sem_perm.mode & IPC_ALLOC) == 0 || s / seminfo.semmni != sp->sem_perm.seq) { u.u_error = EINVAL; return (NULL); } return (sp); } /* * semctl - Semctl system call. */ semctl() { register struct a { int semid; uint semnum; int cmd; int arg; } *uap = (struct a *)u.u_ap; register struct semid_ds *sp; /* ptr to semaphore header */ register struct sem *p; /* ptr to semaphore */ register int i; /* loop control */ if ((sp = semconv(uap->semid)) == NULL) return; u.u_rval1 = 0; switch (uap->cmd) { /* Remove semaphore set. */ case IPC_RMID: if (u.u_uid != sp->sem_perm.uid && u.u_uid != sp->sem_perm.cuid && !suser()) break; semunrm(uap->semid, 0, sp->sem_nsems); for (i = sp->sem_nsems, p = sp->sem_base; i--; p++) { p->semval = p->sempid = 0; if (p->semncnt) { wakeup((caddr_t)&p->semncnt); p->semncnt = 0; } if (p->semzcnt) { wakeup((caddr_t)&p->semzcnt); p->semzcnt = 0; } } rmfree(semmap, (long)sp->sem_nsems, (u_long)((sp->sem_base - sem) + 1)); if (uap->semid + seminfo.semmni < 0) sp->sem_perm.seq = 0; else sp->sem_perm.seq++; sp->sem_perm.mode = 0; return; /* Set ownership and permissions. */ case IPC_SET: { struct semid_ds id; if (u.u_uid != sp->sem_perm.uid && u.u_uid != sp->sem_perm.cuid && !suser()) break; if (u.u_error = copyin((caddr_t)uap->arg, (caddr_t)&id, sizeof (id))) { break; } sp->sem_perm.uid = id.sem_perm.uid; sp->sem_perm.gid = id.sem_perm.gid; sp->sem_perm.mode = id.sem_perm.mode & 0777 | IPC_ALLOC; sp->sem_ctime = (time_t)time.tv_sec; break; } /* Get semaphore data structure. */ case IPC_STAT: if (ipcaccess(&sp->sem_perm, SEM_R)) break; if (u.u_error = copyout((caddr_t)sp, (caddr_t)uap->arg, sizeof (*sp))) break; break; /* Get # of processes sleeping for greater semval. */ case GETNCNT: if (ipcaccess(&sp->sem_perm, SEM_R)) break; if (uap->semnum >= sp->sem_nsems) { u.u_error = EINVAL; break; } u.u_rval1 = (sp->sem_base + uap->semnum)->semncnt; break; /* Get pid of last process to operate on semaphore. */ case GETPID: if (ipcaccess(&sp->sem_perm, SEM_R)) break; if (uap->semnum >= sp->sem_nsems) { u.u_error = EINVAL; break; } u.u_rval1 = (sp->sem_base + uap->semnum)->sempid; break; /* Get semval of one semaphore. */ case GETVAL: if (ipcaccess(&sp->sem_perm, SEM_R)) break; if (uap->semnum >= sp->sem_nsems) { u.u_error = EINVAL; break; } u.u_rval1 = (sp->sem_base + uap->semnum)->semval; break; /* Get all semvals in set. */ case GETALL: { register unsigned short *vals; unsigned vsize; if (ipcaccess(&sp->sem_perm, SEM_R)) break; /* allocate space to hold all semaphore values */ vsize = sp->sem_nsems * sizeof (short); vals = (unsigned short *) new_kmem_alloc(vsize, KMEM_SLEEP); for (i = 0, p = sp->sem_base; i < sp->sem_nsems; p++) vals[i++] = p->semval; u.u_error = copyout((caddr_t)vals, (caddr_t)uap->arg, vsize); kmem_free((caddr_t)vals, vsize); break; } /* Get # of processes sleeping for semval to become zero. */ case GETZCNT: if (ipcaccess(&sp->sem_perm, SEM_R)) break; if (uap->semnum >= sp->sem_nsems) { u.u_error = EINVAL; break; } u.u_rval1 = (sp->sem_base + uap->semnum)->semzcnt; break; /* Set semval of one semaphore. */ case SETVAL: if (ipcaccess(&sp->sem_perm, SEM_A)) break; if (uap->semnum >= sp->sem_nsems) { u.u_error = EINVAL; break; } if ((unsigned)uap->arg > seminfo.semvmx) { u.u_error = ERANGE; break; } if ((p = sp->sem_base + uap->semnum)->semval = uap->arg) { if (p->semncnt) { p->semncnt = 0; wakeup((caddr_t)&p->semncnt); } } else if (p->semzcnt) { p->semzcnt = 0; wakeup((caddr_t)&p->semzcnt); } p->sempid = u.u_procp->p_pid; semunrm(uap->semid, uap->semnum, uap->semnum); break; /* Set semvals of all semaphores in set. */ case SETALL: { register unsigned short *vals; unsigned vsize; if (ipcaccess(&sp->sem_perm, SEM_A)) break; /* allocate space to hold all semaphore values */ vsize = sp->sem_nsems * sizeof (short); vals = (unsigned short *) new_kmem_alloc(vsize, KMEM_SLEEP); if (u.u_error = copyin((caddr_t)uap->arg, (caddr_t)vals, vsize)) goto seterr; for (i = 0; i < sp->sem_nsems; ) if (vals[i++] > seminfo.semvmx) { u.u_error = ERANGE; goto seterr; } semunrm(uap->semid, 0, sp->sem_nsems); for (i = 0, p = sp->sem_base; i < sp->sem_nsems; (p++)->sempid = u.u_procp->p_pid) { if (p->semval = vals[i++]) { if (p->semncnt) { p->semncnt = 0; wakeup((caddr_t)&p->semncnt); } } else if (p->semzcnt) { p->semzcnt = 0; wakeup((caddr_t)&p->semzcnt); } } seterr: kmem_free((caddr_t)vals, vsize); break; } default: u.u_error = EINVAL; break; } } /* * semexit - Called by exit(kern_exit.c) to clean up on process exit. */ semexit() { register struct sem_undo *up, /* process undo struct ptr */ *p; /* undo struct ptr */ register struct semid_ds *sp; /* semid being undone ptr */ register int i; /* loop control */ register long v; /* adjusted value */ register struct sem *semp; /* semaphore ptr */ if ((up = sem_undo[u.u_procp - proc]) == NULL) return; if (up->un_cnt == 0) goto cleanup; for (i = up->un_cnt; i--; ) { if ((sp = semconv(up->un_ent[i].un_id)) == NULL) continue; v = (long)(semp = sp->sem_base + up->un_ent[i].un_num)->semval + up->un_ent[i].un_aoe; if (v < 0 || v > seminfo.semvmx) continue; semp->semval = v; if (v == 0 && semp->semzcnt) { semp->semzcnt = 0; wakeup((caddr_t)&semp->semzcnt); } if (up->un_ent[i].un_aoe > 0 && semp->semncnt) { semp->semncnt = 0; wakeup((caddr_t)&semp->semncnt); } } up->un_cnt = 0; if (semunp == up) semunp = up->un_np; else for (p = semunp; p != NULL; p = p->un_np) if (p->un_np == up) { p->un_np = up->un_np; break; } cleanup: sem_undo[u.u_procp - proc] = NULL; up->un_np = semfup; semfup = up; } /* * semget - Semget system call. */ semget() { register struct a { key_t key; int nsems; int semflg; } *uap = (struct a *)u.u_ap; register struct semid_ds *sp; /* semaphore header ptr */ register unsigned int i; /* temp */ int s; /* ipcget status return */ if ((sp = (struct semid_ds *) ipcget(uap->key, uap->semflg, (struct ipc_perm *)sema, seminfo.semmni, sizeof (*sp), &s)) == NULL) return; if (s) { /* This is a new semaphore set. Finish initialization. */ if (uap->nsems <= 0 || uap->nsems > seminfo.semmsl) { u.u_error = EINVAL; sp->sem_perm.mode = 0; return; } if ((i = rmalloc(semmap, (long)uap->nsems)) == NULL) { u.u_error = ENOSPC; sp->sem_perm.mode = 0; return; } /* point to first semaphore */ sp->sem_base = sem + (i - 1); sp->sem_nsems = uap->nsems; sp->sem_ctime = (time_t)time.tv_sec; sp->sem_otime = (time_t)0; } else if (uap->nsems && sp->sem_nsems < uap->nsems) { u.u_error = EINVAL; return; } /* sem_perm.seq incremented by semctl() when semaphore group released */ u.u_rval1 = sp->sem_perm.seq * seminfo.semmni + (sp - sema); } /* * seminit - Called by main(init_main.c) to initialize the semaphore map. */ seminit() { register i; /* initialize the semaphore map with semmns entries */ rminit(semmap, (long)seminfo.semmns, (long)1, "semmap", seminfo.semmap); /* link all free undo structures together */ semfup = (struct sem_undo *) semu; for (i = 0; i < seminfo.semmnu - 1; i++) { semfup->un_np = (struct sem_undo*)((uint)semfup+seminfo.semusz); semfup = semfup->un_np; } semfup->un_np = NULL; semfup = (struct sem_undo *)semu; } /* * semop - Semop system call. */ semop() { register struct a { int semid; struct sembuf *sops; uint nsops; } *uap = (struct a *)u.u_ap; register struct sembuf *op; /* ptr to operation */ register int i; /* loop control */ register struct semid_ds *sp; /* ptr to associated header */ register struct sem *semp; /* ptr to semaphore */ int again; struct sembuf *uops; /* ptr to copy of user ops */ if ((sp = semconv(uap->semid)) == NULL) return; if (uap->nsops > seminfo.semopm) { u.u_error = E2BIG; return; } /* allocate space to hold all semaphore operations */ uops = (struct sembuf *) new_kmem_alloc( uap->nsops * sizeof (*uops), KMEM_SLEEP); if (u.u_error = copyin((caddr_t)uap->sops, (caddr_t)uops, sizeof (*uops) * uap->nsops)) goto semoperr; /* Verify that sem #s are in range and permissions are granted. */ for (i = 0, op = uops; i++ < uap->nsops; op++) { if (ipcaccess(&sp->sem_perm, (unsigned)(op->sem_op ? SEM_A : SEM_R))) goto semoperr; if ((unsigned)op->sem_num >= sp->sem_nsems) { u.u_error = EFBIG; goto semoperr; } } again = 0; check: /* * Loop waiting for the operations to be satisified atomically. * Actually, do the operations and undo them if a wait is needed * or an error is detected. */ if (again) { /* Verify that the semaphores haven't been removed. */ if (semconv(uap->semid) == NULL) { u.u_error = EIDRM; goto semoperr; } } again = 1; for (i = 0, op = uops; i < uap->nsops; i++, op++) { semp = sp->sem_base + op->sem_num; if (op->sem_op > 0) { if (op->sem_op + (long)semp->semval > seminfo.semvmx || (op->sem_flg & SEM_UNDO && semaoe(op->sem_op, uap->semid, op->sem_num))) { if (u.u_error == 0) u.u_error = ERANGE; if (i) semundo(uops, i, uap->semid, sp); goto semoperr; } semp->semval += op->sem_op; if (semp->semncnt) { semp->semncnt = 0; wakeup((caddr_t)&semp->semncnt); } if (semp->semval == 0 && semp->semzcnt) { semp->semzcnt = 0; wakeup((caddr_t)&semp->semzcnt); } continue; } if (op->sem_op < 0) { if (semp->semval >= -op->sem_op) { if (op->sem_flg & SEM_UNDO && semaoe(op->sem_op, uap->semid, op->sem_num)) { if (i) semundo(uops, i, uap->semid, sp); goto semoperr; } semp->semval += op->sem_op; if (semp->semval == 0 && semp->semzcnt) { semp->semzcnt = 0; wakeup((caddr_t)&semp->semzcnt); } continue; } if (i) semundo(uops, i, uap->semid, sp); if (op->sem_flg & IPC_NOWAIT) { u.u_error = EAGAIN; goto semoperr; } semp->semncnt++; if (sleep((caddr_t)&semp->semncnt, PCATCH | PSEMN)) { if ((semp->semncnt)-- <= 1) { semp->semncnt = 0; wakeup((caddr_t)&semp->semncnt); } u.u_error = EINTR; goto semoperr; } goto check; } if (semp->semval) { if (i) semundo(uops, i, uap->semid, sp); if (op->sem_flg & IPC_NOWAIT) { u.u_error = EAGAIN; goto semoperr; } semp->semzcnt++; if (sleep((caddr_t)&semp->semzcnt, PCATCH | PSEMZ)) { if ((semp->semzcnt)-- <= 1) { semp->semzcnt = 0; wakeup((caddr_t)&semp->semzcnt); } u.u_error = EINTR; goto semoperr; } goto check; } } /* All operations succeeded. Update sempid for accessed semaphores. */ for (i = 0, op = uops; i++ < uap->nsops; (sp->sem_base + (op++)->sem_num)->sempid = u.u_procp->p_pid); sp->sem_otime = (time_t) time.tv_sec; semoperr: /* Before leaving, deallocate the buffer that held the user semops */ kmem_free((caddr_t)uops, (sizeof (*uops) * uap->nsops)); } /* * semsys - System entry point for semctl, semget, and semop system calls. * * Assumes SEMCTL/SEMGET/SEMOP definitions in * ...src/lib/libc/sys/common/semsys.c */ semsys() { int semctl(), semget(), semop(); static int (*calls[])() = {semctl, semget, semop}; register struct a { uint id; /* function code id */ } *uap = (struct a *)u.u_ap; if (uap->id > 2) { u.u_error = EINVAL; return; } u.u_ap = &u.u_arg[1]; (*calls[uap->id])(); } /* * semundo - Undo work done up to finding an operation that can't be done. */ semundo(op, n, id, sp) register struct sembuf *op; /* first operation that was done ptr */ register int n, /* # of operations that were done */ id; /* semaphore id */ register struct semid_ds *sp; /* semaphore data structure ptr */ { register struct sem *semp; /* semaphore ptr */ for (op += n - 1; n--; op--) { if (op->sem_op == 0) continue; semp = sp->sem_base + op->sem_num; semp->semval -= op->sem_op; if (op->sem_flg & SEM_UNDO) (void) semaoe(-op->sem_op, id, op->sem_num); } } /* * semunrm - Undo entry remover. * * This routine is called to clear all undo entries for a set of semaphores * that are being removed from the system or are being reset by SETVAL or * SETVALS commands to semctl. */ semunrm(id, low, high) int id; /* semid */ ushort low, /* lowest semaphore being changed */ high; /* highest semaphore being changed */ { register struct sem_undo *pp, /* ptr to predecessor to p */ *p; /* ptr to current entry */ register struct undo *up; /* ptr to undo entry */ register int i, /* loop control */ j; /* loop control */ pp = NULL; p = semunp; while (p != NULL) { /* Search through current structure for matching entries. */ for (up = p->un_ent, i = 0; i < p->un_cnt; ) { if (id < up->un_id) break; if (id > up->un_id || low > up->un_num) { up++; i++; continue; } if (high < up->un_num) break; /* squeeze out one entry */ for (j = i; ++j < p->un_cnt; p->un_ent[j - 1] = p->un_ent[j]) ; p->un_cnt--; } /* Reset pointers for next round. */ if (p->un_cnt == 0) { /* Remove from linked list. */ if (pp == NULL) { semunp = p->un_np; p->un_np = NULL; p = semunp; } else { pp->un_np = p->un_np; p->un_np = NULL; p = pp->un_np; } } else { pp = p; p = p->un_np; } } }