1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
/** @file flint_lock.cc
* @brief Flint-compatible database locking.
*/
/* Copyright (C) 2005,2006,2007,2008,2009,2010,2011,2012 Olly Betts
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include <config.h>
#include "flint_lock.h"
#ifndef __WIN32__
#include "safeerrno.h"
#include "safefcntl.h"
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstring>
#endif
#include "closefrom.h"
#include "omassert.h"
#ifdef __CYGWIN__
#include <sys/cygwin.h>
#endif
#include "xapian/error.h"
using namespace std;
FlintLock::reason
FlintLock::lock(bool exclusive, string & explanation) {
// Currently we only support exclusive locks.
(void)exclusive;
Assert(exclusive);
#if defined __CYGWIN__ || defined __WIN32__
Assert(hFile == INVALID_HANDLE_VALUE);
#ifdef __CYGWIN__
char fnm[MAX_PATH];
cygwin_conv_to_win32_path(filename.c_str(), fnm);
#else
const char *fnm = filename.c_str();
#endif
hFile = CreateFile(fnm, GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) return SUCCESS;
if (GetLastError() == ERROR_ALREADY_EXISTS) return INUSE;
explanation = string();
return UNKNOWN;
#elif defined __EMX__
APIRET rc;
ULONG ulAction;
rc = DosOpen((PCSZ)filename.c_str(), &hFile, &ulAction, 0, FILE_NORMAL,
OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
OPEN_SHARE_DENYWRITE | OPEN_ACCESS_WRITEONLY,
NULL);
if (rc == NO_ERROR) return SUCCESS;
if (rc == ERROR_ACCESS_DENIED) return INUSE;
explanation = string();
return UNKNOWN;
#else
Assert(fd == -1);
int lockfd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (lockfd < 0) {
// Couldn't open lockfile.
explanation = string("Couldn't open lockfile: ") + strerror(errno);
return ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
}
// If stdin and/or stdout have been closed, it is possible that lockfd could
// be 0 or 1. We need fds 0 and 1 to be available in the child process to
// be stdin and stdout, and we can't use dup() on lockfd after locking it,
// as the lock won't be transferred, so we handle this corner case here by
// using dup() once or twice to get lockfd to be >= 2.
if (rare(lockfd < 2)) {
// Note this temporarily requires one or two spare fds to work, but
// then we need two spare for socketpair() to succeed below anyway.
int lockfd_dup = dup(lockfd);
if (rare(lockfd_dup < 2)) {
int eno = 0;
if (lockfd_dup < 0) {
eno = errno;
close(lockfd);
} else {
int lockfd_dup2 = dup(lockfd);
if (lockfd_dup2 < 0) {
eno = errno;
}
close(lockfd);
close(lockfd_dup);
lockfd = lockfd_dup2;
}
if (eno) {
return ((eno == EMFILE || eno == ENFILE) ? FDLIMIT : UNKNOWN);
}
} else {
close(lockfd);
lockfd = lockfd_dup;
}
}
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0) {
// Couldn't create socketpair.
explanation = string("Couldn't create socketpair: ") + strerror(errno);
reason why = ((errno == EMFILE || errno == ENFILE) ? FDLIMIT : UNKNOWN);
(void)close(lockfd);
return why;
}
pid_t child = fork();
if (child == 0) {
// Child process.
close(fds[0]);
// Connect pipe to stdin and stdout.
dup2(fds[1], 0);
dup2(fds[1], 1);
// Make sure we don't hang on to open files which may get deleted but
// not have their disk space released until we exit. Close these
// before we try to get the lock because if one of them is open on
// the lock file then closing it after obtaining the lock would release
// the lock, which would be really bad.
for (int i = 2; i < lockfd; ++i) {
// Retry on EINTR; just ignore other errors (we'll get
// EBADF if the fd isn't open so that's OK).
while (close(i) < 0 && errno == EINTR) { }
}
closefrom(lockfd + 1);
reason why = SUCCESS;
{
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
while (fcntl(lockfd, F_SETLK, &fl) == -1) {
if (errno != EINTR) {
// Lock failed - translate known errno values into a reason
// code.
if (errno == EACCES || errno == EAGAIN) {
why = INUSE;
} else if (errno == ENOLCK) {
why = UNSUPPORTED;
} else {
_exit(0);
}
break;
}
}
}
{
// Tell the parent if we got the lock, and if not, why not.
char ch = static_cast<char>(why);
while (write(1, &ch, 1) < 0) {
// EINTR means a signal interrupted us, so retry.
// Otherwise we're DOOMED! The best we can do is just exit
// and the parent process should get EOF and know the lock
// failed.
if (errno != EINTR) _exit(1);
}
if (why != SUCCESS) _exit(0);
}
// Make sure we don't block unmount() of partition holding the current
// directory.
if (chdir("/") < 0) {
// We can't usefully do anything in response to an error, so just
// ignore it - the worst harm it can do is make it impossible to
// unmount a partition.
//
// We need the if statement because glibc's _FORTIFY_SOURCE mode
// gives a warning even if we cast the result to void.
}
// FIXME: use special statically linked helper instead of cat.
execl("/bin/cat", "/bin/cat", static_cast<void*>(NULL));
// Emulate cat ourselves (we try to avoid this to reduce VM overhead).
char ch;
while (read(0, &ch, 1) != 0) { /* Do nothing */ }
_exit(0);
}
close(lockfd);
close(fds[1]);
if (child == -1) {
// Couldn't fork.
explanation = string("Couldn't fork: ") + strerror(errno);
close(fds[0]);
return UNKNOWN;
}
reason why = UNKNOWN;
// Parent process.
while (true) {
char ch;
ssize_t n = read(fds[0], &ch, 1);
if (n == 1) {
why = static_cast<reason>(ch);
if (why != SUCCESS) break;
// Got the lock.
fd = fds[0];
pid = child;
return SUCCESS;
}
if (n == 0) {
// EOF means the lock failed.
explanation.assign("Got EOF reading from child process");
break;
}
if (errno != EINTR) {
// Treat unexpected errors from read() as failure to get the lock.
explanation = string("Error reading from child process: ") + strerror(errno);
break;
}
}
close(fds[0]);
int status;
while (waitpid(child, &status, 0) < 0) {
if (errno != EINTR) break;
}
return why;
#endif
}
void
FlintLock::release() {
#if defined __CYGWIN__ || defined __WIN32__
if (hFile == INVALID_HANDLE_VALUE) return;
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
#elif defined __EMX__
if (hFile == NULLHANDLE) return;
DosClose(hFile);
hFile = NULLHANDLE;
#else
if (fd < 0) return;
close(fd);
fd = -1;
// Kill the child process which is holding the lock. Use SIGKILL since
// that can't be caught or ignored (we used to use SIGHUP, but if the
// application has set that to SIG_IGN, the child process inherits that
// setting, which sometimes results in the child process not exiting -
// noted on Linux).
//
// The only likely error from kill is ESRCH (pid doesn't exist). The other
// possibilities (according to the Linux man page) are EINVAL (invalid
// signal) and EPERM (don't have permission to SIGKILL the process) but in
// none of the cases does calling waitpid do us any good!
if (kill(pid, SIGKILL) == 0) {
int status;
while (waitpid(pid, &status, 0) < 0) {
if (errno != EINTR) break;
}
}
#endif
}
void
FlintLock::throw_databaselockerror(FlintLock::reason why,
const string & db_dir,
const string & explanation)
{
string msg("Unable to get write lock on ");
msg += db_dir;
if (why == FlintLock::INUSE) {
msg += ": already locked";
} else if (why == FlintLock::UNSUPPORTED) {
msg += ": locking probably not supported by this FS";
} else if (why == FlintLock::FDLIMIT) {
msg += ": too many open files";
} else if (why == FlintLock::UNKNOWN) {
if (!explanation.empty())
msg += ": " + explanation;
}
throw Xapian::DatabaseLockError(msg);
}
|