chiark / gitweb /
configure.ac: Fix the `--with-perlmoddir' help text.
[misc] / locking.c
1 /* -*-c-*-
2  *
3  * $Id$
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 #include <time.h>
38
39 #include <sys/types.h>
40 #include <sys/time.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <sys/wait.h>
44
45 #include <mLib/mdwopt.h>
46 #include <mLib/quis.h>
47 #include <mLib/report.h>
48
49 /*----- Static variables --------------------------------------------------*/
50
51 static jmp_buf jmp;
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 {
65   pquis(fp, "$ (version " VERSION ")\n");
66 }
67
68 static void help(FILE *fp)
69 {
70   version(fp);
71   putchar('\n');
72   usage(fp);
73   pquis(fp, "\n\
74 Lock FILE and run PROG, passing ARGS.  Options are:\n\
75 \n\
76 -h, --help              Show this help message.\n\
77 -v, --version           Show version string.\n\
78 -u, --usage             Show terse usage summary.\n\
79 \n\
80 -c, --[no-]create       Create FILE if it doesn't exist [default: on].\n\
81 -f, --[no-]fail         Fail if the file is already locked [default: off].\n\
82 -w, --[no-]wait         Wait for the lock to be available [default: off].\n\
83 -x, --[no-]exclusive    Get an exclusive (writer) lock [default: on].\n\
84 -p, --program=REALPROG  Run REALPROG instead of PROG.\n\
85 -t, --timeout=TIME      Wait for TIME for lock to become available.\n\
86 ");
87 }
88
89 int main(int argc, char *argv[])
90 {
91   const char *file = 0;
92   const char *prog = 0;
93   char *const *av;
94   void (*oalrm)(int) = 0;
95   int fd;
96   struct flock l;
97   char *p;
98   int t = -1;
99   unsigned int ot = 0;
100   time_t nt;
101   pid_t kid;
102   int st;
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)
131       break;
132     switch (i) {
133       case 'h':
134         help(stdout);
135         exit(0);
136       case 'v':
137         version(stdout);
138         exit(0);
139       case 'u':
140         usage(stdout);
141         exit(0);
142       case 'w':
143         f |= f_wait;
144         break;
145       case 'w' | OPTF_NEGATED:
146         f &= ~f_wait;
147         break;
148       case 'f':
149         f |= f_fail;
150         break;
151       case 'f' | OPTF_NEGATED:
152         f &= ~f_fail;
153         break;
154       case 'c':
155         f |= f_create;
156         break;
157       case 'c' | OPTF_NEGATED:
158         f &= ~f_create;
159         break;
160       case 'x':
161         f |= f_excl;
162         break;
163       case 'x' | OPTF_NEGATED:
164         f &= ~f_excl;
165         break;
166       case 't':
167         errno = 0;
168         t = strtol(optarg, &p, 0);
169         switch (*p) {
170           case 'd': t *= 24;
171           case 'h': t *= 60;
172           case 'm': t *= 60;
173           case 's': p++;
174           case 0: break;
175           default: die(111, "unknown time unit `%c'", *p);
176         }
177         if (*p || t < 0 || errno)
178           die(111, "bad time value `%s'", optarg);
179         f |= f_wait;
180         break;
181       case 'p':
182         prog = optarg;
183         break;
184       case 0:
185         if (file) {
186           optind--;
187           goto doneopts;
188         }
189         file = optarg;
190         break;
191       default:
192         f |= f_bogus;
193         break;
194     }
195   }
196
197 doneopts:
198   if (f & f_bogus || argc - optind < 1) {
199     usage(stderr);
200     exit(EXIT_FAILURE);
201   }
202
203   av = &argv[optind];
204   if (!prog)
205     prog = av[0];
206   if ((fd = open(file,
207                  ((f & f_create ? O_CREAT : 0) |
208                   (f & f_excl ? O_RDWR : O_RDONLY)), 0666)) < 0)
209     die(111, "error opening `%s': %s", file, strerror(errno));
210   l.l_type = f & f_excl ? F_WRLCK : F_RDLCK;
211   l.l_whence = SEEK_SET;
212   l.l_start = 0;
213   l.l_len = 0;
214   nt = time(0);
215   if (setjmp(jmp)) {
216     errno = EAGAIN;
217     nt = t;
218   } else {
219     ot = alarm(0);
220     oalrm = signal(SIGALRM, alrm);
221     if (t >= 0)
222       alarm(t);
223     if (fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0)
224       errno = 0;
225   }
226   signal(SIGALRM, oalrm);
227   if (!ot)
228     alarm(0);
229   else {
230     nt = time(0) - nt;
231     if (nt > ot)
232       raise(SIGALRM);
233     else
234       alarm(ot - nt);
235   }
236   if (errno &&
237       ((errno != EAGAIN && errno != EWOULDBLOCK && errno != EACCES) ||
238        (f & f_fail)))
239     die(111, "error locking `%s': %s", file, strerror(errno));
240   if (errno)
241     exit(0);
242
243   if ((kid = fork()) < 0)
244     die(111, "error from fork: %s", strerror(errno));
245   if (!kid) {
246     close(fd);
247     execvp(prog, av);
248     die(111, "couldn't exec `%s': %s", prog, strerror(errno));
249   }
250   if (waitpid(kid, &st, 0) < 0)
251     die(EXIT_FAILURE, "error from wait: %s", strerror(errno));
252   l.l_type = F_UNLCK;
253   l.l_whence = SEEK_SET;
254   l.l_start = 0;
255   l.l_len = 0;
256   fcntl(fd, F_SETLK, &l);
257   close(fd);
258   if (WIFEXITED(st))
259     exit(WEXITSTATUS(st));
260   else
261     exit(255);
262 }
263
264 /*----- That's all, folks -------------------------------------------------*/