/* $FreeBSD: src/sys/kern/sysv_sem.c,v 1.69 2004/03/17 09:37:13 cperciva Exp $ */ /* * Implementation of SVID semaphores * * Author: Daniel Boulet * Copyright (c) 2013 Larisa Grigore * * This software is provided ``AS IS'' without any warranties of any kind. */ #include "namespace.h" #include #include #include #include #include #include #include #include #include #include #include #include "un-namespace.h" #include "sysvipc_lock.h" #include "sysvipc_ipc.h" #include "sysvipc_shm.h" #include "sysvipc_sem.h" #include "sysvipc_hash.h" #define SYSV_MUTEX_LOCK(x) if (__isthreaded) _pthread_mutex_lock(x) #define SYSV_MUTEX_UNLOCK(x) if (__isthreaded) _pthread_mutex_unlock(x) #define SYSV_MUTEX_DESTROY(x) if (__isthreaded) _pthread_mutex_destroy(x) extern struct hashtable *shmaddrs; extern struct hashtable *shmres; extern pthread_mutex_t lock_resources; struct sem_undo *undos = NULL; pthread_mutex_t lock_undo = PTHREAD_MUTEX_INITIALIZER; static int semundo_clear(int, int); static int put_shmdata(int id) { struct shm_data *data; int ret = -1; SYSV_MUTEX_LOCK(&lock_resources); data = _hash_lookup(shmres, id); if (!data) { sysv_print_err("something wrong put_shmdata\n"); goto done; /* It should not reach here. */ } data->used--; if (data->used == 0 && data->removed) { sysv_print("really remove the sem\n"); SYSV_MUTEX_UNLOCK(&lock_resources); /* OBS: Even if the shmctl fails (the thread doesn't * have IPC_M permissions), all structures associated * with it will be removed in the current process.*/ sysvipc_shmdt(data->internal); semundo_clear(id, -1); if (data->removed == SEG_ALREADY_REMOVED) return 1; /* The semaphore was removed by another process so there is nothing else we must do. */ /* Else inform the daemon that the segment is removed. */ return (sysvipc_shmctl(id, IPC_RMID, NULL)); } ret = 0; done: SYSV_MUTEX_UNLOCK(&lock_resources); return (ret); } static struct semid_pool * get_semaptr(int semid, int to_remove, int shm_access) { struct semid_pool *semaptr; struct shm_data *shmdata = get_shmdata(semid, to_remove, shm_access); if (!shmdata) { /* Error is set in get_shmdata. */ return (NULL); } semaptr = (struct semid_pool *)shmdata->internal; if (!semaptr) { put_shmdata(semid); errno = EINVAL; return (NULL); } return (semaptr); } static int sema_exist(int semid, struct semid_pool *semaptr) { /* Was it removed? */ if (semaptr->gen == -1 || semaptr->ds.sem_perm.seq != IPCID_TO_SEQ(semid)) return (0); return (1); } /* This is the function called when a the semaphore * is descovered as removed. It marks the process * internal data and munmap the */ static void mark_for_removal(int shmid) { sysv_print("Mark that the segment was removed\n"); get_shmdata(shmid, SEG_ALREADY_REMOVED, 0); /* Setting SEG_ALREADY_REMOVED parameter, when put_shmdata * is called, the internal resources will be freed. */ /* Decrement the "usage" field. */ put_shmdata(shmid); } static int try_rwlock_rdlock(int semid, struct semid_pool *semaptr) { sysv_print(" before rd lock id = %d %p\n", semid, semaptr); #ifdef SYSV_RWLOCK sysv_rwlock_rdlock(&semaptr->rwlock); sysv_print("rd lock id = %d\n", semid); #else sysv_mutex_lock(&semaptr->mutex); sysv_print("lock id = %d\n", semid); #endif if (!sema_exist(semid, semaptr)) { errno = EINVAL; sysv_print("error sema %d doesn't exist\n", semid); #ifdef SYSV_RWLOCK sysv_rwlock_unlock(&semaptr->rwlock); #else sysv_mutex_unlock(&semaptr->mutex); #endif /* Internal resources must be freed. */ mark_for_removal(semid); return (-1); } return (0); } static int try_rwlock_wrlock(int semid, struct semid_pool *semaptr) { #ifdef SYSV_RWLOCK sysv_print("before wrlock id = %d %p\n", semid, semaptr); sysv_rwlock_wrlock(&semaptr->rwlock); #else sysv_print("before lock id = %d %x\n", semid, semaptr); sysv_mutex_lock(&semaptr->mutex); #endif sysv_print("lock id = %d\n", semid); if (!sema_exist(semid, semaptr)) { errno = EINVAL; sysv_print("error sema %d doesn't exist\n", semid); #ifdef SYSV_RWLOCK sysv_rwlock_unlock(&semaptr->rwlock); #else sysv_mutex_unlock(&semaptr->mutex); #endif /* Internal resources must be freed. */ mark_for_removal(semid); return (-1); } return (0); } static int rwlock_unlock(int semid, struct semid_pool *semaptr) { sysv_print("unlock id = %d %p\n", semid, semaptr); if (!sema_exist(semid, semaptr)) { /* Internal resources must be freed. */ mark_for_removal(semid); errno = EINVAL; return (-1); } #ifdef SYSV_RWLOCK sysv_rwlock_unlock(&semaptr->rwlock); #else sysv_mutex_unlock(&semaptr->mutex); #endif return (0); } int sysvipc_semget(key_t key, int nsems, int semflg) { int semid; void *shmaddr; //int shm_access; int size = sizeof(struct semid_pool) + nsems * sizeof(struct sem); //TODO resources limits sysv_print("handle semget\n"); semid = _shmget(key, size, semflg, SEMGET); if (semid == -1) { /* errno already set. */ goto done; } /* If the semaphore is in process of being removed there are two cases: * - the daemon knows that and it will handle this situation. * - one of the threads from this address space remove it and the daemon * wasn't announced yet; in this scenario, the semaphore is marked * using "removed" field of shm_data and future calls will return * EIDRM error. */ #if 0 /* Set access type. */ shm_access = semflg & (IPC_W | IPC_R); if(set_shmdata_access(semid, shm_access) != 0) { /* errno already set. */ goto done; } #endif shmaddr = sysvipc_shmat(semid, NULL, 0); if (!shmaddr) { semid = -1; sysvipc_shmctl(semid, IPC_RMID, NULL); goto done; } //TODO more semaphores in a single file done: sysv_print("end handle semget %d\n", semid); return (semid); } static int semundo_clear(int semid, int semnum) { struct undo *sunptr; int i; sysv_print("semundo clear\n"); SYSV_MUTEX_LOCK(&lock_undo); if (!undos) goto done; sunptr = &undos->un_ent[0]; i = 0; while (i < undos->un_cnt) { if (sunptr->un_id == semid) { if (semnum == -1 || sunptr->un_num == semnum) { undos->un_cnt--; if (i < undos->un_cnt) { undos->un_ent[i] = undos->un_ent[undos->un_cnt]; continue; } } if (semnum != -1) break; } ++i; ++sunptr; } //TODO Shrink memory if case; not sure if necessary done: SYSV_MUTEX_UNLOCK(&lock_undo); sysv_print("end semundo clear\n"); return (0); } int sysvipc___semctl(int semid, int semnum, int cmd, union semun *arg) { int i, error; struct semid_pool *semaptr = NULL; struct sem *semptr = NULL; struct shmid_ds shmds; int shm_access = 0; /*if (!jail_sysvipc_allowed && cred->cr_prison != NULL) return (ENOSYS); */ sysv_print("semctl cmd = %d\n", cmd); error = 0; switch (cmd) { case IPC_SET: /* Originally was IPC_M but this is checked by daemon. */ case SETVAL: case SETALL: shm_access = IPC_W; break; case IPC_STAT: case GETNCNT: case GETPID: case GETVAL: case GETALL: case GETZCNT: shm_access = IPC_R; break; default: break; } semaptr = get_semaptr(semid, cmd==IPC_RMID, shm_access); if (!semaptr) { /* errno already set. */ return (-1); } switch (cmd) { case IPC_RMID: /* Mark that the segment is removed. This is done in * get_semaptr call in order to announce other processes. * It will be actually removed after put_shmdata call and * not other thread from this address space use shm_data * structure. */ break; case IPC_SET: if (!arg->buf) { error = EFAULT; break; } memset(&shmds, 0, sizeof(shmds)/sizeof(unsigned char)); memcpy(&shmds.shm_perm, &arg->buf->sem_perm, sizeof(struct ipc_perm)); error = sysvipc_shmctl(semid, cmd, &shmds); /* OBS: didn't update ctime and mode as in kernel implementation * it is done. Those fields are already updated for shmid_ds * struct when calling shmctl */ break; case IPC_STAT: if (!arg->buf) { error = EFAULT; break; } error = sysvipc_shmctl(semid, cmd, &shmds); if (error) break; memcpy(&arg->buf->sem_perm, &shmds.shm_perm, sizeof(struct ipc_perm)); arg->buf->sem_nsems = (shmds.shm_segsz - sizeof(struct semid_pool)) / sizeof(struct sem); arg->buf->sem_ctime = shmds.shm_ctime; /* otime is semaphore specific so read it from * semaptr */ error = try_rwlock_rdlock(semid, semaptr); if (error) break; arg->buf->sem_otime = semaptr->ds.sem_otime; rwlock_unlock(semid, semaptr); break; case GETNCNT: if (semnum < 0 || semnum >= semaptr->ds.sem_nsems) { errno = EINVAL; break; } error = try_rwlock_rdlock(semid, semaptr); if (error) break; error = semaptr->ds.sem_base[semnum].semncnt; rwlock_unlock(semid, semaptr); break; case GETPID: if (semnum < 0 || semnum >= semaptr->ds.sem_nsems) { errno = EINVAL; break; } error = try_rwlock_rdlock(semid, semaptr); if (error) break; error = semaptr->ds.sem_base[semnum].sempid; rwlock_unlock(semid, semaptr); break; case GETVAL: if (semnum < 0 || semnum >= semaptr->ds.sem_nsems) { errno = EINVAL; break; } error = try_rwlock_rdlock(semid, semaptr); if (error) break; error = semaptr->ds.sem_base[semnum].semval; rwlock_unlock(semid, semaptr); break; case GETALL: if (!arg->array) { error = EFAULT; break; } error = try_rwlock_rdlock(semid, semaptr); if (error) break; for (i = 0; i < semaptr->ds.sem_nsems; i++) { arg->array[i] = semaptr->ds.sem_base[i].semval; } rwlock_unlock(semid, semaptr); break; case GETZCNT: if (semnum < 0 || semnum >= semaptr->ds.sem_nsems) { errno = EINVAL; break; } error = try_rwlock_rdlock(semid, semaptr); if (error) break; error = semaptr->ds.sem_base[semnum].semzcnt; rwlock_unlock(semid, semaptr); break; case SETVAL: if (semnum < 0 || semnum >= semaptr->ds.sem_nsems) { errno = EINVAL; break; } error = try_rwlock_wrlock(semid, semaptr); if (error) break; semptr = &semaptr->ds.sem_base[semnum]; semptr->semval = arg->val; semundo_clear(semid, semnum); if (semptr->semzcnt || semptr->semncnt) umtx_wakeup((int *)&semptr->semval, 0); rwlock_unlock(semid, semaptr); break; case SETALL: if (!arg->array) { error = EFAULT; break; } error = try_rwlock_wrlock(semid, semaptr); if (error) break; for (i = 0; i < semaptr->ds.sem_nsems; i++) { semptr = &semaptr->ds.sem_base[i]; semptr->semval = arg->array[i]; if (semptr->semzcnt || semptr->semncnt) umtx_wakeup((int *)&semptr->semval, 0); } semundo_clear(semid, -1); rwlock_unlock(semid, semaptr); break; default: errno = EINVAL; break; } put_shmdata(semid); sysv_print("end semctl\n"); return (error); } /* * Adjust a particular entry for a particular proc */ static int semundo_adjust(int semid, int semnum, int adjval) { struct undo *sunptr; int i; int error = 0; size_t size; int undoid; void *addr; struct shm_data *data; sysv_print("semundo adjust\n"); if (!adjval) goto done; SYSV_MUTEX_LOCK(&lock_undo); if (!undos) { sysv_print("get undo segment\n"); undoid = _shmget(IPC_PRIVATE, PAGE_SIZE, IPC_CREAT | IPC_EXCL | 0600, UNDOGET); if (undoid == -1) { sysv_print_err("no undo segment\n"); return (-1); } addr = sysvipc_shmat(undoid, NULL, 0); if (!addr) { sysv_print_err("can not map undo segment\n"); sysvipc_shmctl(undoid, IPC_RMID, NULL); return (-1); } undos = (struct sem_undo *)addr; undos->un_pages = 1; undos->un_cnt = 0; } /* * Look for the requested entry and adjust it (delete if adjval becomes * 0). */ sunptr = &undos->un_ent[0]; for (i = 0; i < undos->un_cnt; i++, sunptr++) { if (sunptr->un_id != semid && sunptr->un_num != semnum) continue; sunptr->un_adjval += adjval; if (sunptr->un_adjval == 0) { undos->un_cnt--; if (i < undos->un_cnt) undos->un_ent[i] = undos->un_ent[undos->un_cnt]; } goto done; } /* Didn't find the right entry - create it */ size = sizeof(struct sem_undo) + (undos->un_cnt + 1) * sizeof(struct sem_undo); if (size > (unsigned int)(undos->un_pages * PAGE_SIZE)) { sysv_print("need more undo space\n"); sysvipc_shmdt(undos); undos->un_pages++; SYSV_MUTEX_LOCK(&lock_resources); data = _hash_lookup(shmaddrs, (u_long)undos); SYSV_MUTEX_UNLOCK(&lock_resources); /* It is not necessary any lock on "size" because it is used * only by shmat and shmdt. * shmat for undoid is called only from this function and it * is protected by undo_lock. * shmdt for undoid is not called anywhere because the segment * is destroyed by the daemon when the client dies. */ data->size = undos->un_pages * PAGE_SIZE; undos = sysvipc_shmat(data->shmid, NULL, 0); } sunptr = &undos->un_ent[undos->un_cnt]; undos->un_cnt++; sunptr->un_adjval = adjval; sunptr->un_id = semid; sunptr->un_num = semnum; //if (suptr->un_cnt == seminfo.semume) TODO move it in daemon /*} else { error = EINVAL; //se face prin notificare }*/ done: SYSV_MUTEX_UNLOCK(&lock_undo); sysv_print("semundo adjust end\n"); return (error); } int sysvipc_semop(int semid, struct sembuf *sops, unsigned nsops) { struct semid_pool *semaptr = NULL, *auxsemaptr = NULL; struct sembuf *sopptr; struct sem *semptr = NULL; struct sem *xsemptr = NULL; int eval = 0; int i, j; int do_undos; int val_to_sleep; sysv_print("[client %d] call to semop(%d, %u)\n", getpid(), semid, nsops); //TODO /*if (!jail_sysvipc_allowed && td->td_ucred->cr_prison != NULL) return (ENOSYS); */ semaptr = get_semaptr(semid, 0, IPC_W); if (!semaptr) { errno = EINVAL; return (-1); } #ifdef SYSV_SEMS if (try_rwlock_rdlock(semid, semaptr) == -1) { #else if (try_rwlock_wrlock(semid, semaptr) == -1) { #endif sysv_print("sema removed\n"); errno = EIDRM; goto done2; } if (nsops > MAX_SOPS) { sysv_print("too many sops (max=%d, nsops=%u)\n", MAX_SOPS, nsops); eval = E2BIG; goto done; } /* * Loop trying to satisfy the vector of requests. * If we reach a point where we must wait, any requests already * performed are rolled back and we go to sleep until some other * process wakes us up. At this point, we start all over again. * * This ensures that from the perspective of other tasks, a set * of requests is atomic (never partially satisfied). */ do_undos = 0; for (;;) { semptr = NULL; for (i = 0; i < (int)nsops; i++) { sopptr = &sops[i]; if (sopptr->sem_num >= semaptr->ds.sem_nsems) { eval = EFBIG; goto done; } semptr = &semaptr->ds.sem_base[sopptr->sem_num]; #ifdef SYSV_SEMS sysv_mutex_lock(&semptr->sem_mutex); #endif sysv_print("semop: sem[%d]=%d : op=%d, flag=%s\n", sopptr->sem_num, semptr->semval, sopptr->sem_op, (sopptr->sem_flg & IPC_NOWAIT) ? "nowait" : "wait"); if (sopptr->sem_op < 0) { if (semptr->semval + sopptr->sem_op < 0) { sysv_print("semop: can't do it now\n"); break; } else { semptr->semval += sopptr->sem_op; if (semptr->semval == 0 && semptr->semzcnt > 0) umtx_wakeup((int *)&semptr->semval, 0); } if (sopptr->sem_flg & SEM_UNDO) do_undos = 1; } else if (sopptr->sem_op == 0) { if (semptr->semval > 0) { sysv_print("semop: not zero now\n"); break; } } else { semptr->semval += sopptr->sem_op; if (sopptr->sem_flg & SEM_UNDO) do_undos = 1; if (semptr->semncnt > 0) umtx_wakeup((int *)&semptr->semval, 0); } #ifdef SYSV_SEMS sysv_mutex_unlock(&semptr->sem_mutex); #endif } /* * Did we get through the entire vector? */ if (i >= (int)nsops) goto donex; if (sopptr->sem_op == 0) semptr->semzcnt++; else semptr->semncnt++; /* * Get interlock value before rleeasing sem_mutex. * * XXX horrible hack until we get a umtx_sleep16() (and a umtx_sleep64()) * system call. */ val_to_sleep = *(int *)&semptr->semval; #ifdef SYSV_SEMS sysv_mutex_unlock(&semptr->sem_mutex); #endif /* * Rollback the semaphores we had acquired. */ sysv_print("semop: rollback 0 through %d\n", i-1); for (j = 0; j < i; j++) { xsemptr = &semaptr->ds.sem_base[sops[j].sem_num]; #ifdef SYSV_SEMS sysv_mutex_lock(&xsemptr->sem_mutex); #endif xsemptr->semval -= sops[j].sem_op; if (xsemptr->semval == 0 && xsemptr->semzcnt > 0) umtx_wakeup((int *)&xsemptr->semval, 0); if (xsemptr->semval <= 0 && xsemptr->semncnt > 0) umtx_wakeup((int *)&xsemptr->semval, 0); //?! #ifdef SYSV_SEMS sysv_mutex_unlock(&xsemptr->sem_mutex); #endif } /* * If the request that we couldn't satisfy has the * NOWAIT flag set then return with EAGAIN. */ if (sopptr->sem_flg & IPC_NOWAIT) { eval = EAGAIN; goto done; } /* * Release semaptr->lock while sleeping, allowing other * semops (like SETVAL, SETALL, etc), which require an * exclusive lock and might wake us up. * * Reload and recheck the validity of semaptr on return. * Note that semptr itself might have changed too, but * we've already interlocked for semptr and that is what * will be woken up if it wakes up the tsleep on a MP * race. * */ sysv_print("semop: good night!\n"); rwlock_unlock(semid, semaptr); put_shmdata(semid); /* We don't sleep more than SYSV_TIMEOUT because we could * go to sleep after another process calls wakeup and remain * blocked. */ eval = umtx_sleep((int *)&semptr->semval, val_to_sleep, SYSV_TIMEOUT); /* return code is checked below, after sem[nz]cnt-- */ /* * Make sure that the semaphore still exists */ /* Check if another thread didn't remove the semaphore. */ auxsemaptr = get_semaptr(semid, 0, IPC_W); /* Redundant access check. */ if (!auxsemaptr) { errno = EIDRM; return (-1); } if (auxsemaptr != semaptr) { errno = EIDRM; goto done; } /* Check if another process didn't remove the semaphore. */ #ifdef SYSV_SEMS if (try_rwlock_rdlock(semid, semaptr) == -1) { #else if (try_rwlock_wrlock(semid, semaptr) == -1) { #endif errno = EIDRM; goto done; } sysv_print("semop: good morning (eval=%d)!\n", eval); /* The semaphore is still alive. Readjust the count of * waiting processes. */ semptr = &semaptr->ds.sem_base[sopptr->sem_num]; #ifdef SYSV_SEMS sysv_mutex_lock(&semptr->sem_mutex); #endif if (sopptr->sem_op == 0) semptr->semzcnt--; else semptr->semncnt--; #ifdef SYSV_SEMS sysv_mutex_unlock(&semptr->sem_mutex); #endif /* * Is it really morning, or was our sleep interrupted? * (Delayed check of tsleep() return code because we * need to decrement sem[nz]cnt either way.) * * Always retry on EBUSY */ if (eval == EAGAIN) { eval = EINTR; goto done; } sysv_print("semop: good morning!\n"); /* RETRY LOOP */ } donex: /* * Process any SEM_UNDO requests. */ if (do_undos) { for (i = 0; i < (int)nsops; i++) { /* * We only need to deal with SEM_UNDO's for non-zero * op's. */ int adjval; if ((sops[i].sem_flg & SEM_UNDO) == 0) continue; adjval = sops[i].sem_op; if (adjval == 0) continue; eval = semundo_adjust(semid, sops[i].sem_num, -adjval); if (eval == 0) continue; /* * Oh-Oh! We ran out of either sem_undo's or undo's. * Rollback the adjustments to this point and then * rollback the semaphore ups and down so we can return * with an error with all structures restored. We * rollback the undo's in the exact reverse order that * we applied them. This guarantees that we won't run * out of space as we roll things back out. */ for (j = i - 1; j >= 0; j--) { if ((sops[j].sem_flg & SEM_UNDO) == 0) continue; adjval = sops[j].sem_op; if (adjval == 0) continue; if (semundo_adjust(semid, sops[j].sem_num, adjval) != 0) sysv_print("semop - can't undo undos"); } for (j = 0; j < (int)nsops; j++) { xsemptr = &semaptr->ds.sem_base[ sops[j].sem_num]; #ifdef SYSV_SEMS sysv_mutex_lock(&semptr->sem_mutex); #endif xsemptr->semval -= sops[j].sem_op; if (xsemptr->semval == 0 && xsemptr->semzcnt > 0) umtx_wakeup((int *)&xsemptr->semval, 0); if (xsemptr->semval <= 0 && xsemptr->semncnt > 0) umtx_wakeup((int *)&xsemptr->semval, 0); //?! #ifdef SYSV_SEMS sysv_mutex_unlock(&semptr->sem_mutex); #endif } sysv_print("eval = %d from semundo_adjust\n", eval); goto done; } } /* Set sempid field for each semaphore. */ for (i = 0; i < (int)nsops; i++) { sopptr = &sops[i]; semptr = &semaptr->ds.sem_base[sopptr->sem_num]; #ifdef SYSV_SEMS sysv_mutex_lock(&semptr->sem_mutex); #endif semptr->sempid = getpid(); #ifdef SYSV_SEMS sysv_mutex_unlock(&semptr->sem_mutex); #endif } sysv_print("semop: done\n"); semaptr->ds.sem_otime = time(NULL); done: rwlock_unlock(semid, semaptr); done2: put_shmdata(semid); return (eval); }