xapian-core  1.4.25
flint_lock.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017 Olly Betts
5  *
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.
10  *
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.
15  *
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
20  */
21 
22 #include <config.h>
23 
24 #include "flint_lock.h"
25 
26 #ifndef __WIN32__
27 #include <cerrno>
28 
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
38 
39 #include "closefrom.h"
40 #include "errno_to_string.h"
41 #include "omassert.h"
42 
43 #ifdef __CYGWIN__
44 # include <cygwin/version.h>
45 # include <sys/cygwin.h>
46 #endif
47 
48 #ifdef FLINTLOCK_USE_FLOCK
49 # include <sys/file.h>
50 #endif
51 
52 #include "xapian/error.h"
53 
54 using namespace std;
55 
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
65 
66 XAPIAN_NORETURN(static void throw_cannot_test_lock());
67 static void
69 {
70  throw Xapian::FeatureUnavailableError("Can't test lock without trying to "
71  "take it");
72 }
73 
74 bool
76 {
77  // A database which doesn't support update can't be locked for update.
78  if (filename.empty()) return false;
79 
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
83  // taking the 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
88  // taking the lock.
90 #else
91  if (fd != -1) return true;
92  int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
93  if (lockfd < 0) {
94  // Couldn't open lockfile.
95  reason why = ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
96  throw_databaselockerror(why, filename, "Testing lock");
97  }
98 
99  struct flock fl;
100  fl.l_type = F_WRLCK;
101  fl.l_whence = SEEK_SET;
102  fl.l_start = 0;
103  fl.l_len = 1;
104  fl.l_pid = 0;
105  while (fcntl(lockfd, F_GETLK, &fl) == -1) {
106  if (errno != EINTR) {
107  // Translate known errno values into a reason code.
108  int e = errno;
109  close(lockfd);
110  if (e == ENOSYS) {
111  // F_GETLK always failed with ENOSYS on older GNU Hurd libc
112  // versions: https://bugs.debian.org/190367
114  }
115  reason why = (e == ENOLCK ? UNSUPPORTED : UNKNOWN);
116  throw_databaselockerror(why, filename, "Testing lock");
117  }
118  }
119  close(lockfd);
120  return fl.l_type != F_UNLCK;
121 #endif
122 }
123 
125 FlintLock::lock(bool exclusive, bool wait, string & explanation) {
126  // Currently we only support exclusive locks.
127  (void)exclusive;
128  Assert(exclusive);
129 #if defined __CYGWIN__ || defined __WIN32__
130  Assert(hFile == INVALID_HANDLE_VALUE);
131 #ifdef __CYGWIN__
132  char fnm[MAX_PATH];
133 #if CYGWIN_VERSION_API_MAJOR == 0 && CYGWIN_VERSION_API_MINOR < 181
134  cygwin_conv_to_win32_path(filename.c_str(), fnm);
135 #else
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);
140  return UNKNOWN;
141  }
142 #endif
143 #else
144  const char *fnm = filename.c_str();
145 #endif
146 retry:
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) {
154  if (wait) {
155  Sleep(1000);
156  goto retry;
157  }
158  return INUSE;
159  }
160  explanation = string();
161  return UNKNOWN;
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.
169  //
170  // Also, flock() is problematic over NFS at least on Linux - it's been
171  // supported since Linux 2.6.12 but it's actually emulated by taking out an
172  // fcntl() byte-range lock on the entire file, which means that a process
173  // on the NFS server can get a (genuine) flock() lock on the same file a
174  // process on an NFS client has locked by flock() emulated as an fcntl()
175  // lock.
176  Assert(fd == -1);
177  int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
178  if (lockfd < 0) {
179  // Couldn't open lockfile.
180  explanation.assign("Couldn't open lockfile: ");
181  errno_to_string(errno, explanation);
182  return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
183  }
184 
185  int op = LOCK_EX;
186  if (!wait) op |= LOCK_NB;
187  while (flock(lockfd, op) == -1) {
188  if (errno != EINTR) {
189  // Lock failed - translate known errno values into a reason code.
190  close(lockfd);
191  switch (errno) {
192  case EWOULDBLOCK:
193  return INUSE;
194  case ENOLCK:
195  return UNSUPPORTED; // FIXME: what do we get for NFS?
196  default:
197  return UNKNOWN;
198  }
199  }
200  }
201 
202  fd = lockfd;
203  return SUCCESS;
204 #else
205  Assert(fd == -1);
206 #if defined F_SETFD && defined FD_CLOEXEC
207  int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
208 #else
209  int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
210 #endif
211  if (lockfd < 0) {
212  // Couldn't open lockfile.
213  explanation.assign("Couldn't open lockfile: ");
214  errno_to_string(errno, explanation);
215  return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
216  }
217 
218 #ifdef F_OFD_SETLK
219  // F_OFD_SETLK has exactly the semantics we want, so use it if it's
220  // available. Support was added in Linux 3.15, and it was accepted
221  // for POSIX issue 8 on 2022-12-15:
222  // https://austingroupbugs.net/view.php?id=768
223 
224  // Use a static flag so we don't repeatedly try F_OFD_SETLK when
225  // the kernel in use doesn't support it. This should be safe in a
226  // threaded context - at worst multiple threads might end up trying
227  // F_OFD_SETLK and then setting f_ofd_setlk_fails to true.
228  static bool f_ofd_setlk_fails = false;
229  if (!f_ofd_setlk_fails) {
230  struct flock fl;
231  fl.l_type = F_WRLCK;
232  fl.l_whence = SEEK_SET;
233  fl.l_start = 0;
234  fl.l_len = 1;
235  fl.l_pid = 0;
236  while (fcntl(lockfd, wait ? F_OFD_SETLKW : F_OFD_SETLK, &fl) == -1) {
237  if (errno != EINTR) {
238  if (errno == EINVAL) {
239  // F_OFD_SETLK not supported by this kernel.
240  goto no_ofd_support;
241  }
242  // Lock failed - translate known errno values into a reason
243  // code.
244  int e = errno;
245  close(lockfd);
246  switch (e) {
247  case EACCES: case EAGAIN:
248  return INUSE;
249  case ENOLCK:
250  return UNSUPPORTED;
251  default:
252  return UNKNOWN;
253  }
254  }
255  }
256  fd = lockfd;
257  pid = 0;
258  return SUCCESS;
259 no_ofd_support:
260  f_ofd_setlk_fails = true;
261  }
262 #endif
263 
264  // If stdin and/or stdout have been closed, it is possible that lockfd could
265  // be 0 or 1. We need fds 0 and 1 to be available in the child process to
266  // be stdin and stdout, and we can't use dup() on lockfd after locking it,
267  // as the lock won't be transferred, so we handle this corner case here by
268  // using F_DUPFD or by calling dup() once or twice so that lockfd >= 2.
269  if (rare(lockfd < 2)) {
270  // Note this temporarily requires one or two spare fds to work, but
271  // then we need two spare for socketpair() to succeed below anyway.
272 #ifdef F_DUPFD
273  // Where available, F_DUPFD allows us to directly get the first unused
274  // fd which is at least 2.
275  int lockfd_dup = fcntl(lockfd, F_DUPFD, 2);
276  int eno = errno;
277  close(lockfd);
278  if (lockfd_dup < 0) {
279  return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
280  }
281  lockfd = lockfd_dup;
282 #else
283  // Otherwise we have to call dup() until we get one, though at least
284  // that's only at most twice.
285  int lockfd_dup = dup(lockfd);
286  if (rare(lockfd_dup < 2)) {
287  int eno = 0;
288  if (lockfd_dup < 0) {
289  eno = errno;
290  close(lockfd);
291  } else {
292  int lockfd_dup2 = dup(lockfd);
293  if (lockfd_dup2 < 0) {
294  eno = errno;
295  }
296  close(lockfd);
297  close(lockfd_dup);
298  lockfd = lockfd_dup2;
299  }
300  if (eno) {
301  return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
302  }
303  } else {
304  close(lockfd);
305  lockfd = lockfd_dup;
306  }
307 #endif
308  }
309 
310  int fds[2];
311  if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
312  // Couldn't create socketpair.
313  explanation.assign("Couldn't create socketpair: ");
314  errno_to_string(errno, explanation);
315  reason why = ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
316  (void)close(lockfd);
317  return why;
318  }
319 
320  pid_t child = fork();
321 
322  if (child == 0) {
323  // Child process.
324  close(fds[0]);
325 
326 #if defined F_SETFD && defined FD_CLOEXEC
327  // Clear close-on-exec flag, if we set it when we called socketpair().
328  // Clearing it here means there's no window where another thread in the
329  // parent process could fork()+exec() and end up with this fd still
330  // open (assuming close-on-exec is supported).
331  //
332  // We can't use a preprocessor check on the *value* of SOCK_CLOEXEC as
333  // on Linux SOCK_CLOEXEC is an enum, with '#define SOCK_CLOEXEC
334  // SOCK_CLOEXEC' to allow '#ifdef SOCK_CLOEXEC' to work.
335  if (SOCK_CLOEXEC != 0)
336  (void)fcntl(fds[1], F_SETFD, 0);
337  if (O_CLOEXEC != 0)
338  (void)fcntl(lockfd, F_SETFD, 0);
339 #endif
340  // Connect pipe to stdin and stdout.
341  dup2(fds[1], 0);
342  dup2(fds[1], 1);
343 
344  // Make sure we don't hang on to open files which may get deleted but
345  // not have their disk space released until we exit. Close these
346  // before we try to get the lock because if one of them is open on
347  // the lock file then closing it after obtaining the lock would release
348  // the lock, which would be really bad.
349  for (int i = 2; i < lockfd; ++i) {
350  // Retry on EINTR; just ignore other errors (we'll get
351  // EBADF if the fd isn't open so that's OK).
352  while (close(i) < 0 && errno == EINTR) { }
353  }
354  closefrom(lockfd + 1);
355 
356  reason why = SUCCESS;
357  {
358  struct flock fl;
359  fl.l_type = F_WRLCK;
360  fl.l_whence = SEEK_SET;
361  fl.l_start = 0;
362  fl.l_len = 1;
363  while (fcntl(lockfd, wait ? F_SETLKW : F_SETLK, &fl) == -1) {
364  if (errno != EINTR) {
365  // Lock failed - translate known errno values into a reason
366  // code.
367  if (errno == EACCES || errno == EAGAIN) {
368  why = INUSE;
369  } else if (errno == ENOLCK) {
370  why = UNSUPPORTED;
371  } else {
372  _exit(0);
373  }
374  break;
375  }
376  }
377  }
378 
379  {
380  // Tell the parent if we got the lock, and if not, why not.
381  char ch = static_cast<char>(why);
382  while (write(1, &ch, 1) < 0) {
383  // EINTR means a signal interrupted us, so retry.
384  // Otherwise we're DOOMED! The best we can do is just exit
385  // and the parent process should get EOF and know the lock
386  // failed.
387  if (errno != EINTR) _exit(1);
388  }
389  if (why != SUCCESS) _exit(0);
390  }
391 
392  // Make sure we don't block unmount of partition holding the current
393  // directory.
394  if (chdir("/") < 0) {
395  // We can't usefully do anything in response to an error, so just
396  // ignore it - the worst harm it can do is make it impossible to
397  // unmount a partition.
398  //
399  // We need the if statement because glibc's _FORTIFY_SOURCE mode
400  // gives a warning even if we cast the result to void.
401  }
402 
403  // FIXME: use special statically linked helper instead of cat.
404  execl("/bin/cat", "/bin/cat", static_cast<void*>(NULL));
405  // Emulate cat ourselves (we try to avoid this to reduce VM overhead).
406  char ch;
407  while (read(0, &ch, 1) != 0) {
408  /* Do nothing */
409  }
410  _exit(0);
411  }
412 
413  close(lockfd);
414  close(fds[1]);
415 
416  if (child == -1) {
417  // Couldn't fork.
418  explanation.assign("Couldn't fork: ");
419  errno_to_string(errno, explanation);
420  close(fds[0]);
421  return UNKNOWN;
422  }
423 
424  reason why = UNKNOWN;
425 
426  // Parent process.
427  while (true) {
428  char ch;
429  ssize_t n = read(fds[0], &ch, 1);
430  if (n == 1) {
431  why = static_cast<reason>(ch);
432  if (why != SUCCESS) break;
433  // Got the lock.
434  fd = fds[0];
435  pid = child;
436  return SUCCESS;
437  }
438  if (n == 0) {
439  // EOF means the lock failed.
440  explanation.assign("Got EOF reading from child process");
441  break;
442  }
443  if (errno != EINTR) {
444  // Treat unexpected errors from read() as failure to get the lock.
445  explanation.assign("Error reading from child process: ");
446  errno_to_string(errno, explanation);
447  break;
448  }
449  }
450 
451  close(fds[0]);
452 
453  int status;
454  while (waitpid(child, &status, 0) < 0) {
455  if (errno != EINTR) break;
456  }
457 
458  return why;
459 #endif
460 }
461 
462 void
464 #if defined __CYGWIN__ || defined __WIN32__
465  if (hFile == INVALID_HANDLE_VALUE) return;
466  CloseHandle(hFile);
467  hFile = INVALID_HANDLE_VALUE;
468 #elif defined FLINTLOCK_USE_FLOCK
469  if (fd < 0) return;
470  close(fd);
471  fd = -1;
472 #else
473  if (fd < 0) return;
474  close(fd);
475  fd = -1;
476 #ifdef F_OFD_SETLK
477  if (pid == 0) return;
478 #endif
479  // Kill the child process which is holding the lock. Use SIGKILL since
480  // that can't be caught or ignored (we used to use SIGHUP, but if the
481  // application has set that to SIG_IGN, the child process inherits that
482  // setting, which sometimes results in the child process not exiting -
483  // noted on Linux).
484  //
485  // The only likely error from kill is ESRCH (pid doesn't exist). The other
486  // possibilities (according to the Linux man page) are EINVAL (invalid
487  // signal) and EPERM (don't have permission to SIGKILL the process) but in
488  // none of the cases does calling waitpid do us any good!
489  if (kill(pid, SIGKILL) == 0) {
490  int status;
491  while (waitpid(pid, &status, 0) < 0) {
492  if (errno != EINTR) break;
493  }
494  }
495 #endif
496 }
497 
498 void
500  const string & db_dir,
501  const string & explanation) const
502 {
503  string msg("Unable to get write lock on ");
504  msg += db_dir;
505  if (why == FlintLock::INUSE) {
506  msg += ": already locked";
507  } else if (why == FlintLock::UNSUPPORTED) {
508  msg += ": locking probably not supported by this FS";
509  } else if (why == FlintLock::FDLIMIT) {
510  msg += ": too many open files";
511  } else if (why == FlintLock::UNKNOWN) {
512  if (!explanation.empty())
513  msg += ": " + explanation;
514  }
515  throw Xapian::DatabaseLockError(msg);
516 }
int close(FD &fd)
Definition: fd.h:63
void throw_databaselockerror(FlintLock::reason why, const std::string &db_dir, const std::string &explanation) const
Throw Xapian::DatabaseLockError.
Definition: flint_lock.cc:499
#define Assert(COND)
Definition: omassert.h:122
void release()
Release the lock.
Definition: flint_lock.cc:463
void closefrom(int fd)
Definition: closefrom.cc:89
include <sys/socket.h> with portability workarounds.
Convert errno value to std::string, thread-safe if possible.
Implementation of closefrom() function.
WritableDatabase open()
Construct a WritableDatabase object for a new, empty InMemory database.
Definition: dbfactory.h:104
STL namespace.
Flint-compatible database locking.
#define O_CLOEXEC
Definition: safefcntl.h:90
bool test() const
Test if the lock is held.
Definition: flint_lock.cc:75
#define rare(COND)
Definition: config.h:565
#define SOCK_CLOEXEC
Definition: safesyssocket.h:83
Hierarchy of classes which Xapian can throw as exceptions.
DatabaseLockError indicates failure to lock a database.
Definition: error.h:493
void errno_to_string(int e, string &s)
Indicates an attempt to use a feature which is unavailable.
Definition: error.h:719
static void throw_cannot_test_lock()
Definition: flint_lock.cc:68
reason lock(bool exclusive, bool wait, std::string &explanation)
Attempt to obtain the lock.
Definition: flint_lock.cc:125
Various assertion macros.
include <fcntl.h>, but working around broken platforms.