File: flint_lock.cc

package info (click to toggle)
xapian-core 1.2.12-2%2Bdeb7u1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 19,180 kB
  • sloc: cpp: 100,708; sh: 11,201; ansic: 8,177; makefile: 813; perl: 758; python: 40
file content (305 lines) | stat: -rw-r--r-- 8,867 bytes parent folder | download
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);
}