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