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