diff options
Diffstat (limited to 'util/lock/csem.c')
-rw-r--r-- | util/lock/csem.c | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/util/lock/csem.c b/util/lock/csem.c new file mode 100644 index 0000000000..a84cbbb2b1 --- /dev/null +++ b/util/lock/csem.c @@ -0,0 +1,279 @@ +/* + * Copyright 2003 Sun Microsystems, Inc. + * Copyright 2010 Google, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Developer's note: This was open sourced by Sun Microsystems, which got it + * via Cobalt Networks. It has been fairly extensively modified since then. + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/sem.h> +#include <sys/stat.h> +#include <time.h> +#include <errno.h> +#include <sched.h> + +#include "csem.h" + +#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +/* union semun is defined by including <sys/sem.h> */ +#else +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; /* value for SETVAL */ + struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ + unsigned short int *array; /* array for GETALL, SETALL */ + struct seminfo *__buf; /* buffer for IPC_INFO */ +}; +#endif + +/* + * On some platforms semctl(SETVAL) sets sem_otime, on other platforms it + * does not. Figure out what this platform does. + * + * Returns 0 if semctl(SETVAL) does not set sem_otime + * Returns 1 if semctl(SETVAL) does set sem_otime + * Returns -1 on error + */ +static int does_semctl_set_otime(void) +{ + int sem_id; + int ret; + + /* create a test semaphore */ + sem_id = semget(IPC_PRIVATE, 1, S_IRUSR|S_IWUSR); + if (sem_id < 0) { + return -1; + } + + /* set the value */ + if (csem_setval(sem_id, 1) < 0) { + csem_destroy(sem_id); + return -1; + } + + /* read sem_otime */ + ret = (csem_get_otime(sem_id) > 0) ? 1 : 0; + + /* clean up */ + csem_destroy(sem_id); + + return ret; +} + +int csem_create(key_t key, unsigned val) +{ + static int need_otime_hack = -1; + int sem_id; + + /* see if we need to trigger a semop to set sem_otime */ + if (need_otime_hack < 0) { + int ret = does_semctl_set_otime(); + if (ret < 0) { + return -1; + } + need_otime_hack = !ret; + } + + /* create it or fail */ + sem_id = semget(key, 1, IPC_CREAT|IPC_EXCL | S_IRUSR|S_IWUSR); + if (sem_id < 0) { + return -1; + } + + /* initalize the value */ + if (need_otime_hack) { + val++; + } + if (csem_setval(sem_id, val) < 0) { + csem_destroy(sem_id); + return -1; + } + + if (need_otime_hack) { + /* force sem_otime to change */ + csem_down(sem_id); + } + + return sem_id; +} + +/* how many times to loop, waiting for sem_otime */ +#define MAX_OTIME_LOOPS 1000 + +int csem_get(key_t key) +{ + int sem_id; + int i; + + /* CSEM_PRIVATE needs to go through csem_create() to get an + * initial value */ + if (key == CSEM_PRIVATE) { + errno = EINVAL; + return -1; + } + + /* get the (assumed existing) semaphore */ + sem_id = semget(key, 1, S_IRUSR|S_IWUSR); + if (sem_id < 0) { + return -1; + } + + /* loop until sem_otime != 0, which means it has been initialized */ + for (i = 0; i < MAX_OTIME_LOOPS; i++) { + time_t otime = csem_get_otime(sem_id); + if (otime < 0) { + /* error */ + return -1; + } + if (otime > 0) { + /* success */ + return sem_id; + } + /* retry */ + sched_yield(); + } + + /* fell through - error */ + return -1; +} + +int csem_get_or_create(key_t key, unsigned val) +{ + int sem_id; + + /* try to create the semaphore */ + sem_id = csem_create(key, val); + if (sem_id >= 0 || errno != EEXIST) { + /* it either succeeded or got an error */ + return sem_id; + } + + /* it must exist already - get it */ + sem_id = csem_get(key); + if (sem_id < 0) { + return -1; + } + + return sem_id; +} + +int csem_destroy(int sem_id) +{ + return semctl(sem_id, 0, IPC_RMID); +} + +int csem_getval(int sem_id) +{ + return semctl(sem_id, 0, GETVAL); +} + +int csem_setval(int sem_id, unsigned val) +{ + union semun arg; + arg.val = val; + if (semctl(sem_id, 0, SETVAL, arg) < 0) { + return -1; + } + return 0; +} + +static int csem_up_undoflag(int sem_id, int undoflag) +{ + struct sembuf sops; + sops.sem_num = 0; + sops.sem_op = 1; + sops.sem_flg = undoflag; + return semop(sem_id, &sops, 1); +} + +int csem_up(int sem_id) +{ + return csem_up_undoflag(sem_id, 0); +} + +int csem_up_undo(int sem_id) +{ + return csem_up_undoflag(sem_id, SEM_UNDO); +} + +static int csem_down_undoflag(int sem_id, int undoflag) +{ + struct sembuf sops; + sops.sem_num = 0; + sops.sem_op = -1; + sops.sem_flg = undoflag; + return semop(sem_id, &sops, 1); +} + +int csem_down(int sem_id) +{ + return csem_down_undoflag(sem_id, 0); +} + +int csem_down_undo(int sem_id) +{ + return csem_down_undoflag(sem_id, SEM_UNDO); +} + +static int csem_down_timeout_undoflag(int sem_id, + struct timespec *timeout, + int undoflag) +{ + struct sembuf sops; + sops.sem_num = 0; + sops.sem_op = -1; + sops.sem_flg = undoflag; + return semtimedop(sem_id, &sops, 1, timeout); +} + +int csem_down_timeout(int sem_id, struct timespec *timeout) +{ + return csem_down_timeout_undoflag(sem_id, timeout, 0); +} + +int csem_down_timeout_undo(int sem_id, struct timespec *timeout) +{ + return csem_down_timeout_undoflag(sem_id, timeout, SEM_UNDO); +} + +time_t csem_get_otime(int sem_id) +{ + union semun arg; + struct semid_ds ds; + arg.buf = &ds; + if (semctl(sem_id, 0, IPC_STAT, arg) < 0) { + return -1; + } + return ds.sem_otime; +} |