]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.bin/lockf/lockf.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.bin / lockf / lockf.c
1 /*
2  * Copyright (C) 1997 John D. Polstra.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY JOHN D. POLSTRA AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL JOHN D. POLSTRA OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
28
29 #include <sys/types.h>
30 #include <sys/wait.h>
31
32 #include <err.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <sysexits.h>
39 #include <unistd.h>
40
41 static int acquire_lock(const char *name, int flags);
42 static void cleanup(void);
43 static void killed(int sig);
44 static void timeout(int sig);
45 static void usage(void);
46 static void wait_for_lock(const char *name);
47
48 static const char *lockname;
49 static int lockfd = -1;
50 static int keep;
51 static volatile sig_atomic_t timed_out;
52
53 /*
54  * Execute an arbitrary command while holding a file lock.
55  */
56 int
57 main(int argc, char **argv)
58 {
59         int ch, flags, silent, status, waitsec;
60         pid_t child;
61
62         silent = keep = 0;
63         flags = O_CREAT;
64         waitsec = -1;   /* Infinite. */
65         while ((ch = getopt(argc, argv, "sknt:")) != -1) {
66                 switch (ch) {
67                 case 'k':
68                         keep = 1;
69                         break;
70                 case 'n':
71                         flags &= ~O_CREAT;
72                         break;
73                 case 's':
74                         silent = 1;
75                         break;
76                 case 't':
77                 {
78                         char *endptr;
79                         waitsec = strtol(optarg, &endptr, 0);
80                         if (*optarg == '\0' || *endptr != '\0' || waitsec < 0)
81                                 errx(EX_USAGE,
82                                     "invalid timeout \"%s\"", optarg);
83                 }
84                         break;
85                 default:
86                         usage();
87                 }
88         }
89         if (argc - optind < 2)
90                 usage();
91         lockname = argv[optind++];
92         argc -= optind;
93         argv += optind;
94         if (waitsec > 0) {              /* Set up a timeout. */
95                 struct sigaction act;
96
97                 act.sa_handler = timeout;
98                 sigemptyset(&act.sa_mask);
99                 act.sa_flags = 0;       /* Note that we do not set SA_RESTART. */
100                 sigaction(SIGALRM, &act, NULL);
101                 alarm(waitsec);
102         }
103         /*
104          * If the "-k" option is not given, then we must not block when
105          * acquiring the lock.  If we did, then the lock holder would
106          * unlink the file upon releasing the lock, and we would acquire
107          * a lock on a file with no directory entry.  Then another
108          * process could come along and acquire the same lock.  To avoid
109          * this problem, we separate out the actions of waiting for the
110          * lock to be available and of actually acquiring the lock.
111          *
112          * That approach produces behavior that is technically correct;
113          * however, it causes some performance & ordering problems for
114          * locks that have a lot of contention.  First, it is unfair in
115          * the sense that a released lock isn't necessarily granted to
116          * the process that has been waiting the longest.  A waiter may
117          * be starved out indefinitely.  Second, it creates a thundering
118          * herd situation each time the lock is released.
119          *
120          * When the "-k" option is used, the unlink race no longer
121          * exists.  In that case we can block while acquiring the lock,
122          * avoiding the separate step of waiting for the lock.  This
123          * yields fairness and improved performance.
124          */
125         lockfd = acquire_lock(lockname, flags | O_NONBLOCK);
126         while (lockfd == -1 && !timed_out && waitsec != 0) {
127                 if (keep)
128                         lockfd = acquire_lock(lockname, flags);
129                 else {
130                         wait_for_lock(lockname);
131                         lockfd = acquire_lock(lockname, flags | O_NONBLOCK);
132                 }
133         }
134         if (waitsec > 0)
135                 alarm(0);
136         if (lockfd == -1) {             /* We failed to acquire the lock. */
137                 if (silent)
138                         exit(EX_TEMPFAIL);
139                 errx(EX_TEMPFAIL, "%s: already locked", lockname);
140         }
141         /* At this point, we own the lock. */
142         if (atexit(cleanup) == -1)
143                 err(EX_OSERR, "atexit failed");
144         if ((child = fork()) == -1)
145                 err(EX_OSERR, "cannot fork");
146         if (child == 0) {       /* The child process. */
147                 close(lockfd);
148                 execvp(argv[0], argv);
149                 warn("%s", argv[0]);
150                 _exit(1);
151         }
152         /* This is the parent process. */
153         signal(SIGINT, SIG_IGN);
154         signal(SIGQUIT, SIG_IGN);
155         signal(SIGTERM, killed);
156         if (waitpid(child, &status, 0) == -1)
157                 err(EX_OSERR, "waitpid failed");
158         return (WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE);
159 }
160
161 /*
162  * Try to acquire a lock on the given file, creating the file if
163  * necessary.  The flags argument is O_NONBLOCK or 0, depending on
164  * whether we should wait for the lock.  Returns an open file descriptor
165  * on success, or -1 on failure.
166  */
167 static int
168 acquire_lock(const char *name, int flags)
169 {
170         int fd;
171
172         if ((fd = open(name, flags|O_RDONLY|O_EXLOCK|flags, 0666)) == -1) {
173                 if (errno == EAGAIN || errno == EINTR)
174                         return (-1);
175                 err(EX_CANTCREAT, "cannot open %s", name);
176         }
177         return (fd);
178 }
179
180 /*
181  * Remove the lock file.
182  */
183 static void
184 cleanup(void)
185 {
186
187         if (keep)
188                 flock(lockfd, LOCK_UN);
189         else
190                 unlink(lockname);
191 }
192
193 /*
194  * Signal handler for SIGTERM.  Cleans up the lock file, then re-raises
195  * the signal.
196  */
197 static void
198 killed(int sig)
199 {
200
201         cleanup();
202         signal(sig, SIG_DFL);
203         if (kill(getpid(), sig) == -1)
204                 err(EX_OSERR, "kill failed");
205 }
206
207 /*
208  * Signal handler for SIGALRM.
209  */
210 static void
211 timeout(int sig __unused)
212 {
213
214         timed_out = 1;
215 }
216
217 static void
218 usage(void)
219 {
220
221         fprintf(stderr,
222             "usage: lockf [-kns] [-t seconds] file command [arguments]\n");
223         exit(EX_USAGE);
224 }
225
226 /*
227  * Wait until it might be possible to acquire a lock on the given file.
228  * If the file does not exist, return immediately without creating it.
229  */
230 static void
231 wait_for_lock(const char *name)
232 {
233         int fd;
234
235         if ((fd = open(name, O_RDONLY|O_EXLOCK, 0666)) == -1) {
236                 if (errno == ENOENT || errno == EINTR)
237                         return;
238                 err(EX_CANTCREAT, "cannot open %s", name);
239         }
240         close(fd);
241 }