Branch data Line data Source code
1 : : /** @file backendmanager_remotetcp.cc
2 : : * @brief BackendManager subclass for remotetcp databases.
3 : : */
4 : : /* Copyright (C) 2006,2007,2008,2009 Olly Betts
5 : : * Copyright (C) 2008 Lemur Consulting Ltd
6 : : *
7 : : * This program is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU General Public License as
9 : : * published by the Free Software Foundation; either version 2 of the
10 : : * License, or (at your option) any later version.
11 : : *
12 : : * This program is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this program; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 : : */
21 : :
22 : : #include <config.h>
23 : :
24 : : #include "backendmanager_remotetcp.h"
25 : :
26 : : #include <xapian.h>
27 : :
28 : : #include "safeerrno.h"
29 : : #include <stdio.h> // For fdopen().
30 : : #include <cstring>
31 : :
32 : : #ifdef HAVE_FORK
33 : : # include <signal.h>
34 : : # include <sys/types.h>
35 : : # include <sys/socket.h>
36 : : # include <sys/wait.h>
37 : : # include <unistd.h>
38 : : // Some older systems had SIGCLD rather than SIGCHLD.
39 : : # if !defined SIGCHLD && defined SIGCLD
40 : : # define SIGCHLD SIGCLD
41 : : # endif
42 : : #endif
43 : :
44 : : #ifdef __WIN32__
45 : : # include <io.h> // For _open_osfhandle().
46 : : # include "safefcntl.h"
47 : : # include "safewindows.h"
48 : : #endif
49 : :
50 : : #include "noreturn.h"
51 : : #include "str.h"
52 : :
53 : : #include <string>
54 : : #include <vector>
55 : :
56 : : #ifdef HAVE_VALGRIND
57 : : # include <valgrind/memcheck.h>
58 : : #endif
59 : :
60 : : using namespace std;
61 : :
62 : : // We've had problems on some hosts which run tinderbox tests with "localhost"
63 : : // not being set in /etc/hosts - using the IP address equivalent seems more
64 : : // reliable.
65 : : #define LOCALHOST "127.0.0.1"
66 : :
67 : : // Start at DEFAULT port and try higher ports until one isn't already in use.
68 : : #define DEFAULT_PORT 1239
69 : :
70 : : #ifdef HAVE_FORK
71 : :
72 : : // We can't dynamically allocate memory for this because it confuses the leak
73 : : // detector. We only have 1-3 child fds open at once anyway, so a fixed size
74 : : // array isn't a problem, and linear scanning isn't a problem either.
75 : : struct pid_fd {
76 : : pid_t pid;
77 : : int fd;
78 : : };
79 : :
80 : : static pid_fd pid_to_fd[16];
81 : :
82 : : extern "C" {
83 : :
84 : : static void
85 : 296 : on_SIGCHLD(int /*sig*/)
86 : : {
87 : : int status;
88 : : pid_t child;
89 [ + + ]: 578 : while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
90 [ + - ]: 304 : for (unsigned i = 0; i < sizeof(pid_to_fd) / sizeof(pid_fd); ++i) {
91 [ + + ]: 304 : if (pid_to_fd[i].pid == child) {
92 : 282 : int fd = pid_to_fd[i].fd;
93 : 282 : pid_to_fd[i].fd = 0;
94 : 282 : pid_to_fd[i].pid = 0;
95 : : // NB close() *is* safe to use in a signal handler.
96 : 282 : close(fd);
97 : 282 : break;
98 : : }
99 : : }
100 : : }
101 : 296 : }
102 : :
103 : : }
104 : :
105 : : static int
106 : 771 : launch_xapian_tcpsrv(const string & args)
107 : : {
108 : 771 : int port = DEFAULT_PORT;
109 : :
110 : : // We want to be able to get the exit status of the child process we fork
111 : : // if xapian-tcpsrv doesn't start listening successfully.
112 : 771 : signal(SIGCHLD, SIG_DFL);
113 : : try_next_port:
114 : 771 : string cmd = XAPIAN_TCPSRV" --one-shot --interface "LOCALHOST" --port " + str(port) + " " + args;
115 : : #ifdef HAVE_VALGRIND
116 : : if (RUNNING_ON_VALGRIND) cmd = "./runsrv " + cmd;
117 : : #endif
118 : : int fds[2];
119 [ - + ]: 771 : if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0) {
120 : 0 : string msg("Couldn't create socketpair: ");
121 : 0 : msg += strerror(errno);
122 : 0 : throw msg;
123 : : }
124 : :
125 : 771 : pid_t child = fork();
126 [ - + ]: 771 : if (child == 0) {
127 : : // Child process.
128 : 0 : close(fds[0]);
129 : : // Connect stdout and stderr to the socket.
130 : 0 : dup2(fds[1], 1);
131 : 0 : dup2(fds[1], 2);
132 : 0 : execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), (void*)NULL);
133 : 0 : _exit(-1);
134 : : }
135 : :
136 : 771 : close(fds[1]);
137 [ - + ]: 771 : if (child == -1) {
138 : : // Couldn't fork.
139 : 0 : int fork_errno = errno;
140 : 0 : close(fds[0]);
141 : 0 : string msg("Couldn't fork: ");
142 : 0 : msg += strerror(fork_errno);
143 : 0 : throw msg;
144 : : }
145 : :
146 : : // Parent process.
147 : :
148 : : // Wrap the file descriptor in a FILE * so we can read lines using fgets().
149 : 771 : FILE * fh = fdopen(fds[0], "r");
150 [ - + ]: 771 : if (fh == NULL) {
151 : 0 : string msg("Failed to run command '");
152 : 0 : msg += cmd;
153 : 0 : msg += "': ";
154 : 0 : msg += strerror(errno);
155 : 0 : throw msg;
156 : : }
157 : :
158 : 771 : string output;
159 : 771 : while (true) {
160 : : char buf[256];
161 [ - + ]: 1542 : if (fgets(buf, sizeof(buf), fh) == NULL) {
162 : 0 : fclose(fh);
163 : : // Wait for the child to exit.
164 : : int status;
165 [ # # ]: 0 : if (waitpid(child, &status, 0) == -1) {
166 : 0 : string msg("waitpid failed: ");
167 : 0 : msg += strerror(errno);
168 : 0 : throw msg;
169 : : }
170 [ # # ][ # # ]: 0 : if (++port < 65536 && status != 0) {
[ # # ]
171 [ # # ][ # # ]: 0 : if (WIFEXITED(status) && WEXITSTATUS(status) == 69) {
172 : : // 69 is EX_UNAVAILABLE which xapian-tcpsrv exits
173 : : // with if (and only if) the port specified was
174 : : // in use.
175 : : goto try_next_port;
176 : : }
177 : : }
178 : 0 : string msg("Failed to get 'Listening...' from command '");
179 : 0 : msg += cmd;
180 : 0 : msg += "' (output: ";
181 : 0 : msg += output;
182 : 0 : msg += ")";
183 : 0 : throw msg;
184 : : }
185 [ + + ]: 1542 : if (strcmp(buf, "Listening...\n") == 0) break;
186 : 771 : output += buf;
187 : : }
188 : :
189 : : // dup() the fd we wrapped with fdopen() so we can keep it open so the
190 : : // xapian-tcpsrv keeps running.
191 : 771 : int tracked_fd = dup(fds[0]);
192 : :
193 : : // We must fclose() the FILE* to avoid valgrind detecting memory leaks from
194 : : // its buffers.
195 : 771 : fclose(fh);
196 : :
197 : : // Find a slot to track the pid->fd mapping in. If we can't find a slot
198 : : // it just means we'll leak the fd, so don't worry about that too much.
199 [ + - ]: 910 : for (unsigned i = 0; i < sizeof(pid_to_fd) / sizeof(pid_fd); ++i) {
200 [ + + ]: 910 : if (pid_to_fd[i].pid == 0) {
201 : 771 : pid_to_fd[i].fd = tracked_fd;
202 : 771 : pid_to_fd[i].pid = child;
203 : 771 : break;
204 : : }
205 : : }
206 : :
207 : : // Set a signal handler to clean up the xapian-tcpsrv child process when it
208 : : // finally exits.
209 : 771 : signal(SIGCHLD, on_SIGCHLD);
210 : :
211 [ - + ][ - + ]: 771 : return port;
212 : : }
213 : :
214 : : #elif defined __WIN32__
215 : :
216 : : XAPIAN_NORETURN(static void win32_throw_error_string(const char * str));
217 : : static void win32_throw_error_string(const char * str)
218 : : {
219 : : string msg(str);
220 : : char * error = 0;
221 : : DWORD len;
222 : : len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,
223 : : 0, GetLastError(), 0, (CHAR*)&error, 0, 0);
224 : : if (error) {
225 : : // Remove any trailing \r\n from output of FormatMessage.
226 : : if (len >= 2 && error[len - 2] == '\r' && error[len - 1] == '\n')
227 : : len -= 2;
228 : : if (len) {
229 : : msg += ": ";
230 : : msg.append(error, len);
231 : : }
232 : : LocalFree(error);
233 : : }
234 : : throw msg;
235 : : }
236 : :
237 : : // This implementation uses the WIN32 API to start xapian-tcpsrv as a child
238 : : // process and read its output using a pipe.
239 : : static int
240 : : launch_xapian_tcpsrv(const string & args)
241 : : {
242 : : int port = DEFAULT_PORT;
243 : :
244 : : try_next_port:
245 : : string cmd = XAPIAN_TCPSRV" --one-shot --interface "LOCALHOST" --port " + str(port) + " " + args;
246 : :
247 : : // Create a pipe so we can read stdout/stderr from the child process.
248 : : HANDLE hRead, hWrite;
249 : : if (!CreatePipe(&hRead, &hWrite, 0, 0))
250 : : win32_throw_error_string("Couldn't create pipe");
251 : :
252 : : // Set the write handle to be inherited by the child process.
253 : : SetHandleInformation(hWrite, HANDLE_FLAG_INHERIT, 1);
254 : :
255 : : // Create the child process.
256 : : PROCESS_INFORMATION procinfo;
257 : : memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
258 : :
259 : : STARTUPINFO startupinfo;
260 : : memset(&startupinfo, 0, sizeof(STARTUPINFO));
261 : : startupinfo.cb = sizeof(STARTUPINFO);
262 : : startupinfo.hStdError = hWrite;
263 : : startupinfo.hStdOutput = hWrite;
264 : : startupinfo.hStdInput = INVALID_HANDLE_VALUE;
265 : : startupinfo.dwFlags |= STARTF_USESTDHANDLES;
266 : :
267 : : // For some reason Windows wants a modifiable copy!
268 : : BOOL ok;
269 : : char * cmdline = strdup(cmd.c_str());
270 : : ok = CreateProcess(0, cmdline, 0, 0, TRUE, 0, 0, 0, &startupinfo, &procinfo);
271 : : free(cmdline);
272 : : if (!ok)
273 : : win32_throw_error_string("Couldn't create child process");
274 : :
275 : : CloseHandle(hWrite);
276 : : CloseHandle(procinfo.hThread);
277 : :
278 : : string output;
279 : : FILE *fh = fdopen(_open_osfhandle((intptr_t)hRead, O_RDONLY), "r");
280 : : while (true) {
281 : : char buf[256];
282 : : if (fgets(buf, sizeof(buf), fh) == NULL) {
283 : : fclose(fh);
284 : : DWORD rc;
285 : : // This doesn't seem to be necessary on the machine I tested on,
286 : : // but I guess it could be on a slow machine...
287 : : while (GetExitCodeProcess(procinfo.hProcess, &rc) && rc == STILL_ACTIVE) {
288 : : Sleep(100);
289 : : }
290 : : CloseHandle(procinfo.hProcess);
291 : : if (++port < 65536 && rc == 69) {
292 : : // 69 is EX_UNAVAILABLE which xapian-tcpsrv exits
293 : : // with if (and only if) the port specified was
294 : : // in use.
295 : : goto try_next_port;
296 : : }
297 : : string msg("Failed to get 'Listening...' from command '");
298 : : msg += cmd;
299 : : msg += "' (output: ";
300 : : msg += output;
301 : : msg += ")";
302 : : throw msg;
303 : : }
304 : : if (strcmp(buf, "Listening...\r\n") == 0) break;
305 : : output += buf;
306 : : }
307 : : fclose(fh);
308 : :
309 : : return port;
310 : : }
311 : :
312 : : #else
313 : : # error Neither HAVE_FORK nor __WIN32__ is defined
314 : : #endif
315 : :
316 : 3 : BackendManagerRemoteTcp::~BackendManagerRemoteTcp() {
317 : 3 : BackendManagerRemoteTcp::clean_up();
318 [ # # ][ - + ]: 3 : }
[ # # ]
319 : :
320 : : std::string
321 : 69 : BackendManagerRemoteTcp::get_dbtype() const
322 : : {
323 : 69 : return "remotetcp_" + remote_type;
324 : : }
325 : :
326 : : Xapian::Database
327 : 537 : BackendManagerRemoteTcp::do_get_database(const vector<string> & files)
328 : : {
329 : : // Default to a long (5 minute) timeout so that tests won't fail just
330 : : // because the host is slow or busy.
331 : 537 : return BackendManagerRemoteTcp::get_remote_database(files, 300000);
332 : : }
333 : :
334 : : Xapian::WritableDatabase
335 : 204 : BackendManagerRemoteTcp::get_writable_database(const string & name,
336 : : const string & file)
337 : : {
338 : 204 : string args = get_writable_database_args(name, file);
339 : 204 : int port = launch_xapian_tcpsrv(args);
340 : 204 : return Xapian::Remote::open_writable(LOCALHOST, port);
341 : : }
342 : :
343 : : Xapian::Database
344 : 540 : BackendManagerRemoteTcp::get_remote_database(const vector<string> & files,
345 : : unsigned int timeout)
346 : : {
347 : 540 : string args = get_remote_database_args(files, timeout);
348 : 540 : int port = launch_xapian_tcpsrv(args);
349 : 540 : return Xapian::Remote::open(LOCALHOST, port);
350 : : }
351 : :
352 : : Xapian::Database
353 : 24 : BackendManagerRemoteTcp::get_writable_database_as_database()
354 : : {
355 : 24 : string args = get_writable_database_as_database_args();
356 : 24 : int port = launch_xapian_tcpsrv(args);
357 : 24 : return Xapian::Remote::open(LOCALHOST, port);
358 : : }
359 : :
360 : : Xapian::WritableDatabase
361 : 3 : BackendManagerRemoteTcp::get_writable_database_again()
362 : : {
363 : 3 : string args = get_writable_database_again_args();
364 : 3 : int port = launch_xapian_tcpsrv(args);
365 : 3 : return Xapian::Remote::open_writable(LOCALHOST, port);
366 : : }
367 : :
368 : : void
369 : 1289 : BackendManagerRemoteTcp::clean_up()
370 : : {
371 : : #ifdef HAVE_FORK
372 : 1289 : signal(SIGCHLD, SIG_DFL);
373 [ + + ]: 21913 : for (unsigned i = 0; i < sizeof(pid_to_fd) / sizeof(pid_fd); ++i) {
374 : 20624 : pid_t child = pid_to_fd[i].pid;
375 [ + + ]: 20624 : if (child) {
376 : : int status;
377 [ - + ][ # # ]: 489 : while (waitpid(child, &status, 0) == -1 && errno == EINTR) { }
[ - + ]
378 : : // Other possible error from waitpid is ECHILD, which it seems can
379 : : // only mean that the child has already exited and SIGCHLD was set
380 : : // to SIG_IGN. If we did somehow see that, the sanest response
381 : : // seems to be to close the fd and move on.
382 : 489 : int fd = pid_to_fd[i].fd;
383 : 489 : pid_to_fd[i].fd = 0;
384 : 489 : pid_to_fd[i].pid = 0;
385 : 489 : close(fd);
386 : : }
387 : : }
388 : : #endif
389 : 1289 : }
|