chiark / gitweb /
with-lock-ex: Replace ad-hoc option parser with getopt.
[chiark-utils.git] / cprogs / with-lock-ex.c
1 /*
2  * File locker
3  *
4  * Usage:
5  *  with-lock-ex -<mode> <lockfile> <command> <args>...
6  *  with-lock-ex -l      <lockfile>
7  *
8  * modes are
9  *  w    wait for the lock
10  *  f    fail if the lock cannot be acquired
11  *  q    silently do nothing if the lock cannot be acquired
12  *  l    show who is waiting (print "none" or "read <pid>"
13  *         or "write <pid>"; lockfile opened for reading;
14  *         no command may be specified)
15  *
16  * with-lock-ex will open and lock the lockfile for writing and
17  * then feed the remainder of its arguments to exec(2); when
18  * that process terminates the fd will be closed and the file
19  * unlocked automatically by the kernel.
20  *
21  * If invoked as with-lock, behaves like with-lock-ex -f (for backward
22  * compatibility with an earlier version).
23  *
24  * This file written by me, Ian Jackson, in 1993, 1994, 1995, 1996,
25  * 1998, 1999, 2016.
26  *
27  * Copyright 1993-2016 Ian Jackson in some jurisdictions
28  * Copyright 2017      Ian Jackson in all jurisdictions
29  * Copyright 2017      Genome Research Ltd
30  *
31  * (MIT licence:)
32  *
33  * Permission is hereby granted, free of charge, to any person obtaining a
34  * copy of this software and associated documentation files (the "Software"),
35  * to deal in the Software without restriction, including without limitation
36  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
37  * and/or sell copies of the Software, and to permit persons to whom the
38  * Software is furnished to do so, subject to the following conditions:
39  *
40  * The above copyright notice and this permission notice shall be included in
41  * all copies or substantial portions of the Software.
42  *
43  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
46  * SOFTWARE IN THE PUBLIC INTEREST, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR
47  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
48  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
49  * DEALINGS IN THE SOFTWARE.
50  */
51
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <unistd.h>
57 #include <string.h>
58 #include <sys/stat.h>
59
60 static const char *cmd;
61
62 static void fail(const char *why) __attribute__((noreturn));
63
64 static void fail(const char *why) {
65   fprintf(stderr,"with-lock-ex %s: %s: %s\n",cmd,why,strerror(errno));
66   exit(255);
67 }
68
69 static void badusage(void) __attribute__((noreturn));
70
71 static void badusage(void) {
72     fputs("usage: with-lock-ex -w|-q|-f [-t <secs>] <lockfile> <command> <args>...\n"
73           "       with-lock-ex -l       <lockfile>\n"
74           "       with-lock-ex          <lockfile> <command> <args>...\n",
75           stderr);
76     exit(255);
77 }
78
79 static int mode;
80
81 int main(int argc, char **argv) {
82   int fd, um, c;
83   struct stat stab, fstab;
84   long cloexec;
85   struct flock fl;
86
87   mode= 'x';
88   while ((c= getopt(argc,argv,"+wfqlt:")) != -1) {
89     switch(c) {
90     case 'l':
91     case 'w':
92     case 'f':
93     case 'q':
94       if (mode != 'x') badusage();
95       mode= c;
96       break;
97     default:
98       badusage();
99     }
100   }
101
102   argv += optind-1; argc -= optind-1;
103   if (argc < 2) badusage();
104
105   cmd= argv[2];
106   um= umask(0777); if (um==-1) fail("find umask");
107   if (umask(um)==-1) fail("reset umask");
108
109   for (;;) {
110
111     int openmode = mode=='l' ? O_RDONLY : O_RDWR|O_CREAT;
112
113     fd= open(argv[1],openmode,0666&~(um|((um&0222)<<1)));
114     if (fd<0) fail(argv[1]);
115   
116     for (;;) {
117       fl.l_type= F_WRLCK;
118       fl.l_whence= SEEK_SET;
119       fl.l_start= 0;
120       fl.l_len= mode=='l' ? 0 : 1;
121       if (fcntl(fd,
122                 mode=='l' ? F_GETLK :
123                 mode=='w' ? F_SETLKW :
124                 F_SETLK,
125                 &fl) != -1) break;
126       if (mode=='q' &&
127           (errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY))
128         exit(0);
129       if (errno != EINTR) fail("could not acquire lock");
130     }
131     if (mode=='l') {
132       if (fl.l_pid) {
133         printf("%s %lu\n",
134                fl.l_type == F_WRLCK ? "write" :
135                fl.l_type == F_RDLCK ? "read" : "unknown",
136                (unsigned long)fl.l_pid);
137       } else {
138         printf("none\n");
139       }
140       if (ferror(stdout)) fail("print to stdout\n");
141       exit(0);
142     }
143
144     if (fstat(fd, &fstab)) fail("could not fstat lock fd");
145     if (stat(argv[1], &stab)) {
146       if (errno != ENOENT) fail("could not stat lockfile");
147     } else {
148       if (stab.st_dev == fstab.st_dev &&
149           stab.st_ino == fstab.st_ino) break;
150     }
151     close(fd);
152   }
153
154   cloexec= fcntl(fd, F_GETFD); if (cloexec==-1) fail("fcntl F_GETFD");
155   cloexec &= ~1;
156   if (fcntl(fd, F_SETFD, cloexec)==-1) fail("fcntl F_SETFD");
157
158   execvp(cmd,argv+2);
159   fail("unable to execute command");
160 }