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)) {