Makefile: Warn users of pkgx.dev about their poor life choices
[moreutils.git] / lckdo.c
blob7bfb5e2c0376b6edaa607da00e45445b9d4ef197
1 /* lckdo.c: run a program with a lock held,
2 * to prevent multiple processes running in parallel.
3 * Use just like `nice' or `nohup'.
4 * Written by Michael Tokarev <mjt@tls.msk.ru>
5 * Public domain.
6 */
8 #define _GNU_SOURCE
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <errno.h>
14 #include <stdlib.h>
15 #include <stdarg.h>
16 #include <sysexits.h>
17 #include <sys/file.h>
18 #include <sys/wait.h>
19 #include <signal.h>
21 /* compile with -DUSE_FLOCK to use flock() instead of fcntl() */
23 #if !defined(USE_FLOCK) && !defined(F_SETLKW)
24 # define USE_FLOCK
25 #endif
27 #ifndef __GNUC__
28 # ifndef __attribute__
29 # define __attribute__(x)
30 # endif
31 #endif
33 static char *progname;
34 static void
35 __attribute__((format(printf,3,4)))
36 __attribute__((noreturn))
37 error(int errnum, int exitcode, const char *fmt, ...) {
38 va_list ap;
39 fprintf(stderr, "%s: ", progname);
40 va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
41 if (errnum)
42 fprintf(stderr, ": %s\n", strerror(errnum));
43 else
44 fputs("\n", stderr);
45 exit(exitcode);
48 static const char *lckfile;
49 static int quiet;
51 static void sigalarm(int sig) {
52 if (quiet)
53 _exit(EX_TEMPFAIL);
54 error(0, EX_TEMPFAIL,
55 "lock file `%s' is already locked (timeout waiting)", lckfile);
58 int main(int argc, char **argv) {
59 int fd;
60 int c;
61 int create = O_CREAT;
62 int dofork = 1;
63 int waittime = 0;
64 int shared = 0;
65 int test = 0;
66 int fdn = -1;
67 #ifndef USE_FLOCK
68 struct flock fl;
69 #endif
71 if ((progname = strrchr(argv[0], '/')) == NULL)
72 progname = argv[0];
73 else
74 argv[0] = ++progname;
76 if (argc == 1) {
77 printf(
78 "%s: execute a program with a lock set.\n"
79 "Usage: %s [options] lockfile program [arguments]\n"
80 "where options are:\n"
81 " -w - if the lock is already held by another process,\n"
82 " wait for it to complete instead of failing immediately\n"
83 " -W sec - the same as -w but wait not more than sec seconds\n"
84 " -e - execute the program directly, no fork/wait\n"
85 " (keeps extra open file descriptor)\n"
86 " -E nnn - set the fd# to keep open in -e case (implies -e)\n"
87 " -n - do not create the lock file if it does not exist\n"
88 " -q - produce no output if lock is already held\n"
89 " -s - lock in shared (read) mode\n"
90 " -x - lock in exclusive (write) mode (default)\n"
91 " -t - test for lock existence"
92 #ifndef USE_FLOCK
93 " (just prints pid if any with -q)\n"
94 #endif
95 " (implies -n)\n"
96 , progname, progname);
97 return 0;
100 while ((c = getopt(argc, argv, "+wW:neE:sxtq")) != EOF) {
101 switch(c) {
102 case 'w':
103 if (!waittime)
104 waittime = -1;
105 break;
106 case 'W':
107 if ((waittime = atoi(optarg)) < 1)
108 error(0, EX_USAGE, "invalid wait time `%s'", optarg);
109 break;
110 case 't':
111 test = 1;
112 /* fall thru */
113 case 'n':
114 create = 0;
115 break;
116 case 'E':
117 if ((fdn = atoi(optarg)) < 0 || fdn == 2)
118 error(0, EX_USAGE, "invalid fd# `%s'", optarg);
119 /* fall thru */
120 case 'e':
121 dofork = 0;
122 break;
123 case 's':
124 shared = 1;
125 break;
126 case 'x':
127 shared = 0;
128 break;
129 case 'q':
130 quiet = 1;
131 break;
132 default:
133 return EX_USAGE;
137 argc -= optind; argv += optind;
138 if (!argc || (!test && argc < 2))
139 error(0, EX_USAGE, "too few arguments given");
141 lckfile = *argv++;
143 #ifdef USE_FLOCK
144 create |= O_RDONLY;
145 #else
146 if (!test)
147 create |= shared ? O_RDONLY : O_WRONLY;
148 #endif
149 fd = open(lckfile, create, 0666);
150 if (fd < 0) {
151 if (test && errno == ENOENT) {
152 if (!quiet)
153 printf("lockfile `%s' is not locked\n", lckfile);
154 return 0;
156 error(errno, EX_CANTCREAT, "unable to open `%s'", lckfile);
159 if (!test && fdn >= 0) {
160 /* dup it early to comply with stupid POSIX fcntl locking
161 * semantics */
162 if (dup2(fd, fdn) < 0)
163 error(errno, EX_OSERR, "dup2(%d,%d) failed", fd, fdn);
164 close(fd);
165 fd = fdn;
168 if (test)
169 waittime = 0;
170 else if (waittime > 0) {
171 alarm(waittime);
172 signal(SIGALRM, sigalarm);
174 #ifdef USE_FLOCK
175 c = flock(fd, (shared ? LOCK_SH : LOCK_EX) | (waittime ? 0 : LOCK_NB));
176 if (test && c < 0 &&
177 (errno == EWOULDBLOCK || errno == EAGAIN || errno == EACCES)) {
178 if (!quiet)
179 printf("lockfile `%s' is locked\n", lckfile);
180 else
181 printf("locked\n");
182 return EX_TEMPFAIL;
184 #else
185 memset(&fl, 0, sizeof(fl));
186 fl.l_type = shared ? F_RDLCK : F_WRLCK;
187 c = fcntl(fd, test ? F_GETLK : waittime ? F_SETLKW : F_SETLK, &fl);
188 if (test && c == 0) {
189 if (fl.l_type == F_UNLCK) {
190 if (!quiet)
191 printf("lockfile `%s' is not locked\n", lckfile);
192 return 0;
194 if (!quiet)
195 printf("lockfile `%s' is locked by process %d\n", lckfile, fl.l_pid);
196 else
197 printf("%d\n", fl.l_pid);
198 return EX_TEMPFAIL;
200 #endif
201 if (waittime > 0)
202 alarm(0);
203 if (c < 0) {
204 if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EACCES)
205 error(errno, EX_OSERR, "unable to lock `%s'", lckfile);
206 else if (quiet)
207 return EX_TEMPFAIL;
208 else
209 error(0, EX_TEMPFAIL, "lockfile `%s' is already locked", lckfile);
212 if (dofork) {
213 pid_t pid;
214 int flags = fcntl(fd, F_GETFD, 0);
215 if (flags < 0)
216 error(errno, EX_OSERR, "fcntl() failed");
217 fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
218 pid = fork();
219 if (pid < 0)
220 error(errno, EX_OSERR, "unable to fork");
221 else if (pid) {
222 if (wait(&c) < 0)
223 error(errno, EX_OSERR, "wait() failed");
224 if (WIFSIGNALED(c))
225 error(0, EX_SOFTWARE, "%s: %s", *argv,
226 strsignal(WTERMSIG(c)));
227 return WEXITSTATUS(c);
230 execvp(*argv, argv);
231 error(errno, EX_OSERR, "unable to execute %s", *argv);