5 * with-lock-ex -<mode> [-t <secs>] <lockfile> <command> <args>...
6 * with-lock-ex -l <lockfile>
10 * f fail if the lock cannot be acquired
11 * q silently do nothing if the lock cannot be acquired
12 * l show who is waiting (print "none" or "read <pid>"
13 * or "write <pid>"; lockfile opened for reading;
14 * no command may be specified)
16 * if -t is specified, then with-lock-ex will wait for up to <secs>
17 * seconds to acquire the lock, and then fail or silently do nothing
18 * (depending on whether -f or -q is specified). You cannot specify
19 * a timeout for modes l or w
21 * with-lock-ex will open and lock the lockfile for writing and
22 * then feed the remainder of its arguments to exec(2); when
23 * that process terminates the fd will be closed and the file
24 * unlocked automatically by the kernel.
26 * If invoked as with-lock, behaves like with-lock-ex -f (for backward
27 * compatibility with an earlier version).
29 * This file written by me, Ian Jackson, in 1993, 1994, 1995, 1996,
32 * Copyright 1993-2016 Ian Jackson in some jurisdictions
33 * Copyright 2017 Ian Jackson in all jurisdictions
34 * Copyright 2017 Genome Research Ltd
38 * Permission is hereby granted, free of charge, to any person obtaining a
39 * copy of this software and associated documentation files (the "Software"),
40 * to deal in the Software without restriction, including without limitation
41 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
42 * and/or sell copies of the Software, and to permit persons to whom the
43 * Software is furnished to do so, subject to the following conditions:
45 * The above copyright notice and this permission notice shall be included in
46 * all copies or substantial portions of the Software.
48 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
51 * SOFTWARE IN THE PUBLIC INTEREST, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR
52 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
53 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
54 * DEALINGS IN THE SOFTWARE.
68 static const char *cmd;
70 static void fail(const char *why) __attribute__((noreturn));
72 static void fail(const char *why) {
73 fprintf(stderr,"with-lock-ex %s: %s: %s\n",cmd,why,strerror(errno));
77 static void badusage(void) __attribute__((noreturn));
79 static void badusage(void) {
80 fputs("usage: with-lock-ex -w|-q|-f [-t <secs>] <lockfile> <command> <args>...\n"
81 " with-lock-ex -l <lockfile>\n"
82 " with-lock <lockfile> <command> <args>...\n",
87 volatile int gotlock = 0;
90 /* This signal handler uses unsafe functions, so MUST NOT be callable
91 * during an unsafe function, as that is Undefined Behaviour
93 static void alrm_handler(int signum) {
99 "with-lock-ex %s: timer expired while trying to acquire lock\n",
106 int main(int argc, char **argv) {
108 struct stat stab, fstab;
109 long cloexec, secs=0;
112 sigset_t sigs, oldsigs;
113 struct sigaction siga;
114 struct itimerval itv;
117 while ((c = getopt(argc,argv,"+wfqlt:")) != -1)
123 if (mode != 'x') badusage();
128 secs = strtol(optarg, &endptr, 0);
129 if (*endptr || endptr==optarg || errno==ERANGE)
130 fail("parsing timeout value");
132 fprintf(stderr,"timeout value must be >=0\n");
140 if (secs && (mode=='l' || mode=='w')) {
141 fputs("-t only allowed with -q or -f.\n", stderr);
145 argv += optind-1; argc -= optind-1;
146 if (argc < 2) badusage();
149 if (sigemptyset(&sigs)) fail("Initialising signal set");
150 if (sigaddset(&sigs,SIGALRM)) fail("Adding SIGALRM to signal set");
151 if (sigprocmask(SIG_BLOCK,&sigs,&oldsigs)) fail("Blocking SIGALRM");
152 memset(&siga,0,sizeof(siga));
153 siga.sa_handler=alrm_handler;
154 if (sigaction(SIGALRM,&siga,NULL)) fail("Installing SIGALRM handler");
155 memset(&itv,0,sizeof(itv));
156 itv.it_value.tv_sec=secs;
157 if (setitimer(ITIMER_REAL,&itv,NULL)) fail("Setting timer");
161 um= umask(0777); if (um==-1) fail("find umask");
162 if (umask(um)==-1) fail("reset umask");
166 int openmode = mode=='l' ? O_RDONLY : O_RDWR|O_CREAT;
168 fd= open(argv[1],openmode,0666&~(um|((um&0222)<<1)));
169 if (fd<0) fail(argv[1]);
173 fl.l_whence= SEEK_SET;
175 fl.l_len= mode=='l' ? 0 : 1;
176 if (secs) sigprocmask(SIG_UNBLOCK,&sigs,NULL);
178 mode=='l' ? F_GETLK :
179 mode=='w' || secs > 0 ? F_SETLKW :
182 if (secs) sigprocmask(SIG_BLOCK,&sigs,NULL);
188 (errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY))
190 if (errno != EINTR) fail("could not acquire lock");
195 fl.l_type == F_WRLCK ? "write" :
196 fl.l_type == F_RDLCK ? "read" : "unknown",
197 (unsigned long)fl.l_pid);
201 if (ferror(stdout)) fail("print to stdout\n");
205 itv.it_value.tv_sec=0;
206 if (setitimer(ITIMER_REAL,&itv,NULL)) fail("Clearing timer");
207 sigprocmask(SIG_SETMASK,&oldsigs,NULL);
208 siga.sa_handler=SIG_DFL;
209 sigaction(SIGALRM,&siga,NULL);
212 if (fstat(fd, &fstab)) fail("could not fstat lock fd");
213 if (stat(argv[1], &stab)) {
214 if (errno != ENOENT) fail("could not stat lockfile");
216 if (stab.st_dev == fstab.st_dev &&
217 stab.st_ino == fstab.st_ino) break;
222 cloexec= fcntl(fd, F_GETFD); if (cloexec==-1) fail("fcntl F_GETFD");
224 if (fcntl(fd, F_SETFD, cloexec)==-1) fail("fcntl F_SETFD");
227 fail("unable to execute command");