chiark / gitweb /
Lots of stuff.
[misc] / locking.c
diff --git a/locking.c b/locking.c
new file mode 100644 (file)
index 0000000..735ba47
--- /dev/null
+++ b/locking.c
@@ -0,0 +1,278 @@
+/* -*-c-*-
+ *
+ * $Id: locking.c,v 1.1 2003/10/09 15:05:34 mdw Exp $
+ *
+ * Lock a file, run a program
+ *
+ * (c) 2003 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of the Toys utilties collection.
+ *
+ * Toys 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.
+ * 
+ * Toys 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 Toys; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: locking.c,v $
+ * Revision 1.1  2003/10/09 15:05:34  mdw
+ * Lots of stuff.
+ *
+ * Revision 1.3  2003/09/24 14:58:08  mdw
+ * Fix options parsing again.
+ *
+ * Revision 1.2  2003/09/24 14:14:03  mdw
+ * Fix option handling behaviour.
+ *
+ * Revision 1.1  2003/05/11 13:30:04  mdw
+ * Initial checkin.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+
+/*----- Static variables --------------------------------------------------*/
+
+static jmp_buf jmp;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void alrm(int s) { longjmp(jmp, 1); }
+
+static void usage(FILE *fp)
+{
+  pquis(fp,
+       "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n");
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$ (toys, version " VERSION "\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  putchar('\n');
+  usage(fp);
+  pquis(fp, "\n\
+Lock FILE and run PROG, passing ARGS.  Options are:\n\
+\n\
+-h, --help             Show this help message.\n\
+-v, --version          Show version string.\n\
+-u, --usage            Show terse usage summary.\n\
+\n\
+-c, --[no-]create      Create FILE if it doesn't exist [default: on].\n\
+-f, --[no-]fail                Fail if the file is already locked [default: off].\n\
+-w, --[no-]wait                Wait for the lock to be available [default: off].\n\
+-x, --[no-]exclusive   Get an exclusive (writer) lock [default: on].\n\
+-p, --program=REALPROG Run REALPROG instead of PROG.\n\
+-t, --timeout=TIME     Wait for TIME for lock to become available.\n\
+");
+}
+
+int main(int argc, char *argv[])
+{
+  const char *file = 0;
+  const char *prog = 0;
+  char *const *av;
+  void (*oalrm)(int) = 0;
+  int fd;
+  struct flock l;
+  char *p;
+  int t = -1;
+  unsigned int ot;
+  time_t nt;
+  pid_t kid;
+  int st;
+
+#define f_bogus 1u
+#define f_wait 2u
+#define f_fail 4u
+#define f_create 8u
+#define f_excl 16u
+
+  unsigned f = f_create | f_excl;
+
+  ego(argv[0]);
+
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                        0,              0,      'h' },
+      { "version",             0,              0,      'v' },
+      { "usage",               0,              0,      'u' },
+      { "wait",                        OPTF_NEGATE,    0,      'w' },
+      { "fail",                        OPTF_NEGATE,    0,      'f' },
+      { "create",              OPTF_NEGATE,    0,      'c' },
+      { "program",             OPTF_ARGREQ,    0,      'p' },
+      { "timeout",             OPTF_ARGREQ,    0,      't' },
+      { "exclusive",           OPTF_NEGATE,    0,      'x' },
+      {        0,                      0,              0,      0 }
+    };
+
+    int i = mdwopt(argc, argv, "-hvuw+f+c+x+p:t:", opts,
+                  0, 0, OPTF_NEGATION);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+      case 'w':
+       f |= f_wait;
+       break;
+      case 'w' | OPTF_NEGATED:
+       f &= ~f_wait;
+       break;
+      case 'f':
+       f |= f_fail;
+       break;
+      case 'f' | OPTF_NEGATED:
+       f &= ~f_fail;
+       break;
+      case 'c':
+       f |= f_create;
+       break;
+      case 'c' | OPTF_NEGATED:
+       f &= ~f_create;
+       break;
+      case 'x':
+       f |= f_excl;
+       break;
+      case 'x' | OPTF_NEGATED:
+       f &= ~f_excl;
+       break;
+      case 't':
+       errno = 0;
+       t = strtol(optarg, &p, 0);
+       switch (*p) {
+         case 'd': t *= 24;
+         case 'h': t *= 60;
+         case 'm': t *= 60;
+         case 's': p++;
+         case 0: break;
+         default: die(111, "unknown time unit `%c'", *p);
+       }
+       if (*p || t < 0 || errno)
+         die(111, "bad time value `%s'", optarg);
+       f |= f_wait;
+       break;
+      case 'p':
+       prog = optarg;
+       break;
+      case 0:
+       if (file) {
+         optind--;
+         goto doneopts;
+       }
+       file = optarg;
+       break;
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+doneopts:
+  if (f & f_bogus || argc - optind < 1) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  av = &argv[optind];
+  if (!prog)
+    prog = av[0];
+  if ((fd = open(file,
+                ((f & f_create ? O_CREAT : 0) |
+                 (f & f_excl ? O_RDWR : O_RDONLY)), 0666)) < 0)
+    die(111, "error opening `%s': %s", file, strerror(errno));
+  l.l_type = f & f_excl ? F_WRLCK : F_RDLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  nt = time(0);
+  if (setjmp(jmp)) {
+    errno = EAGAIN;
+    nt = t;
+  } else {
+    ot = alarm(0);
+    oalrm = signal(SIGALRM, alrm);
+    if (t >= 0)
+      alarm(t);
+    if (fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0)
+      errno = 0;
+  }
+  signal(SIGALRM, oalrm);
+  if (ot) {
+    nt = time(0) - nt;
+    if (nt > ot)
+      raise(SIGALRM);
+    else
+      alarm(ot - nt);
+  }
+  if (errno &&
+      ((errno != EAGAIN && errno != EWOULDBLOCK && errno != EACCES) ||
+       (f & f_fail)))
+    die(111, "error locking `%s': %s", file, strerror(errno));
+  if (errno)
+    exit(0);
+
+  if ((kid = fork()) < 0)
+    die(111, "error from fork: %s", strerror(errno));
+  if (!kid) {
+    close(fd);
+    execvp(prog, av);
+    die(111, "couldn't exec `%s': %s", prog, strerror(errno));
+  }
+  if (waitpid(kid, &st, 0) < 0)
+    die(EXIT_FAILURE, "error from wait: %s", strerror(errno));
+  l.l_type = F_UNLCK;
+  l.l_whence = SEEK_SET;
+  l.l_start = 0;
+  l.l_len = 0;
+  fcntl(fd, F_SETLK, &l);
+  close(fd);
+  if (WIFEXITED(st))
+    exit(WEXITSTATUS(st));
+  else
+    exit(255);
+}
+
+/*----- That's all, folks -------------------------------------------------*/