chiark / gitweb /
Build fixes.
[misc] / locking.c
1 /* -*-c-*-
2  *
3  * $Id: locking.c,v 1.2 2003/10/09 15:15:42 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 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: locking.c,v $
32  * Revision 1.2  2003/10/09 15:15:42  mdw
33  * Missed a paren.  Un-`toys'-ify.
34  *
35  * Revision 1.1  2003/10/09 15:05:34  mdw
36  * Lots of stuff.
37  *
38  * Revision 1.3  2003/09/24 14:58:08  mdw
39  * Fix options parsing again.
40  *
41  * Revision 1.2  2003/09/24 14:14:03  mdw
42  * Fix option handling behaviour.
43  *
44  * Revision 1.1  2003/05/11 13:30:04  mdw
45  * Initial checkin.
46  *
47  */
48
49 /*----- Header files ------------------------------------------------------*/
50
51 #include <errno.h>
52 #include <setjmp.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57
58 #include <sys/types.h>
59 #include <sys/time.h>
60 #include <unistd.h>
61 #include <fcntl.h>
62 #include <sys/wait.h>
63
64 #include <mLib/mdwopt.h>
65 #include <mLib/quis.h>
66 #include <mLib/report.h>
67
68 /*----- Static variables --------------------------------------------------*/
69
70 static jmp_buf jmp;
71
72 /*----- Main code ---------------------------------------------------------*/
73
74 static void alrm(int s) { longjmp(jmp, 1); }
75
76 static void usage(FILE *fp)
77 {
78   pquis(fp,
79         "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n");
80 }
81
82 static void version(FILE *fp)
83 {
84   pquis(fp, "$ (version " VERSION ")\n");
85 }
86
87 static void help(FILE *fp)
88 {
89   version(fp);
90   putchar('\n');
91   usage(fp);
92   pquis(fp, "\n\
93 Lock FILE and run PROG, passing ARGS.  Options are:\n\
94 \n\
95 -h, --help              Show this help message.\n\
96 -v, --version           Show version string.\n\
97 -u, --usage             Show terse usage summary.\n\
98 \n\
99 -c, --[no-]create       Create FILE if it doesn't exist [default: on].\n\
100 -f, --[no-]fail         Fail if the file is already locked [default: off].\n\
101 -w, --[no-]wait         Wait for the lock to be available [default: off].\n\
102 -x, --[no-]exclusive    Get an exclusive (writer) lock [default: on].\n\
103 -p, --program=REALPROG  Run REALPROG instead of PROG.\n\
104 -t, --timeout=TIME      Wait for TIME for lock to become available.\n\
105 ");
106 }
107
108 int main(int argc, char *argv[])
109 {
110   const char *file = 0;
111   const char *prog = 0;
112   char *const *av;
113   void (*oalrm)(int) = 0;
114   int fd;
115   struct flock l;
116   char *p;
117   int t = -1;
118   unsigned int ot;
119   time_t nt;
120   pid_t kid;
121   int st;
122
123 #define f_bogus 1u
124 #define f_wait 2u
125 #define f_fail 4u
126 #define f_create 8u
127 #define f_excl 16u
128
129   unsigned f = f_create | f_excl;
130
131   ego(argv[0]);
132
133   for (;;) {
134     static const struct option opts[] = {
135       { "help",                 0,              0,      'h' },
136       { "version",              0,              0,      'v' },
137       { "usage",                0,              0,      'u' },
138       { "wait",                 OPTF_NEGATE,    0,      'w' },
139       { "fail",                 OPTF_NEGATE,    0,      'f' },
140       { "create",               OPTF_NEGATE,    0,      'c' },
141       { "program",              OPTF_ARGREQ,    0,      'p' },
142       { "timeout",              OPTF_ARGREQ,    0,      't' },
143       { "exclusive",            OPTF_NEGATE,    0,      'x' },
144       { 0,                      0,              0,      0 }
145     };
146
147     int i = mdwopt(argc, argv, "-hvuw+f+c+x+p:t:", opts,
148                    0, 0, OPTF_NEGATION);
149     if (i < 0)
150       break;
151     switch (i) {
152       case 'h':
153         help(stdout);
154         exit(0);
155       case 'v':
156         version(stdout);
157         exit(0);
158       case 'u':
159         usage(stdout);
160         exit(0);
161       case 'w':
162         f |= f_wait;
163         break;
164       case 'w' | OPTF_NEGATED:
165         f &= ~f_wait;
166         break;
167       case 'f':
168         f |= f_fail;
169         break;
170       case 'f' | OPTF_NEGATED:
171         f &= ~f_fail;
172         break;
173       case 'c':
174         f |= f_create;
175         break;
176       case 'c' | OPTF_NEGATED:
177         f &= ~f_create;
178         break;
179       case 'x':
180         f |= f_excl;
181         break;
182       case 'x' | OPTF_NEGATED:
183         f &= ~f_excl;
184         break;
185       case 't':
186         errno = 0;
187         t = strtol(optarg, &p, 0);
188         switch (*p) {
189           case 'd': t *= 24;
190           case 'h': t *= 60;
191           case 'm': t *= 60;
192           case 's': p++;
193           case 0: break;
194           default: die(111, "unknown time unit `%c'", *p);
195         }
196         if (*p || t < 0 || errno)
197           die(111, "bad time value `%s'", optarg);
198         f |= f_wait;
199         break;
200       case 'p':
201         prog = optarg;
202         break;
203       case 0:
204         if (file) {
205           optind--;
206           goto doneopts;
207         }
208         file = optarg;
209         break;
210       default:
211         f |= f_bogus;
212         break;
213     }
214   }
215
216 doneopts:
217   if (f & f_bogus || argc - optind < 1) {
218     usage(stderr);
219     exit(EXIT_FAILURE);
220   }
221
222   av = &argv[optind];
223   if (!prog)
224     prog = av[0];
225   if ((fd = open(file,
226                  ((f & f_create ? O_CREAT : 0) |
227                   (f & f_excl ? O_RDWR : O_RDONLY)), 0666)) < 0)
228     die(111, "error opening `%s': %s", file, strerror(errno));
229   l.l_type = f & f_excl ? F_WRLCK : F_RDLCK;
230   l.l_whence = SEEK_SET;
231   l.l_start = 0;
232   l.l_len = 0;
233   nt = time(0);
234   if (setjmp(jmp)) {
235     errno = EAGAIN;
236     nt = t;
237   } else {
238     ot = alarm(0);
239     oalrm = signal(SIGALRM, alrm);
240     if (t >= 0)
241       alarm(t);
242     if (fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0)
243       errno = 0;
244   }
245   signal(SIGALRM, oalrm);
246   if (ot) {
247     nt = time(0) - nt;
248     if (nt > ot)
249       raise(SIGALRM);
250     else
251       alarm(ot - nt);
252   }
253   if (errno &&
254       ((errno != EAGAIN && errno != EWOULDBLOCK && errno != EACCES) ||
255        (f & f_fail)))
256     die(111, "error locking `%s': %s", file, strerror(errno));
257   if (errno)
258     exit(0);
259
260   if ((kid = fork()) < 0)
261     die(111, "error from fork: %s", strerror(errno));
262   if (!kid) {
263     close(fd);
264     execvp(prog, av);
265     die(111, "couldn't exec `%s': %s", prog, strerror(errno));
266   }
267   if (waitpid(kid, &st, 0) < 0)
268     die(EXIT_FAILURE, "error from wait: %s", strerror(errno));
269   l.l_type = F_UNLCK;
270   l.l_whence = SEEK_SET;
271   l.l_start = 0;
272   l.l_len = 0;
273   fcntl(fd, F_SETLK, &l);
274   close(fd);
275   if (WIFEXITED(st))
276     exit(WEXITSTATUS(st));
277   else
278     exit(255);
279 }
280
281 /*----- That's all, folks -------------------------------------------------*/