9435318191bea22eb23acd707ebd47a31ae69965
[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
43 #include <mLib/mdwopt.h>
44 #include <mLib/quis.h>
45 #include <mLib/report.h>
46
47 /*----- Static variables --------------------------------------------------*/
48
49 static jmp_buf jmp;
50
51 /*----- Main code ---------------------------------------------------------*/
52
53 static void alrm(int s) { longjmp(jmp, 1); }
54
55 static void usage(FILE *fp)
56 {
57 pquis(fp,
58 "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n");
59 }
60
61 static void version(FILE *fp)
62 { pquis(fp, "$ (version " VERSION ")\n"); }
63
64 static void help(FILE *fp)
65 {
66 version(fp);
67 putchar('\n');
68 usage(fp);
69 pquis(fp, "\n\
70 Lock FILE and run PROG, passing ARGS. Options are:\n\
71 \n\
72 -h, --help Show this help message.\n\
73 -v, --version Show version string.\n\
74 -u, --usage Show terse usage summary.\n\
75 \n\
76 -c, --[no-]create Create FILE if it doesn't exist [default: on].\n\
77 -f, --[no-]fail Fail if the file is already locked [default: off].\n\
78 -w, --[no-]wait Wait for the lock to be available [default: off].\n\
79 -x, --[no-]exclusive Get an exclusive (writer) lock [default: on].\n\
80 -p, --program=REALPROG Run REALPROG instead of PROG.\n\
81 -t, --timeout=TIME Wait for TIME for lock to become available.\n\
82 ");
83 }
84
85 int main(int argc, char *argv[])
86 {
87 const char *file = 0;
88 const char *prog = 0;
89 char *const *av;
90 void (*oalrm)(int) = 0;
91 int fd;
92 struct flock l;
93 char *p;
94 int t = -1;
95 unsigned int ot = 0;
96 time_t nt;
97 pid_t kid;
98 int st;
99
100 #define f_bogus 1u
101 #define f_wait 2u
102 #define f_fail 4u
103 #define f_create 8u
104 #define f_excl 16u
105
106 unsigned f = f_create | f_excl;
107
108 ego(argv[0]);
109
110 for (;;) {
111 static const struct option opts[] = {
112 { "help", 0, 0, 'h' },
113 { "version", 0, 0, 'v' },
114 { "usage", 0, 0, 'u' },
115 { "wait", OPTF_NEGATE, 0, 'w' },
116 { "fail", OPTF_NEGATE, 0, 'f' },
117 { "create", OPTF_NEGATE, 0, 'c' },
118 { "program", OPTF_ARGREQ, 0, 'p' },
119 { "timeout", OPTF_ARGREQ, 0, 't' },
120 { "exclusive", OPTF_NEGATE, 0, 'x' },
121 { 0, 0, 0, 0 }
122 };
123
124 int i = mdwopt(argc, argv, "-hvuw+f+c+x+p:t:", opts,
125 0, 0, OPTF_NEGATION);
126 if (i < 0) break;
127 switch (i) {
128 case 'h': help(stdout); exit(0);
129 case 'v': version(stdout); exit(0);
130 case 'u': usage(stdout); exit(0);
131 case 'w': f |= f_wait; break;
132 case 'w' | OPTF_NEGATED: f &= ~f_wait; break;
133 case 'f': f |= f_fail; break;
134 case 'f' | OPTF_NEGATED: f &= ~f_fail; break;
135 case 'c': f |= f_create; break;
136 case 'c' | OPTF_NEGATED: f &= ~f_create; break;
137 case 'x': f |= f_excl; break;
138 case 'x' | OPTF_NEGATED: f &= ~f_excl; break;
139 case 't':
140 errno = 0;
141 t = strtol(optarg, &p, 0);
142 switch (*p) {
143 case 'd': t *= 24;
144 case 'h': t *= 60;
145 case 'm': t *= 60;
146 case 's': p++;
147 case 0: break;
148 default: die(111, "unknown time unit `%c'", *p);
149 }
150 if (*p || t < 0 || errno) die(111, "bad time value `%s'", optarg);
151 f |= f_wait;
152 break;
153 case 'p': prog = optarg; break;
154 case 0:
155 if (file) { optind--; goto doneopts; }
156 file = optarg;
157 break;
158 default: f |= f_bogus; break;
159 }
160 }
161
162 doneopts:
163 if (f & f_bogus || argc - optind < 1) {
164 usage(stderr);
165 exit(EXIT_FAILURE);
166 }
167
168 av = &argv[optind];
169 if (!prog) prog = av[0];
170 if ((fd = open(file,
171 ((f & f_create ? O_CREAT : 0) |
172 (f & f_excl ? O_RDWR : O_RDONLY)), 0666)) < 0)
173 die(111, "error opening `%s': %s", file, strerror(errno));
174 l.l_type = f & f_excl ? F_WRLCK : F_RDLCK;
175 l.l_whence = SEEK_SET;
176 l.l_start = 0;
177 l.l_len = 0;
178 nt = time(0);
179 if (setjmp(jmp)) {
180 errno = EAGAIN;
181 nt = t;
182 } else {
183 ot = alarm(0);
184 oalrm = signal(SIGALRM, alrm);
185 if (t >= 0) alarm(t);
186 if (fcntl(fd, f & f_wait ? F_SETLKW : F_SETLK, &l) >= 0) errno = 0;
187 }
188 signal(SIGALRM, oalrm);
189 if (!ot)
190 alarm(0);
191 else {
192 nt = time(0) - nt;
193 if (nt > ot) raise(SIGALRM);
194 else alarm(ot - nt);
195 }
196 if (errno &&
197 ((errno != EAGAIN && errno != EWOULDBLOCK && errno != EACCES) ||
198 (f & f_fail)))
199 die(111, "error locking `%s': %s", file, strerror(errno));
200 if (errno) exit(0);
201
202 if ((kid = fork()) < 0) die(111, "error from fork: %s", strerror(errno));
203 if (!kid) {
204 close(fd);
205 execvp(prog, av);
206 die(111, "couldn't exec `%s': %s", prog, strerror(errno));
207 }
208 if (waitpid(kid, &st, 0) < 0)
209 die(EXIT_FAILURE, "error from wait: %s", strerror(errno));
210 l.l_type = F_UNLCK;
211 l.l_whence = SEEK_SET;
212 l.l_start = 0;
213 l.l_len = 0;
214 fcntl(fd, F_SETLK, &l);
215 close(fd);
216 if (WIFEXITED(st)) exit(WEXITSTATUS(st));
217 else exit(255);
218 }
219
220 /*----- That's all, folks -------------------------------------------------*/