Fix whitespace irregularities in code
[xapian.git] / xapian-core / backends / flint_lock.cc
blobcb3a8d3743975362414dbbbec7c8b1d589ad534e
1 /** @file flint_lock.cc
2 * @brief Flint-compatible database locking.
3 */
4 /* Copyright (C) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015 Olly Betts
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
22 #include <config.h>
24 #include "flint_lock.h"
26 #ifndef __WIN32__
27 #include "safeerrno.h"
29 #include "safefcntl.h"
30 #include <unistd.h>
31 #include <cstdlib>
32 #include <sys/types.h>
33 #include "safesyssocket.h"
34 #include <sys/wait.h>
35 #include <signal.h>
36 #include <cstring>
37 #endif
39 #include "closefrom.h"
40 #include "errno_to_string.h"
41 #include "omassert.h"
43 #ifdef __CYGWIN__
44 # include <cygwin/version.h>
45 # include <sys/cygwin.h>
46 #endif
48 #ifdef FLINTLOCK_USE_FLOCK
49 # include <sys/file.h>
50 #endif
52 #include "xapian/error.h"
54 using namespace std;
56 #ifndef F_OFD_SETLK
57 # ifdef __linux__
58 // Apparently defining _GNU_SOURCE should get us F_OFD_SETLK, etc, but that
59 // doesn't actually seem to work, so hard-code the known values.
60 # define F_OFD_GETLK 36
61 # define F_OFD_SETLK 37
62 # define F_OFD_SETLKW 38
63 # endif
64 #endif
66 FlintLock::reason
67 FlintLock::lock(bool exclusive, bool wait, string & explanation) {
68 // Currently we only support exclusive locks.
69 (void)exclusive;
70 Assert(exclusive);
71 #if defined __CYGWIN__ || defined __WIN32__
72 Assert(hFile == INVALID_HANDLE_VALUE);
73 #ifdef __CYGWIN__
74 char fnm[MAX_PATH];
75 #if CYGWIN_VERSION_API_MAJOR == 0 && CYGWIN_VERSION_API_MINOR < 181
76 cygwin_conv_to_win32_path(filename.c_str(), fnm);
77 #else
78 if (cygwin_conv_path(CCP_POSIX_TO_WIN_A|CCP_RELATIVE, filename.c_str(),
79 fnm, MAX_PATH) < 0) {
80 explanation.assign("cygwin_conv_path failed: ");
81 errno_to_string(errno, explanation);
82 return UNKNOWN;
84 #endif
85 #else
86 const char *fnm = filename.c_str();
87 #endif
88 retry:
89 // FIXME: Use LockFileEx() for locking, which would allow proper blocking
90 // and also byte-range locking for when we implement MVCC. But is there a
91 // way to interwork with the CreateFile()-based locking while doing so?
92 hFile = CreateFile(fnm, GENERIC_WRITE, FILE_SHARE_READ,
93 NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
94 if (hFile != INVALID_HANDLE_VALUE) return SUCCESS;
95 if (GetLastError() == ERROR_ALREADY_EXISTS) {
96 if (wait) {
97 Sleep(1000);
98 goto retry;
100 return INUSE;
102 explanation = string();
103 return UNKNOWN;
104 #elif defined FLINTLOCK_USE_FLOCK
105 // This is much simpler than using fcntl() due to saner semantics around
106 // releasing locks when closing other descriptors on the same file (at
107 // least on platforms where flock() isn't just a compatibility wrapper
108 // around fcntl()). We can't simply switch to this without breaking
109 // locking compatibility with previous releases, though it might be useful
110 // for porting to platforms without fcntl() locking. Also, flock()
111 // apparently doesn't work over NFS - perhaps that's OK, but we should at
112 // least check the failure mode.
113 Assert(fd == -1);
114 int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
115 if (lockfd < 0) {
116 // Couldn't open lockfile.
117 explanation.assign("Couldn't open lockfile: ");
118 errno_to_string(errno, explanation);
119 return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
122 int op = LOCK_EX;
123 if (!wait) op |= LOCK_NB;
124 while (flock(lockfd, op) == -1) {
125 if (errno != EINTR) {
126 // Lock failed - translate known errno values into a reason code.
127 close(lockfd);
128 switch (errno) {
129 case EWOULDBLOCK:
130 return INUSE;
131 case ENOLCK:
132 return UNSUPPORTED; // FIXME: what do we get for NFS?
133 default:
134 return UNKNOWN;
139 fd = lockfd;
140 return SUCCESS;
141 #else
142 Assert(fd == -1);
143 #if defined F_SETFD && defined FD_CLOEXEC
144 int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
145 #else
146 int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
147 #endif
148 if (lockfd < 0) {
149 // Couldn't open lockfile.
150 explanation.assign("Couldn't open lockfile: ");
151 errno_to_string(errno, explanation);
152 return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
155 #ifdef F_OFD_SETLK
156 // F_OFD_SETLK has exactly the semantics we want, so use it if it's
157 // available. Support was added in Linux 3.15, and there's work on
158 // getting it standardised via POSIX:
159 // http://austingroupbugs.net/view.php?id=768
161 // Use a static flag so we don't repeatedly try F_OFD_SETLK when
162 // the kernel in use doesn't support it. This should be safe in a
163 // threaded context - at worst multiple threads might end up trying
164 // F_OFD_SETLK and then setting f_ofd_setlk_fails to true.
165 static bool f_ofd_setlk_fails = false;
166 if (!f_ofd_setlk_fails) {
167 struct flock fl;
168 fl.l_type = F_WRLCK;
169 fl.l_whence = SEEK_SET;
170 fl.l_start = 0;
171 fl.l_len = 1;
172 fl.l_pid = 0;
173 while (fcntl(lockfd, wait ? F_OFD_SETLKW : F_OFD_SETLK, &fl) == -1) {
174 if (errno != EINTR) {
175 if (errno == EINVAL) {
176 // F_OFD_SETLK not supported by this kernel.
177 goto no_ofd_support;
179 // Lock failed - translate known errno values into a reason
180 // code.
181 int e = errno;
182 close(lockfd);
183 switch (e) {
184 case EACCES: case EAGAIN:
185 return INUSE;
186 case ENOLCK:
187 return UNSUPPORTED;
188 default:
189 return UNKNOWN;
193 fd = lockfd;
194 pid = 0;
195 return SUCCESS;
196 no_ofd_support:
197 f_ofd_setlk_fails = true;
199 #endif
201 // If stdin and/or stdout have been closed, it is possible that lockfd could
202 // be 0 or 1. We need fds 0 and 1 to be available in the child process to
203 // be stdin and stdout, and we can't use dup() on lockfd after locking it,
204 // as the lock won't be transferred, so we handle this corner case here by
205 // using F_DUPFD or by calling dup() once or twice so that lockfd >= 2.
206 if (rare(lockfd < 2)) {
207 // Note this temporarily requires one or two spare fds to work, but
208 // then we need two spare for socketpair() to succeed below anyway.
209 #ifdef F_DUPFD
210 // Where available, F_DUPFD allows us to directly get the first unused
211 // fd which is at least 2.
212 int lockfd_dup = fcntl(lockfd, F_DUPFD, 2);
213 int eno = errno;
214 close(lockfd);
215 if (lockfd_dup < 0) {
216 return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
218 lockfd = lockfd_dup;
219 #else
220 // Otherwise we have to call dup() until we get one, though at least
221 // that's only at most twice.
222 int lockfd_dup = dup(lockfd);
223 if (rare(lockfd_dup < 2)) {
224 int eno = 0;
225 if (lockfd_dup < 0) {
226 eno = errno;
227 close(lockfd);
228 } else {
229 int lockfd_dup2 = dup(lockfd);
230 if (lockfd_dup2 < 0) {
231 eno = errno;
233 close(lockfd);
234 close(lockfd_dup);
235 lockfd = lockfd_dup2;
237 if (eno) {
238 return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
240 } else {
241 close(lockfd);
242 lockfd = lockfd_dup;
244 #endif
247 int fds[2];
248 if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
249 // Couldn't create socketpair.
250 explanation.assign("Couldn't create socketpair: ");
251 errno_to_string(errno, explanation);
252 reason why = ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
253 (void)close(lockfd);
254 return why;
257 pid_t child = fork();
259 if (child == 0) {
260 // Child process.
261 close(fds[0]);
263 #if defined F_SETFD && defined FD_CLOEXEC
264 // Clear close-on-exec flag, if we set it when we called socketpair().
265 // Clearing it here means there's no window where another thread in the
266 // parent process could fork()+exec() and end up with this fd still
267 // open (assuming close-on-exec is supported).
269 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
270 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
271 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
272 if (SOCK_CLOEXEC != 0)
273 (void)fcntl(fds[1], F_SETFD, 0);
274 if (O_CLOEXEC != 0)
275 (void)fcntl(lockfd, F_SETFD, 0);
276 #endif
277 // Connect pipe to stdin and stdout.
278 dup2(fds[1], 0);
279 dup2(fds[1], 1);
281 // Make sure we don't hang on to open files which may get deleted but
282 // not have their disk space released until we exit. Close these
283 // before we try to get the lock because if one of them is open on
284 // the lock file then closing it after obtaining the lock would release
285 // the lock, which would be really bad.
286 for (int i = 2; i < lockfd; ++i) {
287 // Retry on EINTR; just ignore other errors (we'll get
288 // EBADF if the fd isn't open so that's OK).
289 while (close(i) < 0 && errno == EINTR) { }
291 closefrom(lockfd + 1);
293 reason why = SUCCESS;
295 struct flock fl;
296 fl.l_type = F_WRLCK;
297 fl.l_whence = SEEK_SET;
298 fl.l_start = 0;
299 fl.l_len = 1;
300 while (fcntl(lockfd, wait ? F_SETLKW : F_SETLK, &fl) == -1) {
301 if (errno != EINTR) {
302 // Lock failed - translate known errno values into a reason
303 // code.
304 if (errno == EACCES || errno == EAGAIN) {
305 why = INUSE;
306 } else if (errno == ENOLCK) {
307 why = UNSUPPORTED;
308 } else {
309 _exit(0);
311 break;
317 // Tell the parent if we got the lock, and if not, why not.
318 char ch = static_cast<char>(why);
319 while (write(1, &ch, 1) < 0) {
320 // EINTR means a signal interrupted us, so retry.
321 // Otherwise we're DOOMED! The best we can do is just exit
322 // and the parent process should get EOF and know the lock
323 // failed.
324 if (errno != EINTR) _exit(1);
326 if (why != SUCCESS) _exit(0);
329 // Make sure we don't block unmount of partition holding the current
330 // directory.
331 if (chdir("/") < 0) {
332 // We can't usefully do anything in response to an error, so just
333 // ignore it - the worst harm it can do is make it impossible to
334 // unmount a partition.
336 // We need the if statement because glibc's _FORTIFY_SOURCE mode
337 // gives a warning even if we cast the result to void.
340 // FIXME: use special statically linked helper instead of cat.
341 execl("/bin/cat", "/bin/cat", static_cast<void*>(NULL));
342 // Emulate cat ourselves (we try to avoid this to reduce VM overhead).
343 char ch;
344 while (read(0, &ch, 1) != 0) { /* Do nothing */ }
345 _exit(0);
348 close(lockfd);
349 close(fds[1]);
351 if (child == -1) {
352 // Couldn't fork.
353 explanation.assign("Couldn't fork: ");
354 errno_to_string(errno, explanation);
355 close(fds[0]);
356 return UNKNOWN;
359 reason why = UNKNOWN;
361 // Parent process.
362 while (true) {
363 char ch;
364 ssize_t n = read(fds[0], &ch, 1);
365 if (n == 1) {
366 why = static_cast<reason>(ch);
367 if (why != SUCCESS) break;
368 // Got the lock.
369 fd = fds[0];
370 pid = child;
371 return SUCCESS;
373 if (n == 0) {
374 // EOF means the lock failed.
375 explanation.assign("Got EOF reading from child process");
376 break;
378 if (errno != EINTR) {
379 // Treat unexpected errors from read() as failure to get the lock.
380 explanation.assign("Error reading from child process: ");
381 errno_to_string(errno, explanation);
382 break;
386 close(fds[0]);
388 int status;
389 while (waitpid(child, &status, 0) < 0) {
390 if (errno != EINTR) break;
393 return why;
394 #endif
397 void
398 FlintLock::release() {
399 #if defined __CYGWIN__ || defined __WIN32__
400 if (hFile == INVALID_HANDLE_VALUE) return;
401 CloseHandle(hFile);
402 hFile = INVALID_HANDLE_VALUE;
403 #elif defined FLINTLOCK_USE_FLOCK
404 if (fd < 0) return;
405 close(fd);
406 fd = -1;
407 #else
408 if (fd < 0) return;
409 close(fd);
410 fd = -1;
411 #ifdef F_OFD_SETLK
412 if (pid == 0) return;
413 #endif
414 // Kill the child process which is holding the lock. Use SIGKILL since
415 // that can't be caught or ignored (we used to use SIGHUP, but if the
416 // application has set that to SIG_IGN, the child process inherits that
417 // setting, which sometimes results in the child process not exiting -
418 // noted on Linux).
420 // The only likely error from kill is ESRCH (pid doesn't exist). The other
421 // possibilities (according to the Linux man page) are EINVAL (invalid
422 // signal) and EPERM (don't have permission to SIGKILL the process) but in
423 // none of the cases does calling waitpid do us any good!
424 if (kill(pid, SIGKILL) == 0) {
425 int status;
426 while (waitpid(pid, &status, 0) < 0) {
427 if (errno != EINTR) break;
430 #endif
433 void
434 FlintLock::throw_databaselockerror(FlintLock::reason why,
435 const string & db_dir,
436 const string & explanation)
438 string msg("Unable to get write lock on ");
439 msg += db_dir;
440 if (why == FlintLock::INUSE) {
441 msg += ": already locked";
442 } else if (why == FlintLock::UNSUPPORTED) {
443 msg += ": locking probably not supported by this FS";
444 } else if (why == FlintLock::FDLIMIT) {
445 msg += ": too many open files";
446 } else if (why == FlintLock::UNKNOWN) {
447 if (!explanation.empty())
448 msg += ": " + explanation;
450 throw Xapian::DatabaseLockError(msg);