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