-
Notifications
You must be signed in to change notification settings - Fork 11
/
lockf.c
366 lines (346 loc) · 9.85 KB
/
lockf.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
static const char help_str[] =
"Call: %s -d FD [OPT..] COMMAND [ARG..]\n"
" -f LOCKFILE [OPT..] COMMAND [ARG..]\n"
" ...\n"
"\n"
"Serialize execution of COMMAND using POSIX lockf() locking\n"
"(or other methods).\n"
"\n"
"Usecases:\n"
"\n"
" - Make sure that only one instance of a cron job is running\n"
" - Coordinate command executions via NFS\n"
"\n"
"Options:\n"
"\n"
"-b let lockf()/fcntl()/flock block and wait on a locked file\n"
"-c LOCKFILE open LOCKFILE with O_CREAT before calling lockf()\n"
"-d FD lock the already open file descriptor FD\n"
"-e LOCKFILE open LOCKFILE with O_CREAT and O_EXCL for locking\n"
"-f LOCKFILE open LOCKFILE for locking (O_WRONLY-only) with lockf()\n"
"-h,--help this screen\n"
"-i LOCKFILE use link() for locking (cf. -s)\n"
"-k,-K enable/disable suicide on parent exit (default: disabled)\n"
" On Linux, a parent death signal is installed in the child\n"
" that execs COMMAND, otherwise the TERM signal handler kills\n"
" the child.\n"
"-l use flock() instead of lockf()\n"
"-n use fcntl instead of lockf()\n"
"-m LOCKDIR use mkdir for locking\n"
"-r LOCKFILE use rename() for locking; LOCKFILE is moved to SOURCE (cf. -s);\n"
" -u moves the file back\n"
"-s SOURCE use SOURCE for hardlinking source/rename destination\n"
" (if not specified one is created via mkstemp())\n"
"-u unlink LOCKFILE on exit\n"
"\n"
"With lockf()/fcntl()/flock(), the lock is automatically removed\n"
"when the program terminates.\n"
"\n"
"Not all methods are necessarily reliable over NFS. Especially in\n"
"heterogenous environments. See the README.md for details. lockf()\n"
"and open(... O_CREAT|O_EXCL) should be a relative good bet, though.\n"
"\n"
"2016, Georg Sauthoff <[email protected]>, GPLv3+\n"
"cf. https://github.com/gsauthof/utility\n"
"\n";
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#if defined(__linux__)
#include <sys/prctl.h>
#endif
#include "utility.h"
#ifndef USE_PRCTL
#if defined(__linux__)
#define USE_PRCTL 1
#else
#define USE_PRCTL 0
#endif
#endif
#define USE_KILL 1
static void help(FILE *f, const char *argv0)
{
fprintf(f, help_str , argv0);
}
enum Method {
LOCKF,
MKDIR,
OPEN,
FLOCK,
FCNTL,
LINK,
RENAME
};
typedef enum Method Method;
struct Arguments {
Method method;
bool unlink;
bool unlink_source;
bool block;
bool suicide;
int fd;
const char *filename;
const char *source;
char **childs_argv;
};
typedef struct Arguments Arguments;
#define verify_exclusive(x, msg) do { if (x) { fprintf(stderr, (msg)); exit(1); } ++(x); } while(0)
static void post_process_arguments(Arguments *a)
{
if ((a->method == LINK || a->method == RENAME) && !a->source) {
const char suffix[] = "_XXXXXX";
size_t n = strlen(a->filename);
char *s = calloc(n+sizeof(suffix), 1);
if (!s)
exit(1);
memcpy(s, a->filename, n);
memcpy(s + n, suffix, sizeof(suffix));
int fd = mkstemp(s);
check_exit(fd, "creating temp file");
int r = close(fd);
check_exit(r, "closing temp file");
a->unlink_source = true;
a->source = s;
}
}
static void parse_arguments(int argc, char **argv, Arguments *a)
{
*a = (Arguments) {0};
char c = 0;
unsigned x = 0;
const char excl_str[] = "only one of -c/-d/-e/-f/-m/-r/-s allowed\n";
const char opt_str[] = "+bc:d:e:f:hi:lm:nr:s:u";
while ((c = getopt(argc, argv, opt_str)) != -1) {
switch (c) {
case '?': help(stderr, argv[0]); exit(1); break;
case 'b':
a->block = true;
break;
case 'c':
verify_exclusive(x, excl_str);
a->filename = optarg;
a->fd = open(a->filename, O_CREAT | O_WRONLY, 0666);
check_exit(a->fd, "opening lockfile");
break;
case 'd':
verify_exclusive(x, excl_str);
{
errno = 0;
char *s =0;
a->fd = strtol(optarg, &s, 10);
if (errno || s == optarg) {
perror("converting -d argument");
exit(1);
}
if (!a->fd) {
fprintf(stderr, "Cannot lock file descriptor 0\n");
exit(1);
}
}
break;
case 'e':
verify_exclusive(x, excl_str);
a->filename = optarg;
a->method = OPEN;
break;
case 'f':
verify_exclusive(x, excl_str);
a->filename = optarg;
a->fd = open(a->filename, O_WRONLY);
check_exit(a->fd, "opening lockfile");
break;
case 'h': help(stdout, argv[0]); exit(0); break;
case 'i':
verify_exclusive(x, excl_str);
a->method = LINK;
a->filename = optarg;
break;
case 'k': a->suicide = true ; break;
case 'K': a->suicide = false; break;
case 'l': a->method = FLOCK; break;
case 'm':
verify_exclusive(x, excl_str);
a->method = MKDIR;
a->filename = optarg;
break;
case 'n': a->method = FCNTL; break;
case 'r':
verify_exclusive(x, excl_str);
a->method = RENAME;
a->filename = optarg;
break;
case 's': a->source = optarg; break;
case 'u': a->unlink = true; break;
}
}
if (optind == argc || !x) {
help(stderr, argv[0]);
exit(1);
}
a->childs_argv = argv + optind;
post_process_arguments(a);
}
#if USE_KILL
static pid_t child_pid_ = 0;
static void kill_child(int sig)
{
(void)sig;
if (child_pid_)
kill(child_pid_, SIGTERM);
// we don't exit because we want to still wait on the
// child to release the lock after it has finished
}
#endif
static void supervise_child(pid_t pid, const Arguments *a)
{
// we ignore QUIT/INT because when issued via Ctrl+\/Ctrl+C in the terminal,
// UNIX sends them both to the parent and the child
// (cf. http://unix.stackexchange.com/questions/176235/fork-and-how-signals-are-delivered-to-processes)
// we ignore those such that we reliably terminate after the
// child (assuming default action there), thus, lock is
// released after the child's exit
struct sigaction ignore_action = { .sa_handler = SIG_IGN };
struct sigaction old_int_action;
int r = sigaction(SIGINT, &ignore_action, &old_int_action);
check_exit(r, "ignoring SIGINT");
struct sigaction old_quit_action;
r = sigaction(SIGQUIT, &ignore_action, &old_quit_action);
check_exit(r, "ignoring SIGQUIT");
#if USE_KILL
child_pid_ = pid;
struct sigaction term_action = { .sa_handler = kill_child };
struct sigaction old_term_action;
if (a->suicide) {
int r = sigaction(SIGTERM, &term_action, &old_term_action);
check_exit(r, "installing SIGTERM handler");
}
#endif
siginfo_t siginfo;
r = waitid(P_PID, pid, &siginfo, WEXITED);
check_exit(r, "waiting on child");
#if USE_KILL
if (a->suicide) {
r = sigaction(SIGTERM, &old_term_action, 0);
check_exit(r, "restoring SIGTERM");
}
#endif
r = sigaction(SIGINT, &old_int_action, 0);
check_exit(r, "restoring SIGINT");
r = sigaction(SIGQUIT, &old_quit_action, 0);
check_exit(r, "restoring SIGQUIT");
if (a->unlink) {
if (a->method == RENAME) {
int r = rename(a->source, a->filename);
check_exit(r, "move lockfile back");
} else {
int r = unlink(a->filename);
check_exit(r, "unlinking lockfile");
}
}
int code = siginfo.si_code == CLD_EXITED
? siginfo.si_status : 128 + siginfo.si_status;
exit(code);
}
static void exec_child(char **argv)
{
int r = execvp(*argv, argv);
if (r == -1) {
perror("executing command");
// cf. http://tldp.org/LDP/abs/html/exitcodes.html
exit(errno == ENOENT ? 127 : 126);
}
}
static void aquire_lock(const Arguments *a)
{
int r = 0;
switch (a->method) {
case LOCKF:
r = lockf(a->fd, a->block ? F_LOCK : F_TLOCK, 0);
check_exit(r, "lockf locking");
break;
case FLOCK:
#ifdef __sun
fprintf(stderr, "Solaris doesn't support flock(), anymore\n");
exit(1);
#else
r = flock(a->fd, LOCK_EX | (a->block ? 0 : LOCK_NB ));
check_exit(r, "flock locking");
#endif
break;
case FCNTL:
{
struct flock l = { .l_type = F_WRLCK,
.l_start = 0,
.l_whence = SEEK_SET,
.l_len = 0 };
r = fcntl(a->fd, a->block ? F_SETLKW : F_SETLK, &l);
}
check_exit(r, "fcntl locking");
break;
case OPEN:
r = open(a->filename, O_CREAT | O_EXCL | O_RDONLY, 0666);
check_exit(r, "excl open locking");
break;
case MKDIR:
r = mkdir(a->filename, 0777);
check_exit(r, "mkdir locking");
break;
case LINK:
r = link(a->source, a->filename);
check_exit(r, "link locking");
if (a->unlink_source) {
r = unlink(a->source);
check_exit(r, "unlinking source");
}
break;
case RENAME:
r = rename(a->filename, a->source);
check_exit(r, "rename locking");
break;
default:
break;
}
}
int main(int argc, char **argv)
{
Arguments a = {0};
parse_arguments(argc, argv, &a);
aquire_lock(&a);
#if USE_PRCTL
pid_t ppid_before_fork = getpid();
#endif
pid_t pid = fork();
check_exit(pid, "forking child");
if (pid) {
// we don't need stdin
int r = close(0);
check_exit(r, "closing stdin in parent");
supervise_child(pid, &a);
} else {
#if USE_PRCTL
if (a.suicide) {
// is valid through the exec, but is not inherited into children
int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
check_exit(r, "installing parent death signal");
// cf. http://stackoverflow.com/a/36945270/427158
if (getppid() != ppid_before_fork)
exit(1);
}
#endif
if (a.fd) {
int r = close(a.fd);
check_exit(r, "closing fd before exec");
}
exec_child(a.childs_argv);
}
return 0;
}