1 /** @file flint_lock.cc
2 * @brief Flint-compatible database locking.
4 /* Copyright (C) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017 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
24 #include "flint_lock.h"
27 #include "safeerrno.h"
29 #include "safefcntl.h"
32 #include <sys/types.h>
33 #include "safesyssocket.h"
39 #include "closefrom.h"
40 #include "errno_to_string.h"
44 # include <cygwin/version.h>
45 # include <sys/cygwin.h>
48 #ifdef FLINTLOCK_USE_FLOCK
49 # include <sys/file.h>
52 #include "xapian/error.h"
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
68 throw_cannot_test_lock()
70 throw Xapian::FeatureUnavailableError("Can't test lock without trying to "
75 FlintLock::test() const
77 // A database which doesn't support update can't be locked for update.
78 if (filename
.empty()) return false;
80 #if defined __CYGWIN__ || defined __WIN32__
81 if (hFile
!= INVALID_HANDLE_VALUE
) return true;
82 // Doesn't seem to be possible to check if the lock is held without briefly
84 throw_cannot_test_lock();
85 #elif defined FLINTLOCK_USE_FLOCK
86 if (fd
!= -1) return true;
87 // Doesn't seem to be possible to check if the lock is held without briefly
89 throw_cannot_test_lock();
91 if (fd
!= -1) return true;
92 int lockfd
= open(filename
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, 0666);
94 // Couldn't open lockfile.
95 reason why
= ((errno
== EMFILE
|| errno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
96 throw_databaselockerror(why
, filename
, "Testing lock");
101 fl
.l_whence
= SEEK_SET
;
105 while (fcntl(lockfd
, F_GETLK
, &fl
) == -1) {
106 if (errno
!= EINTR
) {
107 // Translate known errno values into a reason code.
111 // F_GETLK isn't implemented by GNU Hurd, and always fails with
112 // ENOSYS: https://bugs.debian.org/190367
113 throw_cannot_test_lock();
115 reason why
= (e
== ENOLCK
? UNSUPPORTED
: UNKNOWN
);
116 throw_databaselockerror(why
, filename
, "Testing lock");
120 return fl
.l_type
!= F_UNLCK
;
125 FlintLock::lock(bool exclusive
, bool wait
, string
& explanation
) {
126 // Currently we only support exclusive locks.
129 #if defined __CYGWIN__ || defined __WIN32__
130 Assert(hFile
== INVALID_HANDLE_VALUE
);
133 #if CYGWIN_VERSION_API_MAJOR == 0 && CYGWIN_VERSION_API_MINOR < 181
134 cygwin_conv_to_win32_path(filename
.c_str(), fnm
);
136 if (cygwin_conv_path(CCP_POSIX_TO_WIN_A
|CCP_RELATIVE
, filename
.c_str(),
137 fnm
, MAX_PATH
) < 0) {
138 explanation
.assign("cygwin_conv_path failed: ");
139 errno_to_string(errno
, explanation
);
144 const char *fnm
= filename
.c_str();
147 // FIXME: Use LockFileEx() for locking, which would allow proper blocking
148 // and also byte-range locking for when we implement MVCC. But is there a
149 // way to interwork with the CreateFile()-based locking while doing so?
150 hFile
= CreateFile(fnm
, GENERIC_WRITE
, FILE_SHARE_READ
,
151 NULL
, OPEN_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
152 if (hFile
!= INVALID_HANDLE_VALUE
) return SUCCESS
;
153 if (GetLastError() == ERROR_ALREADY_EXISTS
) {
160 explanation
= string();
162 #elif defined FLINTLOCK_USE_FLOCK
163 // This is much simpler than using fcntl() due to saner semantics around
164 // releasing locks when closing other descriptors on the same file (at
165 // least on platforms where flock() isn't just a compatibility wrapper
166 // around fcntl()). We can't simply switch to this without breaking
167 // locking compatibility with previous releases, though it might be useful
168 // for porting to platforms without fcntl() locking. Also, flock()
169 // apparently doesn't work over NFS - perhaps that's OK, but we should at
170 // least check the failure mode.
172 int lockfd
= open(filename
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, 0666);
174 // Couldn't open lockfile.
175 explanation
.assign("Couldn't open lockfile: ");
176 errno_to_string(errno
, explanation
);
177 return ((errno
== EMFILE
|| errno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
181 if (!wait
) op
|= LOCK_NB
;
182 while (flock(lockfd
, op
) == -1) {
183 if (errno
!= EINTR
) {
184 // Lock failed - translate known errno values into a reason code.
190 return UNSUPPORTED
; // FIXME: what do we get for NFS?
201 #if defined F_SETFD && defined FD_CLOEXEC
202 int lockfd
= open(filename
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, 0666);
204 int lockfd
= open(filename
.c_str(), O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
207 // Couldn't open lockfile.
208 explanation
.assign("Couldn't open lockfile: ");
209 errno_to_string(errno
, explanation
);
210 return ((errno
== EMFILE
|| errno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
214 // F_OFD_SETLK has exactly the semantics we want, so use it if it's
215 // available. Support was added in Linux 3.15, and there's work on
216 // getting it standardised via POSIX:
217 // http://austingroupbugs.net/view.php?id=768
219 // Use a static flag so we don't repeatedly try F_OFD_SETLK when
220 // the kernel in use doesn't support it. This should be safe in a
221 // threaded context - at worst multiple threads might end up trying
222 // F_OFD_SETLK and then setting f_ofd_setlk_fails to true.
223 static bool f_ofd_setlk_fails
= false;
224 if (!f_ofd_setlk_fails
) {
227 fl
.l_whence
= SEEK_SET
;
231 while (fcntl(lockfd
, wait
? F_OFD_SETLKW
: F_OFD_SETLK
, &fl
) == -1) {
232 if (errno
!= EINTR
) {
233 if (errno
== EINVAL
) {
234 // F_OFD_SETLK not supported by this kernel.
237 // Lock failed - translate known errno values into a reason
242 case EACCES
: case EAGAIN
:
255 f_ofd_setlk_fails
= true;
259 // If stdin and/or stdout have been closed, it is possible that lockfd could
260 // be 0 or 1. We need fds 0 and 1 to be available in the child process to
261 // be stdin and stdout, and we can't use dup() on lockfd after locking it,
262 // as the lock won't be transferred, so we handle this corner case here by
263 // using F_DUPFD or by calling dup() once or twice so that lockfd >= 2.
264 if (rare(lockfd
< 2)) {
265 // Note this temporarily requires one or two spare fds to work, but
266 // then we need two spare for socketpair() to succeed below anyway.
268 // Where available, F_DUPFD allows us to directly get the first unused
269 // fd which is at least 2.
270 int lockfd_dup
= fcntl(lockfd
, F_DUPFD
, 2);
273 if (lockfd_dup
< 0) {
274 return ((eno
== EMFILE
|| eno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
278 // Otherwise we have to call dup() until we get one, though at least
279 // that's only at most twice.
280 int lockfd_dup
= dup(lockfd
);
281 if (rare(lockfd_dup
< 2)) {
283 if (lockfd_dup
< 0) {
287 int lockfd_dup2
= dup(lockfd
);
288 if (lockfd_dup2
< 0) {
293 lockfd
= lockfd_dup2
;
296 return ((eno
== EMFILE
|| eno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
306 if (socketpair(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, PF_UNSPEC
, fds
) < 0) {
307 // Couldn't create socketpair.
308 explanation
.assign("Couldn't create socketpair: ");
309 errno_to_string(errno
, explanation
);
310 reason why
= ((errno
== EMFILE
|| errno
== ENFILE
) ? FDLIMIT
: UNKNOWN
);
315 pid_t child
= fork();
321 #if defined F_SETFD && defined FD_CLOEXEC
322 // Clear close-on-exec flag, if we set it when we called socketpair().
323 // Clearing it here means there's no window where another thread in the
324 // parent process could fork()+exec() and end up with this fd still
325 // open (assuming close-on-exec is supported).
327 // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
328 // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
329 // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
330 if (SOCK_CLOEXEC
!= 0)
331 (void)fcntl(fds
[1], F_SETFD
, 0);
333 (void)fcntl(lockfd
, F_SETFD
, 0);
335 // Connect pipe to stdin and stdout.
339 // Make sure we don't hang on to open files which may get deleted but
340 // not have their disk space released until we exit. Close these
341 // before we try to get the lock because if one of them is open on
342 // the lock file then closing it after obtaining the lock would release
343 // the lock, which would be really bad.
344 for (int i
= 2; i
< lockfd
; ++i
) {
345 // Retry on EINTR; just ignore other errors (we'll get
346 // EBADF if the fd isn't open so that's OK).
347 while (close(i
) < 0 && errno
== EINTR
) { }
349 closefrom(lockfd
+ 1);
351 reason why
= SUCCESS
;
355 fl
.l_whence
= SEEK_SET
;
358 while (fcntl(lockfd
, wait
? F_SETLKW
: F_SETLK
, &fl
) == -1) {
359 if (errno
!= EINTR
) {
360 // Lock failed - translate known errno values into a reason
362 if (errno
== EACCES
|| errno
== EAGAIN
) {
364 } else if (errno
== ENOLCK
) {
375 // Tell the parent if we got the lock, and if not, why not.
376 char ch
= static_cast<char>(why
);
377 while (write(1, &ch
, 1) < 0) {
378 // EINTR means a signal interrupted us, so retry.
379 // Otherwise we're DOOMED! The best we can do is just exit
380 // and the parent process should get EOF and know the lock
382 if (errno
!= EINTR
) _exit(1);
384 if (why
!= SUCCESS
) _exit(0);
387 // Make sure we don't block unmount of partition holding the current
389 if (chdir("/") < 0) {
390 // We can't usefully do anything in response to an error, so just
391 // ignore it - the worst harm it can do is make it impossible to
392 // unmount a partition.
394 // We need the if statement because glibc's _FORTIFY_SOURCE mode
395 // gives a warning even if we cast the result to void.
398 // FIXME: use special statically linked helper instead of cat.
399 execl("/bin/cat", "/bin/cat", static_cast<void*>(NULL
));
400 // Emulate cat ourselves (we try to avoid this to reduce VM overhead).
402 while (read(0, &ch
, 1) != 0) {
413 explanation
.assign("Couldn't fork: ");
414 errno_to_string(errno
, explanation
);
419 reason why
= UNKNOWN
;
424 ssize_t n
= read(fds
[0], &ch
, 1);
426 why
= static_cast<reason
>(ch
);
427 if (why
!= SUCCESS
) break;
434 // EOF means the lock failed.
435 explanation
.assign("Got EOF reading from child process");
438 if (errno
!= EINTR
) {
439 // Treat unexpected errors from read() as failure to get the lock.
440 explanation
.assign("Error reading from child process: ");
441 errno_to_string(errno
, explanation
);
449 while (waitpid(child
, &status
, 0) < 0) {
450 if (errno
!= EINTR
) break;
458 FlintLock::release() {
459 #if defined __CYGWIN__ || defined __WIN32__
460 if (hFile
== INVALID_HANDLE_VALUE
) return;
462 hFile
= INVALID_HANDLE_VALUE
;
463 #elif defined FLINTLOCK_USE_FLOCK
472 if (pid
== 0) return;
474 // Kill the child process which is holding the lock. Use SIGKILL since
475 // that can't be caught or ignored (we used to use SIGHUP, but if the
476 // application has set that to SIG_IGN, the child process inherits that
477 // setting, which sometimes results in the child process not exiting -
480 // The only likely error from kill is ESRCH (pid doesn't exist). The other
481 // possibilities (according to the Linux man page) are EINVAL (invalid
482 // signal) and EPERM (don't have permission to SIGKILL the process) but in
483 // none of the cases does calling waitpid do us any good!
484 if (kill(pid
, SIGKILL
) == 0) {
486 while (waitpid(pid
, &status
, 0) < 0) {
487 if (errno
!= EINTR
) break;
494 FlintLock::throw_databaselockerror(FlintLock::reason why
,
495 const string
& db_dir
,
496 const string
& explanation
) const
498 string
msg("Unable to get write lock on ");
500 if (why
== FlintLock::INUSE
) {
501 msg
+= ": already locked";
502 } else if (why
== FlintLock::UNSUPPORTED
) {
503 msg
+= ": locking probably not supported by this FS";
504 } else if (why
== FlintLock::FDLIMIT
) {
505 msg
+= ": too many open files";
506 } else if (why
== FlintLock::UNKNOWN
) {
507 if (!explanation
.empty())
508 msg
+= ": " + explanation
;
510 throw Xapian::DatabaseLockError(msg
);