+/* -*-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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/sem.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/tv.h>
+
+#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 -------------------------------------------------*/