Hi,
Revised patch attached [also the out-of-band branch is updated]. As
suggested, the exiting-when-timedout logic is now done in the signal
handler, and I make sure not to call any unsafe functions while that
signal handler is in effect.
The signal-faffing is all conditional on secs (so won't happen if -t
isn't specified).
I don't call fail() when the signal handler is in effect and ALRM might
be unblocked, as that would be UB; one might wish to abort() or exit()
if the relevant calls failed?
Regards,
Matthew
diff --git a/cprogs/with-lock-ex.c b/cprogs/with-lock-ex.c
index 2b7b45b..94f36df 100644
--- a/cprogs/with-lock-ex.c
+++ b/cprogs/with-lock-ex.c
@@ -2,7 +2,7 @@
* File locker
*
* Usage:
- * with-lock-ex -<mode> <lockfile> <command> <args>...
+ * with-lock-ex -<mode> [-t <secs>] <lockfile> <command> <args>...
* with-lock-ex -l <lockfile>
*
* modes are
@@ -13,6 +13,11 @@
* or "write <pid>"; lockfile opened for reading;
* no command may be specified)
*
+ * if -t is specified, then with-lock-ex will wait for up to <secs>
+ * seconds to acquire the lock, and then fail or silently do nothing
+ * (depending on whether -f or -q is specified). You cannot specify
+ * a timeout for modes l or w
+ *
* with-lock-ex will open and lock the lockfile for writing and
* then feed the remainder of its arguments to exec(2); when
* that process terminates the fd will be closed and the file
@@ -32,6 +37,9 @@
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/time.h>
static const char *cmd;
@@ -42,28 +50,89 @@ static void fail(const char *why) {
exit(255);
}
-int main(int argc, char **argv) {
- int fd, mode, um;
- struct stat stab, fstab;
- long cloexec;
- struct flock fl;
- const char *p;
-
- if (argc >= 3 && !strcmp((p= strrchr(argv[0],'/')) ? ++p : argv[0],
"with-lock")) {
- mode= 'f';
- } else if (argc < 3 || argv[1][0] != '-' || argv[1][2] ||
- ((mode= argv[1][1]) != 'w' && mode != 'q' && mode != 'f'
- && mode != 'l') ||
- (mode != 'l' && argc < 4) ||
- (mode == 'l' && argc != 3)) {
- fputs("usage: with-lock-ex -w|-q|-f <lockfile> <command> <args>...\n"
+static void badusage(void) __attribute__((noreturn));
+
+static void badusage(void) {
+ fputs("usage: with-lock-ex -w|-q|-f [-t <secs>] <lockfile> <command>
<args>...\n"
" with-lock-ex -l <lockfile>\n"
" with-lock <lockfile> <command> <args>...\n",
stderr);
exit(255);
- } else {
- argv++; argc--;
+}
+
+volatile int gotlock = 0;
+static int mode;
+
+/* This signal handler uses unsafe functions, so MUST NOT be callable
+ * during an unsafe function, as that is Undefined Behaviour
+ */
+static void alrm_handler(int signum) {
+ if (!gotlock) {
+ if (mode=='q') {
+ exit(0);
+ } else {
+ fprintf(stderr,
+ "with-lock-ex %s: timer expired while trying to acquire lock\n",
+ cmd);
+ exit(255);
+ }
}
+}
+
+int main(int argc, char **argv) {
+ int fd, um, c, r;
+ struct stat stab, fstab;
+ long cloexec, secs=0;
+ struct flock fl;
+ char *endptr;
+ sigset_t sigs, oldsigs;
+ struct sigaction siga;
+ struct itimerval itv;
+
+ mode='x';
+ while ((c = getopt(argc,argv,"+wfqlt:")) != -1)
+ switch(c) {
+ case 'l':
+ case 'w':
+ case 'f':
+ case 'q':
+ if (mode != 'x') badusage();
+ mode = c;
+ break;
+ case 't':
+ errno = 0;
+ secs = strtol(optarg, &endptr, 0);
+ if (*endptr || endptr==optarg || errno==ERANGE)
+ fail("parsing timeout value");
+ if (secs < 0) {
+ fprintf(stderr,"timeout value must be >=0\n");
+ exit(255);
+ }
+ break;
+ default:
+ badusage();
+ }
+
+ if (secs && (mode=='l' || mode=='w')) {
+ fputs("-t only allowed with -q or -f.\n", stderr);
+ exit(255);
+ }
+
+ argv += optind-1; argc -= optind-1;
+ if (argc < 2) badusage();
+
+ if (secs) {
+ if (sigemptyset(&sigs)) fail("Initialising signal set");
+ if (sigaddset(&sigs,SIGALRM)) fail("Adding SIGALRM to signal set");
+ if (sigprocmask(SIG_BLOCK,&sigs,&oldsigs)) fail("Blocking SIGALRM");
+ memset(&siga,0,sizeof(siga));
+ siga.sa_handler=alrm_handler;
+ if (sigaction(SIGALRM,&siga,NULL)) fail("Installing SIGALRM handler");
+ memset(&itv,0,sizeof(itv));
+ itv.it_value.tv_sec=secs;
+ if (setitimer(ITIMER_REAL,&itv,NULL)) fail("Setting timer");
+ }
+
cmd= argv[2];
um= umask(0777); if (um==-1) fail("find umask");
if (umask(um)==-1) fail("reset umask");
@@ -80,11 +149,17 @@ int main(int argc, char **argv) {
fl.l_whence= SEEK_SET;
fl.l_start= 0;
fl.l_len= mode=='l' ? 0 : 1;
- if (fcntl(fd,
+ if (secs) sigprocmask(SIG_UNBLOCK,&sigs,NULL);
+ r = fcntl(fd,
mode=='l' ? F_GETLK :
- mode=='w' ? F_SETLKW :
+ mode=='w' || secs > 0 ? F_SETLKW :
F_SETLK,
- &fl) != -1) break;
+ &fl);
+ if (secs) sigprocmask(SIG_BLOCK,&sigs,NULL);
+ if (!r) {
+ gotlock=1;
+ break;
+ }
if (mode=='q' &&
(errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY))
exit(0);
@@ -102,6 +177,13 @@ int main(int argc, char **argv) {
if (ferror(stdout)) fail("print to stdout\n");
exit(0);
}
+ if (secs) {
+ itv.it_value.tv_sec=0;
+ if (setitimer(ITIMER_REAL,&itv,NULL)) fail("Clearing timer");
+ sigprocmask(SIG_SETMASK,&oldsigs,NULL);
+ siga.sa_handler=SIG_DFL;
+ sigaction(SIGALRM,&siga,NULL);
+ }
if (fstat(fd, &fstab)) fail("could not fstat lock fd");
if (stat(argv[1], &stab)) {