1 /** @file backendmanager_remotetcp.cc
2 * @brief BackendManager subclass for remotetcp databases.
4 /* Copyright (C) 2006,2007,2008,2009,2013,2015 Olly Betts
5 * Copyright (C) 2008 Lemur Consulting Ltd
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.
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.
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
24 #include "backendmanager_remotetcp.h"
28 #include <stdio.h> // For fdopen().
34 # include <sys/types.h>
35 # include "safesyssocket.h"
36 # include <sys/wait.h>
38 // Some older systems had SIGCLD rather than SIGCHLD.
39 # if !defined SIGCHLD && defined SIGCLD
40 # define SIGCHLD SIGCLD
45 # include <io.h> // For _open_osfhandle().
46 # include "safefcntl.h"
47 # include "safewindows.h"
48 # include <cstdlib> // For free().
57 # include <valgrind/memcheck.h>
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
65 #define LOCALHOST "127.0.0.1"
67 // Start at DEFAULT port and try higher ports until one isn't already in use.
68 #define DEFAULT_PORT 1239
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.
80 static pid_fd pid_to_fd
[16];
85 on_SIGCHLD(int /*sig*/)
89 while ((child
= waitpid(-1, &status
, WNOHANG
)) > 0) {
90 for (unsigned i
= 0; i
< sizeof(pid_to_fd
) / sizeof(pid_fd
); ++i
) {
91 if (pid_to_fd
[i
].pid
== child
) {
92 int fd
= pid_to_fd
[i
].fd
;
95 // NB close() *is* safe to use in a signal handler.
106 launch_xapian_tcpsrv(const string
& args
)
108 int port
= DEFAULT_PORT
;
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 signal(SIGCHLD
, SIG_DFL
);
114 string cmd
= XAPIAN_TCPSRV
" --one-shot --interface " LOCALHOST
" --port ";
119 if (RUNNING_ON_VALGRIND
) cmd
= "./runsrv " + cmd
;
122 if (socketpair(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, PF_UNSPEC
, fds
) < 0) {
123 string
msg("Couldn't create socketpair: ");
124 msg
+= strerror(errno
);
128 pid_t child
= fork();
132 // Connect stdout and stderr to the socket.
134 // Make sure the socket isn't fd 1 or 2. We need to ensure that
135 // FD_CLOEXEC isn't set for stdout or stderr (which creating them with
136 // dup2() achieves), and that we close fds[1]. The cleanest way to
137 // address this seems to be to turn the unusual situation into the
139 if (fds
[1] == 1 || fds
[1] == 2) {
146 execl("/bin/sh", "/bin/sh", "-c", cmd
.c_str(), static_cast<void*>(0));
153 int fork_errno
= errno
;
155 string
msg("Couldn't fork: ");
156 msg
+= strerror(fork_errno
);
162 // Wrap the file descriptor in a FILE * so we can read lines using fgets().
163 FILE * fh
= fdopen(fds
[0], "r");
165 string
msg("Failed to run command '");
168 msg
+= strerror(errno
);
175 if (fgets(buf
, sizeof(buf
), fh
) == NULL
) {
177 // Wait for the child to exit.
179 if (waitpid(child
, &status
, 0) == -1) {
180 string
msg("waitpid failed: ");
181 msg
+= strerror(errno
);
184 if (++port
< 65536 && status
!= 0) {
185 if (WIFEXITED(status
) && WEXITSTATUS(status
) == 69) {
186 // 69 is EX_UNAVAILABLE which xapian-tcpsrv exits
187 // with if (and only if) the port specified was
192 string
msg("Failed to get 'Listening...' from command '");
194 msg
+= "' (output: ";
199 if (strcmp(buf
, "Listening...\n") == 0) break;
203 // dup() the fd we wrapped with fdopen() so we can keep it open so the
204 // xapian-tcpsrv keeps running.
205 int tracked_fd
= dup(fds
[0]);
207 // We must fclose() the FILE* to avoid valgrind detecting memory leaks from
211 // Find a slot to track the pid->fd mapping in. If we can't find a slot
212 // it just means we'll leak the fd, so don't worry about that too much.
213 for (unsigned i
= 0; i
< sizeof(pid_to_fd
) / sizeof(pid_fd
); ++i
) {
214 if (pid_to_fd
[i
].pid
== 0) {
215 pid_to_fd
[i
].fd
= tracked_fd
;
216 pid_to_fd
[i
].pid
= child
;
221 // Set a signal handler to clean up the xapian-tcpsrv child process when it
223 signal(SIGCHLD
, on_SIGCHLD
);
228 #elif defined __WIN32__
231 static void win32_throw_error_string(const char * str
)
236 len
= FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
|FORMAT_MESSAGE_ALLOCATE_BUFFER
,
237 0, GetLastError(), 0, (CHAR
*)&error
, 0, 0);
239 // Remove any trailing \r\n from output of FormatMessage.
240 if (len
>= 2 && error
[len
- 2] == '\r' && error
[len
- 1] == '\n')
244 msg
.append(error
, len
);
251 // This implementation uses the WIN32 API to start xapian-tcpsrv as a child
252 // process and read its output using a pipe.
254 launch_xapian_tcpsrv(const string
& args
)
256 int port
= DEFAULT_PORT
;
259 string cmd
= XAPIAN_TCPSRV
" --one-shot --interface " LOCALHOST
" --port ";
264 // Create a pipe so we can read stdout/stderr from the child process.
265 HANDLE hRead
, hWrite
;
266 if (!CreatePipe(&hRead
, &hWrite
, 0, 0))
267 win32_throw_error_string("Couldn't create pipe");
269 // Set the write handle to be inherited by the child process.
270 SetHandleInformation(hWrite
, HANDLE_FLAG_INHERIT
, 1);
272 // Create the child process.
273 PROCESS_INFORMATION procinfo
;
274 memset(&procinfo
, 0, sizeof(PROCESS_INFORMATION
));
276 STARTUPINFO startupinfo
;
277 memset(&startupinfo
, 0, sizeof(STARTUPINFO
));
278 startupinfo
.cb
= sizeof(STARTUPINFO
);
279 startupinfo
.hStdError
= hWrite
;
280 startupinfo
.hStdOutput
= hWrite
;
281 startupinfo
.hStdInput
= INVALID_HANDLE_VALUE
;
282 startupinfo
.dwFlags
|= STARTF_USESTDHANDLES
;
284 // For some reason Windows wants a modifiable copy!
286 char * cmdline
= strdup(cmd
.c_str());
287 ok
= CreateProcess(0, cmdline
, 0, 0, TRUE
, 0, 0, 0, &startupinfo
, &procinfo
);
290 win32_throw_error_string("Couldn't create child process");
293 CloseHandle(procinfo
.hThread
);
296 FILE *fh
= fdopen(_open_osfhandle(intptr_t(hRead
), O_RDONLY
), "r");
299 if (fgets(buf
, sizeof(buf
), fh
) == NULL
) {
302 // This doesn't seem to be necessary on the machine I tested on,
303 // but I guess it could be on a slow machine...
304 while (GetExitCodeProcess(procinfo
.hProcess
, &rc
) && rc
== STILL_ACTIVE
) {
307 CloseHandle(procinfo
.hProcess
);
308 if (++port
< 65536 && rc
== 69) {
309 // 69 is EX_UNAVAILABLE which xapian-tcpsrv exits
310 // with if (and only if) the port specified was
314 string
msg("Failed to get 'Listening...' from command '");
316 msg
+= "' (output: ";
321 if (strcmp(buf
, "Listening...\r\n") == 0) break;
330 # error Neither HAVE_FORK nor __WIN32__ is defined
333 BackendManagerRemoteTcp::~BackendManagerRemoteTcp() {
334 BackendManagerRemoteTcp::clean_up();
338 BackendManagerRemoteTcp::get_dbtype() const
340 return "remotetcp_" + sub_manager
->get_dbtype();
344 BackendManagerRemoteTcp::do_get_database(const vector
<string
> & files
)
346 // Default to a long (5 minute) timeout so that tests won't fail just
347 // because the host is slow or busy.
348 return BackendManagerRemoteTcp::get_remote_database(files
, 300000);
351 Xapian::WritableDatabase
352 BackendManagerRemoteTcp::get_writable_database(const string
& name
,
355 string args
= get_writable_database_args(name
, file
);
356 int port
= launch_xapian_tcpsrv(args
);
357 return Xapian::Remote::open_writable(LOCALHOST
, port
);
361 BackendManagerRemoteTcp::get_remote_database(const vector
<string
> & files
,
362 unsigned int timeout
)
364 string args
= get_remote_database_args(files
, timeout
);
365 int port
= launch_xapian_tcpsrv(args
);
366 return Xapian::Remote::open(LOCALHOST
, port
);
370 BackendManagerRemoteTcp::get_writable_database_as_database()
372 string args
= get_writable_database_as_database_args();
373 int port
= launch_xapian_tcpsrv(args
);
374 return Xapian::Remote::open(LOCALHOST
, port
);
377 Xapian::WritableDatabase
378 BackendManagerRemoteTcp::get_writable_database_again()
380 string args
= get_writable_database_again_args();
381 int port
= launch_xapian_tcpsrv(args
);
382 return Xapian::Remote::open_writable(LOCALHOST
, port
);
386 BackendManagerRemoteTcp::clean_up()
389 signal(SIGCHLD
, SIG_DFL
);
390 for (unsigned i
= 0; i
< sizeof(pid_to_fd
) / sizeof(pid_fd
); ++i
) {
391 pid_t child
= pid_to_fd
[i
].pid
;
394 while (waitpid(child
, &status
, 0) == -1 && errno
== EINTR
) { }
395 // Other possible error from waitpid is ECHILD, which it seems can
396 // only mean that the child has already exited and SIGCHLD was set
397 // to SIG_IGN. If we did somehow see that, the sanest response
398 // seems to be to close the fd and move on.
399 int fd
= pid_to_fd
[i
].fd
;
401 pid_to_fd
[i
].pid
= 0;