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