From: Mark Wooding Date: Sun, 6 Mar 2016 22:25:44 +0000 (+0000) Subject: sema: New program for hacking with semaphores. X-Git-Tag: 1.4.2~6 X-Git-Url: https://git.distorted.org.uk/~mdw/misc/commitdiff_plain/300a556d392d9bd6ac5b98c1b4b2d0a052459053 sema: New program for hacking with semaphores. This one needs some surprising support features. * C++ can find out what the maximum `time_t' value is, even though C can't. * Introduce a magic `compiler_fence' function, to force things into memory while we synchronize hackily with a signal handler. --- diff --git a/Makefile.am b/Makefile.am index 79da5b4..2521b3d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -110,6 +110,17 @@ gorp_LDADD = $(catacomb_LIBS) dist_man_MANS += gorp.1 endif +## sema +if HAVE_MLIB +bin_PROGRAMS += sema +sema_SOURCES = sema.c +sema_SOURCES += fence.c fence.h +sema_SOURCES += timemax.cc timemax.h +sema_LDADD = $(mLib_LIBS) +sema_LINK = $(LINK) # don't need C++ libraries here +dist_man_MANS += sema.1 +endif + ## cdb tools if HAVE_LIBCDB bin_PROGRAMS += cdb-probe cdb-check-domain diff --git a/configure.ac b/configure.ac index bf1b725..4724381 100644 --- a/configure.ac +++ b/configure.ac @@ -49,6 +49,8 @@ dnl C programming environment. ## Compiler. AC_PROG_CC AX_CFLAGS_WARN_ALL +AC_PROG_CXX +AX_CXXFLAGS_WARN_ALL ## Libraries. OLIBS=$LIBS diff --git a/debian/control b/debian/control index 3e234a7..428958e 100644 --- a/debian/control +++ b/debian/control @@ -180,6 +180,16 @@ Description: Identify and fix problematic whitespace in text files. It can safely update files in place, and could therefore be used as part of a commit hook. +Package: sema +Architecture: any +Depends: ${shlibs:Depends} +Section: utils +Description: Perform simple operations on SysV semaphores. + The sema program performs simple operations on (System V-style) semaphores. + It's not intended to be a utility for general-purpose hacking on existing + semaphores, but rather a tool for doing synchronization between shell + scripts or other simple programs. + Package: x86-model Architecture: any-i386 any-amd64 Depends: ${shlibs:Depends} diff --git a/debian/inst b/debian/inst index f1c7b02..0169cd9 100644 --- a/debian/inst +++ b/debian/inst @@ -44,6 +44,7 @@ stamp.1 stamp /usr/share/man/man1 unfwd distorted-mail /usr/bin unfwd.1 distorted-mail /usr/share/man/man1 xtitle xtitle /usr/bin +xtitle.1 xtitle /usr/share/man/man1 z zz /usr/bin z.1 zz /usr/share/man/man1 x86-model x86-model /usr/bin @@ -54,3 +55,5 @@ with-umask with-umask /usr/bin with-umask.1 with-umask /usr/share/man/man1 sshsvc-mkauthkeys sshsvc-mkauthkeys /usr/bin sshsvc-mkauthkeys.1 sshsvc-mkauthkeys /usr/share/man/man1 +sema sema /usr/bin +sema.1 sema /usr/share/man/man1 diff --git a/fence.c b/fence.c new file mode 100644 index 0000000..c57abf6 --- /dev/null +++ b/fence.c @@ -0,0 +1,45 @@ +/* -*-c-*- + * + * Bodge to control memory access ordering + * + * (c) 2016 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "fence.h" + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @compiler_fence@ --- * + * + * Arguments: some pointers to things + * + * Returns: Nothing. + * + * Use: Actually doesn't do anything at all, but in a separate + * translation unit so that the compiler can't tell that. So it + * won't be able to move memory accesses to the addressed + * objects across the call. + */ + +void compiler_fence(void *p, ...) { } + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/fence.h b/fence.h new file mode 100644 index 0000000..a78b9f7 --- /dev/null +++ b/fence.h @@ -0,0 +1,59 @@ +/* -*-c-*- + * + * Bodge to control memory access ordering + * + * (c) 2016 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef FENCE_H +#define FENCE_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @compiler_fence@ --- * + * + * Arguments: some pointers to things + * + * Returns: Nothing. + * + * Use: Actually doesn't do anything at all, but in a separate + * translation unit so that the compiler can't tell that. So it + * won't be able to move memory accesses to the addressed + * objects across the call. + */ + +#define FENCE_END ((void *)0) +EXECL_LIKE(0) void compiler_fence(void */*p*/, ...); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/sema.1 b/sema.1 new file mode 100644 index 0000000..3c81b23 --- /dev/null +++ b/sema.1 @@ -0,0 +1,370 @@ +.TH "sema" 1 "6 March 2016" "Mark Wooding" "Toys" +.SH NAME +sema \- operations on (SysV-style) semaphores +. +.SH SYNOPSIS +.B sema +.RB [ \-w +.IR when ] +.I subcmd +.RI [ args ...] +.PP +Subcommands: +'RS +.br +.B mkfile +.RB [ \-x ] +.RB [ \-m +.IR mode ] +.I name +.br +.B new +.RB [ \-x ] +.RB [ \-m +.IR mode ] +.I name +.I value +.br +.B rm +.RB [ \-f ] +.I name +.br +.B get +.I name +.br +.B set +.I name +.I value +.br +.B post +.RB [ \-n +.IR count ] +.I name +.br +.B wait +.RB [ \-n +.IR count ] +.I name +.RI [ cmd +.IR args ...] +.RE +. +.SH DESCRIPTION +The +.B sema +program performs simple operations +on (System V-style) semaphores. +It's not intended to be a utility for +general-purpose hacking on +existing semaphores, +but rather a tool for doing synchronization +between shell scripts or other simple programs. +The two biggest limitations of +.B sema +are that it only ever acts on the first semaphore of a set, +and it has no way to change the project-id +(see +.BR ftok (3)). +.SS Common features +Semaphores are identified by a +.IR name . +Currently, this is always a pathname, +though additional ways of designating semaphore sets +may be added in later versions, +so an open-ended syntax is used: +a +.I name +has the form +.IP +.RI [ type \c +.BR : ] \c +.I value +.PP +Currently defined +.IR type s +and the required +.IR value s +are as follows. +.TP +.B file +The +.I value +is a pathname to a file in the filesystem. +The file's inode number and other information +are converted to an IPC key using +.BR ftok (3) +which is used to fetch a semaphore id using +.BR semget (2). +.PP +The +.I type +may be omitted if it is +.B file +and the +.I value +does not contain a +.RB ` : ' +before its first +.RB ` / ' +(if any); +as a result, +most plain pathnames can be used directly. +.PP +The command-line options accepted before the subcommand name +are as follows. +.TP +.B "\-h, \-\-help" +Write a help message to standard output +and exit successfully. +.TP +.B "\-v, \-\-version" +Write a version number to standard output +and exit successfully. +.TP +.BI "\-w, \-\-wait " duration +Nearly all operations on SysV semaphores may need to block +for an extended period of time. +This is obvious for waiting, but, alas, +semaphore initialization is poorly designed, +and +.I every +operation which expects a properly initialized semaphore +has little choice but to wait until the semaphore has been set up. +.IP +If +.I duration +is +.B forever +(the default) +then +.B sema +will wait indefinitely until the thing it's waiting for happens. +If +.I duration +is +.B none +or +.B never +then +.B sema +will exit immediately, +with status 251, +instead of waiting. +Otherwise, +.I +duration +should be a +(possibly fractional \(en i.e., with a decimal point) +number followed by an optional unit suffix: +.RB ` s ' +for seconds (the default), +.RB ` m ' +for minutes, +.RB ` h ' +for hours, and +.RB ` d ' +for days. +.SS Exit status +.IP 0 +Success. +.IP 251 +The semaphore did not become available +within the period +.B sema +was told to wait. +.IP 252 +The semaphore was not properly initialized +within the period +.B sema +was told to wait. +.IP 253 +An error was detected in +.BR sema 's +command-line arguments. +.IP 254 +A system error occurred. +.IP Other +Exit status from the program executed by the +.B wait +subcommand. +.PP +If +.B sema +detects an error, +it writes a human-readable description of the problem +to standard error and exits with one of the above status codes. +The codes have been chosen so as not to conflict +with those commonly used by other programs, +so that errors from +.B sema +itself can be distinguished from problems encountered +by the program executed by the +.B wait +subcommand, +but the available space for error codes is small +and conflicts are inevitable. +. +.SH SUBCOMMANDS +.SS mkfile +Create the file designated by the semaphore +.I name +argument. +(An error is reported if the +.I name +does not designate a pathname.) +.PP +Options are as follows. +.TP +.BI "\-m, \-\-mode " mode +Set the permissions for the new file. +The +.I mode +should be a numeric file permission specification, in octal, +as for +.BR chmod (1). +The default is to allow read and write according to the process umask. +.TP +.B "\-x, \-\-exclusive" +Fail if the file already exists. +.SS new +Create a new semaphore set containing a single semaphore +with the given +.I name +and initial +.IR value . +Options are as follows. +.TP +.BI "\-m, \-\-mode " mode +Set the permissions for the new file. +The +.I mode +should be a numeric file permission specification, in octal; +the read and write bits determine whether the owner, group and others +can read and change the semaphore; +the execute bits are ignored. +The default is to allow read and write according to the process umask. +.TP +.B "\-x, \-\-exclusive" +Fail if a semaphore set with the given +.I name +already exists. +.SS rm +Delete the semaphore set with the given +.IR name . +.PP +Options are as follows. +.TP +.B "\-f, \-\-force" +Don't report an error if no semaphore set exists with the given name. +.SS get +Fetch the current value of the semaphore with the given +.I name +and +write it, in decimal, to standard outout, followed by a newline. +.PP +Because this captures a snapshot of the state +of an asynchronously changing value, +this command is only really useful for diagnostic purposes +or when the system is known to be quiescent. +.PP +There are no options. +.SS set +Set the value of the semaphore with the given +.I name +to +.IR value . +.PP +Because this modifies a the state +of an asynchronously changing value, +this command is only really useful +when the system is known to be quiescent. +.PP +There are no options. +.SS post +Atomically increment the value of the semaphore with the given +.IR name . +This operation can't block +(though it may still be necessary to wait +until the semaphore set is initialized). +.PP +Options are as follows. +.TP +.BI "\-n, \-\-count " count +Adjust the semaphore value by +.IR count , +which must be a positive integer, +rather than 1. +.SS wait +Wait until the value of the semaphore with the given +.I name +is positive and then atomically decrement it. +.PP +If a +.I command +is provided +(possibly with some +.IR arguments ) +then execute it and then increment the semaphore value again +when it finishes. +The program is executed directly by +.BR execvp (3). +Restoring the semaphore value is reliable: +it is done by the kernel, +so there's no risk of some program crashing and +leaving the semaphore in an inconsistent state; +though obviously if the program gets stuck +it will continue to hold the semaphore until it's killed. +The semaphore is released as soon as the +.I command +exits; +if it forked child processed, +the semaphore will be released +and the children will continue to run. +.PP +Options are as follows. +.TP +.BI "\-n, \-\-count " count +Adjust the semaphore value by +.IR count , +which must be a positive integer, +rather than 1. +.B sema +will wait until semaphore value is at least +.IR count , +and atomically decrease it by +.IR count . +If there is a +.I command +then +.B sema +arranges for the semaphore value to be increased by +.I count +when it exits. +. +.SH BUGS +System V semaphores are remarkably awful. +POSIX semaphores are superficially much better, +but actually deficient in a number of ways. +Most significantly for our purposes, +there's no analogue of the +.B SEM_UNDO +feature, +so to implement the feature of +.B wait +which holds the semaphore during the execution of a command +.B sema +would have to wait for it to finish; +and if +.B sema +is killed in the meantime then nobody will fix the semaphore. +Another important deficiency is that +POSIX semaphores can only be adjusted a single step at a time, +so the +.B \-n +feature of the +.B wait +and +.B post +commands can't be implemented satisfactorily. +.SH AUTHOR +Mark Wooding, diff --git a/sema.c b/sema.c new file mode 100644 index 0000000..e150bcd --- /dev/null +++ b/sema.c @@ -0,0 +1,878 @@ +/* -*-c-*- + * + * Operations on SysV semaphores + * + * (c) 2016 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "fence.h" +#include "timemax.h" + +/* Oh, for pity's sake why did nobody do this for us? */ +union semun { + int val; + struct semid_ds *buf; + unsigned short *array; +}; + +/*----- Random utilities --------------------------------------------------*/ + +static int parse_int(const char *what, const char *p, + int radix, int min, int max) +{ + char *q; + int oerr; + long i = -1; + + oerr = errno; + if (isspace((unsigned char)*p)) errno = EINVAL; + else i = strtol(p, &q, radix); + if (*q || errno || !(min <= i && i <= max)) + die(253, "invalid %s `%s'", what, p); + errno = oerr; + return ((int)i); +} + +/*----- Timeout handling --------------------------------------------------*/ + +enum { NEVER, UNTIL, FOREVER }; + +struct timeout { + int wait; + struct timeval tv; +}; + +static struct timeval now; + +static void update_now(void) { int rc = gettimeofday(&now, 0); assert(!rc); } + +static void f2tv(double t, struct timeval *tv) + { tv->tv_sec = t; tv->tv_usec = 1e6*(t - tv->tv_sec); } + +static void parse_timeout(const char *p, struct timeout *t) +{ + double x = 0; + char *q; + int oerr; + time_t t_max = timemax(); + struct timeval tv; + + if (strcmp(p, "forever") == 0) + t->wait = FOREVER; + else if (strcmp(p, "none") == 0 || strcmp(p, "never") == 0) + t->wait = NEVER; + else { + oerr = errno; errno = 0; + if (isspace((unsigned char)*p)) errno = EINVAL; + else x = strtod(p, &q); + if (x < 0) errno = ERANGE; + if (!errno) { + switch (tolower((unsigned char )*q)) { + case 'd': x *= 24; + case 'h': x *= 60; + case 'm': x *= 60; + case 's': q++; break; + } + if (*q) errno = EINVAL; + } + if (errno) die(253, "invalid timeout specification `%s'", p); + if (x >= t_max - now.tv_sec) t->wait = FOREVER; + else if (!x) t->wait = NEVER; + else { t->wait = UNTIL; f2tv(x, &tv); TV_ADD(&t->tv, &now, &tv); } + errno = oerr; + } +} + +/*----- Semaphore operations ----------------------------------------------*/ + +#define SEMA_PROJ 0xd1 /* fair die roll */ + +enum { NTY_PATH }; +struct nameinfo { + unsigned ty; + union { + const char *path; + } u; +}; + +static void parse_name(const char *name, struct nameinfo *ni) +{ + size_t plen = strcspn(name, ":/"); + switch (name[plen]) { + case 0: case '/': ni->ty = NTY_PATH; ni->u.path = name; break; + case ':': + if (strncmp(name, "file:", 5) == 0) + { ni->ty = NTY_PATH; ni->u.path = name + plen + 1; } + else + die(253, "unknown name type `%.*s'", (int)plen, name); + break; + } +} + +static int sema_initialized_p(int semid) +{ + union semun u; + struct semid_ds sem; + + u.buf = &sem; + if (semctl(semid, 0, IPC_STAT, u) < 0) { + die(254, "failed to check that semaphore set is initialized: %s", + strerror(errno)); + } + return (!!sem.sem_otime); +} + +static void make_sema_file(const char *name, int of, mode_t mode) +{ + int fd; + struct nameinfo ni; + + parse_name(name, &ni); + if (ni.ty != NTY_PATH) + die(253, "semaphore name `%s' doesn't designate a path", name); + + if ((fd = open(ni.u.path, O_CREAT | of, mode)) < 0) { + die(254, "failed to create semaphore file `%s': %s", + ni.u.path, strerror(errno)); + } + close(fd); +} + +#define OF_PROBE 1u +#define OF_UNINIT 2u + +static int open_sema(const char *name, unsigned f, int of, + mode_t mode, int ival, + const struct timeout *t) +{ + int ff = mode & 0777, rc, nrace = 5; + int semid; + union semun u; + struct sembuf buf[1]; + double w, ww; + struct nameinfo ni; + struct timeval tv, ttv; + key_t k; + + /* Turn the name into an IPC key. */ + parse_name(name, &ni); + assert(ni.ty == NTY_PATH); + if ((k = ftok(ni.u.path, SEMA_PROJ)) == (key_t)-1) { + die(254, "failed to get key from semaphore file `%s': %s", + ni.u.path, strerror(errno)); + } + + for (;;) { + /* Oh, horrors. A newly created semaphore set is uninitialized. But if + * we set IPC_CREAT without IPC_EXCL then we don't have any idea whether + * the semaphore set was created or not. So we have this little dance to + * do. + */ + if ((of & (O_CREAT | O_EXCL)) != (O_CREAT | O_EXCL)) { + semid = semget(k, 1, ff); + if (semid >= 0) goto await; + else if (errno != ENOENT) goto fail; + else if (ff & O_CREAT) /* try to create -- below */; + else if (f & OF_PROBE) return (-1); + else goto fail; + } + + /* So, here, we have O_CREAT set, and either the semaphore set doesn't + * seem to exist yet, or O_EXCL is set and we didn't bother checking yet. + * So now we try to create the set. If that fails with something other + * than EEXIST, or we were trying with O_EXCL, then we're done. + * Otherwise it's just appeared out of nowhere, so let's briefly try + * racing with whoever else it is, but give up if it doesn't look like + * we're likely to win. + */ + if ((semid = semget(k, 1, ff | IPC_CREAT | IPC_EXCL)) >= 0) break; + else if ((ff & O_EXCL) || errno != EEXIST || nrace--) goto fail; + } + + /* Right, we just created the semaphore set. Now we have to initialize it, + * because nobody did that for us. Set the initial value through semop to + * set the sem_otime stamp as an indicator that the set is properly + * initialized. + */ + u.val = ival ? 0 : 1; + buf[0].sem_num = 0; + buf[0].sem_op = ival ? ival : -1; + buf[0].sem_flg = IPC_NOWAIT; + if (semctl(semid, 0, SETVAL, u) < 0 || + semop(semid, buf, 1) < 0) + die(254, "failed to initialize semaphore set: %s", strerror(errno)); + + /* Whew. */ + return (semid); + + /* Make sure that the semaphore set is actually initialized before we + * continue. + */ +await: + if ((f & OF_UNINIT) || sema_initialized_p(semid)) return (semid); + if (t->wait == NEVER) goto notready; + w = 1e-4; + update_now(); + for (;;) { + ww = w/2 + w*rand()/RAND_MAX; f2tv(ww, &tv); TV_ADD(&tv, &tv, &now); + if (t->wait == UNTIL) { + if (TV_CMP(&now, >=, &t->tv)) goto notready; + if (TV_CMP(&tv, >, &t->tv)) tv = t->tv; + } + for (;;) { + TV_SUB(&ttv, &tv, &now); + rc = select(0, 0, 0, 0, &ttv); + update_now(); + if (!rc) break; + else if (errno != EINTR) + die(254, "unexpected error from select: %s", strerror(errno)); + } + if (sema_initialized_p(semid)) return (0); + w *= 2; + if (w >= 10) w = 10; + } + + /* Done. */ + return (semid); + + /* Report a get failure. */ +fail: + die(254, "failed to open semaphore set: %s", strerror(errno)); + +notready: + die(252, "semaphore set not ready"); +} + +static void rm_sema(int semid) +{ + if (semctl(semid, 0, IPC_RMID)) + die(254, "failed to remove semaphore set: %s", strerror(errno)); +} + +static unsigned read_sema(int semid) +{ + int val; + + if ((val = semctl(semid, 0, GETVAL)) < 0) + die(254, "failed to read semaphore value: %s", strerror(errno)); + return (val); +} + +static void set_sema(int semid, unsigned val) +{ + union semun u; + + u.val = val; + if (semctl(semid, 0, SETVAL, u) < 0) + die(254, "failed to set semaphore value: %s", strerror(errno)); +} + +static void post_sema(int semid, int by) +{ + struct sembuf buf[1]; + + buf[0].sem_num = 0; + buf[0].sem_op = by; + buf[0].sem_flg = 0; + if (semop(semid, buf, 1)) + die(254, "failed to post semaphore: %s", strerror(errno)); +} + +static struct sembuf *sigsembuf; +static int sigflag; + +static void wait_alarm(int hunoz) + { sigflag = 1; if (sigsembuf) sigsembuf->sem_flg |= IPC_NOWAIT; } + +static int wait_sema(int semid, int by, int flg, const struct timeout *t) +{ + struct sembuf buf[1]; + struct sigaction sa, osa; + sigset_t ss, oss, ps; + struct timeval tv; + struct itimerval it, oit; + int rc, sig; + unsigned f = 0; +#define f_alarm 1u + + /* Set up the command buffer. */ + buf[0].sem_num = 0; + buf[0].sem_op = -by; + buf[0].sem_flg = flg; + + /* Modify it according to the timeout settings. */ + switch (t->wait) { + + case NEVER: + /* Don't wait. */ + buf[0].sem_flg |= IPC_NOWAIT; + break; + + case FOREVER: + /* Wait until it's ready. */ + break; + + case UNTIL: + /* Wait until the specified time. This is fiddly, and will likely + * require setting an alarm. + */ + + /* If the timeout is already in the past, then don't bother with the + * alarm, and simply don't wait. + */ + if (TV_CMP(&t->tv, <=, &now)) { + buf[0].sem_flg |= IPC_NOWAIT; + break; + } + + /* Find the current alarm setting. */ + if (getitimer(ITIMER_REAL, &oit)) { + die(254, "failed to read current alarm setting: %s", + strerror(errno)); + } + + /* If that's not set, or it's going to go off after our timeout, then + * we should override it temporarily. Otherwise, just let it do its + * (likely lethal) thing. + */ + if (!oit.it_value.tv_sec && !oit.it_value.tv_usec) + f |= f_alarm; + else { + TV_ADD(&oit.it_value, &oit.it_value, &now); + if (TV_CMP(&t->tv, <, &oit.it_value)) f |= f_alarm; + } + + /* If we are setting an alarm, then now is the time to mess about with + * signals and timers. + */ + if (f & f_alarm) { + + /* Mask out the signal while we fiddle with the settings. */ + sigemptyset(&ss); + sigaddset(&ss, SIGALRM); + if (sigprocmask(SIG_BLOCK, &ss, &oss)) + die(254, "failed to block alarm signal: %s", strerror(errno)); + + /* Establish our new signal handler. */ + sigsembuf = &buf[0]; + sigflag = 0; + sa.sa_handler = wait_alarm; + sa.sa_mask = ss; + sa.sa_flags = 0; + if (sigaction(SIGALRM, &sa, &osa)) + die(254, "failed to set alarm handler: %s", strerror(errno)); + + /* Set up the timer. */ + TV_SUB(&it.it_value, &t->tv, &now); + it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; + if (setitimer(ITIMER_REAL, &it, 0)) + die(254, "failed to set alarm: %s", strerror(errno)); + + /* This bit's quite scummy. There isn't a POSIX-standard way to wait + * for a finite time before giving up, so we use alarms. But there + * also isn't a standard way to avoid a race between the alarm going + * off and us waiting for the semaphore, because semop doesn't have a + * signal set argument. So instead we have the signal handler frob + * the semop parameter block as well as setting a flag. Assuming + * that semop is actually a system call, then one of the following + * will happen: the signal will happen before the call, in which case + * it will set IPC_NOWAIT and semop will either succeed or fail + * immediately; the signal will happen during the call, while we're + * waiting for the semaphore, so semop will return with EINTR; or the + * signal will happen afterwards, by which point it's too late for us + * to care. + */ + compiler_fence(buf, &sigflag, FENCE_END); + if (sigprocmask(SIG_UNBLOCK, &ss, 0)) + die(254, "failed to unblock alarm signal: %s", strerror(errno)); + } + break; + } + + /* Wait for the semaphore. */ + if ((rc = semop(semid, buf, 1)) < 0 && + errno != EAGAIN && + !(errno == EINTR && sigflag)) + die(254, "failed to post semaphore: %s", strerror(errno)); + + /* Now we clean up again afterwards. */ + update_now(); + + /* If we set an alarm, we must dismantle it and restore the previous + * situation. + */ + if (f & f_alarm) { + + /* We're messing with the alarm again, so mask it out temporarily. */ + if (sigprocmask(SIG_BLOCK, &ss, &oss)) + die(254, "failed to block alarm signal: %s", strerror(errno)); + + /* Turn off the alarm timer. We don't need it any more. */ + it.it_value.tv_sec = 0; it.it_value.tv_usec = 0; + if (setitimer(ITIMER_REAL, &it, 0)) + die(254, "failed to disarm alarm timer: %s", strerror(errno)); + + /* At this point, if there's an alarm signal pending, it must be from the + * timer we set, and it's too late to do any good. So just clear it and + * move on. After this point, alarms will be from the previously + * established timer, and we should let them happen. + */ + if (sigpending(&ps)) + die(254, "failed to read pending signals: %s", strerror(errno)); + if (sigismember(&ps, SIGALRM) && sigwait(&ss, &sig)) + die(254, "failed to clear pending alarm: %s", strerror(errno)); + + /* Figure out how to restore the old timer. */ + if (oit.it_value.tv_sec || oit.it_value.tv_usec) { + if (TV_CMP(&oit.it_value, >, &now)) + TV_SUB(&oit.it_value, &oit.it_value, &now); + else { + /* We should have had an alarm by now. Schedule one for when we + * unblock the signal again. + */ + raise(SIGALRM); + + /* Sort out the timer again. */ + if (!oit.it_interval.tv_sec && !oit.it_interval.tv_usec) + oit.it_value.tv_sec = 0, oit.it_value.tv_usec = 0; + else { + TV_SUB(&tv, &now, &oit.it_value); + if (TV_CMP(&tv, <, &oit.it_interval)) + TV_SUB(&oit.it_value, &oit.it_interval, &tv); + else { + /* We've overshot the previous deadline, and missed at least one + * repetition. If this is bad, then we've already failed quite + * miserably. + */ + oit.it_value = oit.it_interval; + } + } + } + } + + /* We've now recovered the right settings, even if that's just to turn + * the alarm off, so go do that. + */ + if ((oit.it_value.tv_sec || oit.it_value.tv_usec) && + setitimer(ITIMER_REAL, &oit, 0)) + die(254, "failed to restore old alarm settings: %s", strerror(errno)); + + /* Restore the old signal handler. */ + if (sigaction(SIGALRM, &osa, 0)) + die(254, "failed to restore old alarm handler: %s", strerror(errno)); + + /* And finally we can restore the previous signal mask. */ + if (sigprocmask(SIG_SETMASK, &oss, 00)) + die(254, "failed to restore old signal mask: %s", strerror(errno)); + } + + return (rc ? -1 : 0); +} + +/*----- Main code ---------------------------------------------------------*/ + +static struct timeout timeout = { FOREVER }; + +static int cmd_mkfile(int argc, char *argv[]) +{ + mode_t mode = 0666, mask = 0; + const char *name = 0; + int of = 0; + unsigned f = 0; +#define f_bogus 1u +#define f_mode 2u + + for (;;) { + static const struct option opts[] = { + { "mode", OPTF_ARGREQ, 0, 'm' }, + { "exclusive", 0, 0, 'x' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "m:x", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + case 'm': + mode = parse_int("mode", optarg, 8, 0, 07777); + f |= f_mode; + break; + case 'x': of |= O_EXCL; break; + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (argc < 1) f |= f_bogus; + else { name = argv[0]; argc--; argv++; } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + if (f & f_mode) mask = umask(0); + make_sema_file(name, of, mode); + if (f & f_mode) umask(mask); + return (0); + +#undef f_bogus +#undef f_mode +} + +static int cmd_new(int argc, char *argv[]) +{ + mode_t mode = 0, mask; + const char *name = 0; + unsigned init = 0; + int of = O_CREAT; + unsigned f = 0; +#define f_bogus 1u +#define f_mode 2u + + for (;;) { + static const struct option opts[] = { + { "mode", OPTF_ARGREQ, 0, 'm' }, + { "exclusive", 0, 0, 'x' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "m:x", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + case 'm': + mode = parse_int("mode", optarg, 8, 0, 0777); + f |= f_mode; + break; + case 'x': of |= O_EXCL; break; + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (argc < 2) f |= f_bogus; + else { + name = argv[0]; + init = parse_int("initial semaphore value", argv[1], + 0, 0, SEM_VALUE_MAX); + argc -= 2; argv += 2; + } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + if (!(f & f_mode)) { + mask = umask(0); umask(mask); + mode = ~mask & 0777; + } + open_sema(name, 0, of, mode, init, &timeout); + return (0); + +#undef f_bogus +#undef f_mode +} + +static int cmd_rm(int argc, char *argv[]) +{ + const char *name = 0; + unsigned f = 0, ff = 0; + int semid; +#define f_bogus 1u + + for (;;) { + static const struct option opts[] = { + { "force", 0, 0, 'f' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "f", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + case 'f': ff |= OF_PROBE; break; + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (!argc) f |= f_bogus; + else { name = argv[0]; argc--; argv++; } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + semid = open_sema(name, ff | OF_UNINIT, 0, 0, 0, &timeout); + if (semid >= 0) rm_sema(semid); + + return (0); + +#undef f_bogus +} + +static int cmd_set(int argc, char *argv[]) +{ + const char *name = 0; + int semid; + unsigned n = 0; + unsigned f = 0; +#define f_bogus 1u + + for (;;) { + static const struct option opts[] = { + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (argc < 2) f |= f_bogus; + else { + name = argv[0]; + n = parse_int("count", argv[1], 0, 0, SEM_VALUE_MAX); + argc -= 2; argv += 2; + } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + semid = open_sema(name, 0, 0, 0, 0, &timeout); + set_sema(semid, n); + return (0); + +#undef f_bogus +} + +static int cmd_get(int argc, char *argv[]) +{ + const char *name = 0; + int semid; + unsigned f = 0; +#define f_bogus 1u + + for (;;) { + static const struct option opts[] = { + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (!argc) f |= f_bogus; + else { name = argv[0]; argc--; argv++; } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + semid = open_sema(name, 0, 0, 0, 0, &timeout); + printf("%u\n", read_sema(semid)); + return (0); + +#undef f_bogus +} + +static int cmd_post(int argc, char *argv[]) +{ + const char *name = 0; + int semid, n = 1; + unsigned f = 0; +#define f_bogus 1u + + for (;;) { + static const struct option opts[] = { + { "count", OPTF_ARGREQ, 0, 'n' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "n:", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + case 'n': n = parse_int("count", optarg, 0, 1, SEM_VALUE_MAX); break; + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (!argc) f |= f_bogus; + else { name = argv[0]; argc--; argv++; } + if (argc) f |= f_bogus; + if (f & f_bogus) return (-1); + + semid = open_sema(name, 0, 0, 0, 0, &timeout); + post_sema(semid, n); + return (0); + +#undef f_bogus +} + +static int cmd_wait(int argc, char *argv[]) +{ + const char *name = 0; + int semid, n = 1; + int flg = 0; + unsigned f = 0; +#define f_bogus 1u + + for (;;) { + static const struct option opts[] = { + { "count", OPTF_ARGREQ, 0, 'n' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "-n:", opts, 0, 0, OPTF_NOPROGNAME); + + if (o < 0) break; + switch (o) { + case 'n': n = parse_int("count", optarg, 0, 1, SEM_VALUE_MAX); break; + case 0: + if (!name) name = optarg; + else { optind--; goto done_opts; } + break; + default: f |= f_bogus; break; + } + } +done_opts: + argc -= optind; argv += optind; + if (!name) { + if (!argc) f |= f_bogus; + else { name = argv[0]; argc--; argv++; } + } + if (argc) flg |= SEM_UNDO; + if (f & f_bogus) return (-1); + + semid = open_sema(name, 0, 0, 0, 0, &timeout); + if (wait_sema(semid, n, flg, &timeout)) + die(251, "semaphore busy"); + + if (argc) { + execvp(argv[0], argv); + die(254, "failed to exec `%s': %s", argv[0], strerror(errno)); + } + return (0); + +#undef f_bogus +} + +static const struct subcmd { + const char *name; + int (*func)(int, char *[]); + const char *usage; +} subcmds[] = { + { "mkfile", cmd_mkfile, "[-x] [-m MODE] NAME" }, + { "new", cmd_new, "[-x] [-m MODE] NAME VALUE" }, + { "rm", cmd_rm, "[-f] NAME" }, + { "get", cmd_get, "NAME" }, + { "set", cmd_set, "NAME VALUE" }, + { "post", cmd_post, "[-n COUNT] NAME" }, + { "wait", cmd_wait, "[-n COUNT] NAME [COMMAND ARGUMENTS ...]" }, + { 0, 0, 0 } +}; + +static void version(FILE *fp) + { pquis(fp, "$, version " VERSION "\n"); } + +static void usage(FILE *fp) +{ + const struct subcmd *c; + + fprintf(fp, "usage:\n"); + for (c = subcmds; c->name; c++) + fprintf(fp, "\t%s [-OPTIONS] %s %s\n", QUIS, c->name, c->usage); +} + +static void help(FILE *fp) +{ + version(fp); putc('\n', fp); + usage(fp); + fprintf(fp, "\n\ +Top-level options:\n\ +\n\ + -w, --wait=WHEN How long to wait (`forever', `never', or\n\ + nonnegative number followed by `s', `m',\n\ + `h', `d')\n"); +} + +int main(int argc, char *argv[]) +{ + const struct subcmd *c; + int rc; + unsigned f = 0; +#define f_bogus 1u + + ego(argv[0]); + update_now(); + for (;;) { + static const struct option opts[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "wait", OPTF_ARGREQ, 0, 'w' }, + { 0, 0, 0, 0 } + }; + int o = mdwopt(argc, argv, "+hvw:", opts, 0, 0, 0); + + if (o < 0) break; + switch (o) { + case 'h': help(stdout); exit(0); break; + case 'v': version(stdout); exit(0); break; + case 'w': parse_timeout(optarg, &timeout); break; + default: f |= f_bogus; break; + } + } + argc -= optind; argv += optind; + if (!argc) f |= f_bogus; + if (f & f_bogus) { usage(stderr); exit(253); } + + optind = 0; + for (c = subcmds; c->name; c++) + if (strcmp(argv[0], c->name) == 0) goto found; + die(253, "unknown command `%s'", argv[0]); +found: + argc--; argv++; + rc = c->func(argc, argv); + if (rc < 0) { + fprintf(stderr, "usage: %s %s %s\n", QUIS, c->name, c->usage); + exit(253); + } + return (rc); + +#undef f_bogus +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/timemax.cc b/timemax.cc new file mode 100644 index 0000000..f02cba5 --- /dev/null +++ b/timemax.cc @@ -0,0 +1,49 @@ +/* -*-c++-*- + * + * Return the largest allowable time_t value + * + * (c) 2016 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include + +#include "timemax.h" + +using namespace std; + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @timemax@ --- * + * + * Arguments: --- + * + * Returns: The largest value repesentable in an object of type @time_t@. + * + * Use: For some stupid reason there isn't a @TIME_MAX@ anywhere, and + * it's really hard to discover the right value in C. But it's + * quite easy in C++, so that's what we do. + */ + +time_t timemax() { return numeric_limits::max(); } + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/timemax.h b/timemax.h new file mode 100644 index 0000000..a20bb9e --- /dev/null +++ b/timemax.h @@ -0,0 +1,57 @@ +/* -*-c-*- + * + * Return the largest allowable @time_t@ value + * + * (c) 2016 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef TIMEMAX_H +#define TIMEMAX_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include + +/*----- Functions provided ------------------------------------------------*/ + +/* --- @timemax@ --- * + * + * Arguments: --- + * + * Returns: The largest value repesentable in an object of type @time_t@. + * + * Use: For some stupid reason there isn't a @TIME_MAX@ anywhere, and + * it's really hard to discover the right value in C. But it's + * quite easy in C++, so that's what we do. + */ + +time_t timemax(void); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif