chiark / gitweb /
Merge remote-tracking branch 'refs/remotes/origin/master'
[misc] / locking.c
1 /* -*-c-*-
2  *
3  * Lock a file, run a program
4  *
5  * (c) 2003 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Toys utilties collection.
11  *
12  * Toys is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * Toys is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with Toys; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include <errno.h>
30 #include <setjmp.h>
31 #include <signal.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <sys/wait.h>
42 #include <sys/stat.h>
43
44 #include <mLib/mdwopt.h>
45 #include <mLib/quis.h>
46 #include <mLib/report.h>
47
48 /*----- Static variables --------------------------------------------------*/
49
50 static jmp_buf jmp;
51 static int err;
52
53 /*----- Main code ---------------------------------------------------------*/
54
55 static void alrm(int s) { longjmp(jmp, 1); }
56
57 static void usage(FILE *fp)
58 {
59   pquis(fp,
60         "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n");
61 }
62
63 static void version(FILE *fp)
64   { pquis(fp, "$ (version " VERSION ")\n"); }
65
66 static void help(FILE *fp)
67 {
68   version(fp);
69   putchar('\n');
70   usage(fp);
71   pquis(fp, "\n\
72 Lock FILE and run PROG, passing ARGS.  Options are:\n\
73 \n\
74 -h, --help              Show this help message.\n\
75 -v, --version           Show version string.\n\
76 -u, --usage             Show terse usage summary.\n\
77 \n\
78 -c, --[no-]create       Create FILE if it doesn't exist [default: on].\n\
79 -f, --[no-]fail         Fail if the file is already locked [default: off].\n\
80 -w, --[no-]wait         Wait for the lock to be available [default: off].\n\
81 -x, --[no-]exclusive    Get an exclusive (writer) lock [default: on].\n\
82 -p, --program=REALPROG  Run REALPROG instead of PROG.\n\
83 -t, --timeout=TIME      Wait for TIME for lock to become available.\n\
84 ");
85 }
86
87 int main(int argc, char *argv[])
88 {
89   const char *file = 0;
90   const char *prog = 0;
91   char *const *av;
92   void (*oalrm)(int) = 0;
93   int fd = -1;
94   struct flock l;
95   char *p;
96   int t = -1;
97   int oflag;
98   unsigned int ot = 0;
99   struct stat st, nst;
100   time_t nt;
101   pid_t kid;
102   int rc;
103
104 #define f_bogus 1u
105 #define f_wait 2u
106 #define f_fail 4u
107 #define f_create 8u
108 #define f_excl 16u
109
110   unsigned f = f_create | f_excl;
111
112   ego(argv[0]);
113
114   for (;;) {
115     static const struct option opts[] = {
116       { "help",                 0,              0,      'h' },
117       { "version",              0,              0,      'v' },
118       { "usage",                0,              0,      'u' },
119       { "wait",                 OPTF_NEGATE,    0,      'w' },
120       { "fail",                 OPTF_NEGATE,    0,      'f' },
121       { "create",               OPTF_NEGATE,    0,      'c' },
122       { "program",              OPTF_ARGREQ,    0,      'p' },
123       { "timeout",              OPTF_ARGREQ,    0,      't' },
124       { "exclusive",            OPTF_NEGATE,    0,      'x' },
125       { 0,                      0,              0,      0 }
126     };
127
128     int i = mdwopt(argc, argv, "-hvuw+f+c+x+p:t:", opts,
129                    0, 0, OPTF_NEGATION);
130     if (i < 0) break;
131     switch (i) {
132       case 'h': help(stdout); exit(0);
133       case 'v': version(stdout); exit(0);
134       case 'u': usage(stdout); exit(0);
135       case 'w': f |= f_wait; break;
136       case 'w' | OPTF_NEGATED: f &= ~f_wait; break;
137       case 'f': f |= f_fail; break;
138       case 'f' | OPTF_NEGATED: f &= ~f_fail; break;
139       case 'c': f |= f_create; break;
140       case 'c' | OPTF_NEGATED: f &= ~f_create; break;
141       case 'x': f |= f_excl; break;
142       case 'x' | OPTF_NEGATED: f &= ~f_excl; break;
143       case 't':
144         errno = 0;
145         t = strtol(optarg, &p, 0);
146         switch (*p) {
147           case 'd': t *= 24;
148           case 'h': t *= 60;
149           case 'm': t *= 60;
150           case 's': p++;
151           case 0: break;
152           default: die(111, "unknown time unit `%c'", *p);
153         }
154         if (*p || t < 0 || errno) die(111, "bad time value `%s'", optarg);
155         f |= f_wait;
156         break;
157       case 'p': prog = optarg; break;
158       case 0:
159         if (file) { optind--; goto doneopts; }
160         file = optarg;
161         break;
162       default: f |= f_bogus; break;
163     }
164   }
165
166 doneopts:
167   if (f & f_bogus || argc - optind < 1) {
168     usage(stderr);
169     exit(EXIT_FAILURE);
170   }
171
172   av = &argv[optind];
173   if (!prog) prog = av[0];
174   oflag = f & f_excl ? O_RDWR : O_RDONLY;
175   if (f & f_create) oflag |= O_CREAT;
176   l.l_type = f & f_excl ? F_WRLCK : F_RDLCK;
177   l.l_whence = SEEK_SET;
178   l.l_start = 0;
179   l.l_len = 0;
180   nt = time(0);
181   if (setjmp(jmp)) {
182     err = EAGAIN;
183     nt = t;
184     goto done;
185   }
186   ot = alarm(0);
187   oalrm = signal(SIGALRM, alrm);
188   if (t >= 0) alarm(t);
189 again:
190   if ((fd = open(file, oflag, 0666)) < 0)
191     die(111, "error opening `%s': %s", file, strerror(errno));
192   if (fstat(fd, &st))
193     die(111, "error from fstat on `%s': %s", file, strerror(errno));
194   err = fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0 ? 0 : errno;
195   if (stat(file, &nst)) {
196     if (errno == ENOENT) { close(fd); goto again; }
197     else die(111, "error from stat on `%s': %s", file, strerror(errno));
198   }
199   if (st.st_dev != nst.st_dev || st.st_ino != nst.st_ino)
200     { close(fd); goto again; }
201 done:
202   signal(SIGALRM, oalrm);
203   if (!ot)
204     alarm(0);
205   else {
206     nt = time(0) - nt;
207     if (nt > ot) raise(SIGALRM);
208     else alarm(ot - nt);
209   }
210   switch (err) {
211     case 0: break;
212     case EAGAIN:
213 #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
214     case EWOULDBLOCK:
215 #endif
216     case EACCES:
217       if (!(f & f_fail)) exit(0);
218     default:
219       die(111, "error locking `%s': %s", file, strerror(errno));
220   }
221   if ((kid = fork()) < 0) die(111, "error from fork: %s", strerror(errno));
222   if (!kid) {
223     close(fd);
224     execvp(prog, av);
225     die(111, "couldn't exec `%s': %s", prog, strerror(errno));
226   }
227   if (waitpid(kid, &rc, 0) < 0)
228     die(EXIT_FAILURE, "error from wait: %s", strerror(errno));
229   l.l_type = F_UNLCK;
230   l.l_whence = SEEK_SET;
231   l.l_start = 0;
232   l.l_len = 0;
233   fcntl(fd, F_SETLK, &l);
234   if (fd >= 0) close(fd);
235   if (WIFEXITED(rc)) exit(WEXITSTATUS(rc));
236   else exit(128 + WTERMSIG(rc));
237 }
238
239 /*----- That's all, folks -------------------------------------------------*/