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
|
/* lckdo.c: run a program with a lock held,
* to prevent multiple processes running in parallel.
* Use just like `nice' or `nohup'.
* Written by Michael Tokarev <mjt@tls.msk.ru>
* Public domain.
*/
#define _GNU_SOURCE
#define _BSD_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sysexits.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <signal.h>
/* compile with -DUSE_FLOCK to use flock() instead of fcntl() */
#if !defined(USE_FLOCK) && !defined(F_SETLKW)
# define USE_FLOCK
#endif
#ifndef __GNUC__
# ifndef __attribute__
# define __attribute__(x)
# endif
#endif
static char *progname;
static void
__attribute__((format(printf,3,4)))
__attribute__((noreturn))
error(int errnum, int exitcode, const char *fmt, ...) {
va_list ap;
fprintf(stderr, "%s: ", progname);
va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
if (errnum)
fprintf(stderr, ": %s\n", strerror(errnum));
else
fputs("\n", stderr);
exit(exitcode);
}
static const char *lckfile;
static int quiet;
static void sigalarm(int sig) {
if (quiet)
_exit(EX_TEMPFAIL);
error(0, EX_TEMPFAIL,
"lock file `%s' is already locked (timeout waiting)", lckfile);
}
int main(int argc, char **argv) {
int fd;
int c;
int create = O_CREAT;
int dofork = 1;
int waittime = 0;
int shared = 0;
int test = 0;
int fdn = -1;
#ifndef USE_FLOCK
struct flock fl;
#endif
if ((progname = strrchr(argv[0], '/')) == NULL)
progname = argv[0];
else
argv[0] = ++progname;
if (argc == 1) {
printf(
"%s: execute a program with a lock set.\n"
"Usage: %s [options] lockfile program [arguments]\n"
"where options are:\n"
" -w - if the lock is already held by another process,\n"
" wait for it to complete instead of failing immediately\n"
" -W sec - the same as -w but wait not more than sec seconds\n"
" -e - execute the program directly, no fork/wait\n"
" (keeps extra open file descriptor)\n"
" -E nnn - set the fd# to keep open in -e case (implies -e)\n"
" -n - do not create the lock file if it does not exist\n"
" -q - produce no output if lock is already held\n"
" -s - lock in shared (read) mode\n"
" -x - lock in exclusive (write) mode (default)\n"
" -t - test for lock existence"
#ifndef USE_FLOCK
" (just prints pid if any with -q)\n"
#endif
" (implies -n)\n"
, progname, progname);
return 0;
}
while ((c = getopt(argc, argv, "+wW:neE:sxtq")) != EOF) {
switch(c) {
case 'w':
if (!waittime)
waittime = -1;
break;
case 'W':
if ((waittime = atoi(optarg)) < 1)
error(0, EX_USAGE, "invalid wait time `%s'", optarg);
break;
case 't':
test = 1;
/* fall thru */
case 'n':
create = 0;
break;
case 'E':
if ((fdn = atoi(optarg)) < 0 || fdn == 2)
error(0, EX_USAGE, "invalid fd# `%s'", optarg);
/* fall thru */
case 'e':
dofork = 0;
break;
case 's':
shared = 1;
break;
case 'x':
shared = 0;
break;
case 'q':
quiet = 1;
break;
default:
return EX_USAGE;
}
}
argc -= optind; argv += optind;
if (!argc || (!test && argc < 2))
error(0, EX_USAGE, "too few arguments given");
lckfile = *argv++;
#ifdef USE_FLOCK
create |= O_RDONLY;
#else
if (!test)
create |= shared ? O_RDONLY : O_WRONLY;
#endif
fd = open(lckfile, create, 0666);
if (fd < 0) {
if (test && errno == ENOENT) {
if (!quiet)
printf("lockfile `%s' is not locked\n", lckfile);
return 0;
}
error(errno, EX_CANTCREAT, "unable to open `%s'", lckfile);
}
if (!test && fdn >= 0) {
/* dup it early to comply with stupid POSIX fcntl locking
* semantics */
if (dup2(fd, fdn) < 0)
error(errno, EX_OSERR, "dup2(%d,%d) failed", fd, fdn);
close(fd);
fd = fdn;
}
if (test)
waittime = 0;
else if (waittime > 0) {
alarm(waittime);
signal(SIGALRM, sigalarm);
}
#ifdef USE_FLOCK
c = flock(fd, (shared ? LOCK_SH : LOCK_EX) | (waittime ? 0 : LOCK_NB));
if (test && c < 0 &&
(errno == EWOULDBLOCK || errno == EAGAIN || errno == EACCES)) {
if (!quiet)
printf("lockfile `%s' is locked\n", lckfile);
else
printf("locked\n");
return EX_TEMPFAIL;
}
#else
memset(&fl, 0, sizeof(fl));
fl.l_type = shared ? F_RDLCK : F_WRLCK;
c = fcntl(fd, test ? F_GETLK : waittime ? F_SETLKW : F_SETLK, &fl);
if (test && c == 0) {
if (fl.l_type == F_UNLCK) {
if (!quiet)
printf("lockfile `%s' is not locked\n", lckfile);
return 0;
}
if (!quiet)
printf("lockfile `%s' is locked by process %d\n", lckfile, fl.l_pid);
else
printf("%d\n", fl.l_pid);
return EX_TEMPFAIL;
}
#endif
if (waittime > 0)
alarm(0);
if (c < 0) {
if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EACCES)
error(errno, EX_OSERR, "unable to lock `%s'", lckfile);
else if (quiet)
return EX_TEMPFAIL;
else
error(0, EX_TEMPFAIL, "lockfile `%s' is already locked", lckfile);
}
if (dofork) {
pid_t pid;
int flags = fcntl(fd, F_GETFD, 0);
if (flags < 0)
error(errno, EX_OSERR, "fcntl() failed");
fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
pid = fork();
if (pid < 0)
error(errno, EX_OSERR, "unable to fork");
else if (pid) {
if (wait(&c) < 0)
error(errno, EX_OSERR, "wait() failed");
if (WIFSIGNALED(c))
error(0, EX_SOFTWARE, "%s: %s", *argv,
strsignal(WTERMSIG(c)));
return WEXITSTATUS(c);
}
}
execvp(*argv, argv);
error(errno, EX_OSERR, "unable to execute %s", *argv);
}
|