xapian-core  2.0.0
backendmanager_remotetcp.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2006,2007,2008,2009,2013,2015,2023 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, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
25 
26 #include <xapian.h>
27 
28 #include <stdio.h> // For fdopen().
29 #include <cerrno>
30 #include <cstring>
31 
32 #include "safesysexits.h"
33 
34 #ifdef HAVE_FORK
35 # include <signal.h>
36 # include <sys/types.h>
37 # include "safesyssocket.h"
38 # include <sys/wait.h>
39 # include <unistd.h>
40 #endif
41 
42 #ifdef __WIN32__
43 # include <io.h> // For _open_osfhandle().
44 # include "safefcntl.h"
45 # include "safewindows.h"
46 # include <cstdlib> // For free().
47 #endif
48 
49 #include "errno_to_string.h"
50 #include "str.h"
51 
52 #include <string>
53 #include <vector>
54 
55 #ifdef HAVE_VALGRIND
56 # include <valgrind/memcheck.h>
57 #endif
58 
59 using namespace std;
60 
61 // We've had problems on some hosts which run tinderbox tests with "localhost"
62 // not being set in /etc/hosts - using the IP address equivalent seems more
63 // reliable.
64 #define LOCALHOST "127.0.0.1"
65 
66 // Start at port DEFAULT_PORT and increment until one isn't already in use.
67 #define DEFAULT_PORT 1239
68 
69 class ServerData {
70 #ifdef HAVE_FORK
71  typedef pid_t pid_type;
72 #elif defined __WIN32__
73  typedef DWORD pid_type;
74 #else
75 # error Neither HAVE_FORK nor __WIN32__ is defined
76 #endif
77 
79  static constexpr pid_type DEAD_PID = 0;
80 
85  pid_type pid;
86 
87 #ifdef __WIN32__
88  HANDLE handle;
89 #endif
90 
96  const void* db_internal;
97 
98  public:
99 #ifndef __WIN32__
100  void init(pid_type pid_) {
101  pid = pid_;
102  db_internal = nullptr;
103  }
104 #else
105  void init(pid_type pid_, HANDLE handle_) {
106  pid = pid_;
107  handle = handle_;
108  db_internal = nullptr;
109  }
110 #endif
111 
112  void set_db_internal(const void* dbi) { db_internal = dbi; }
113 
114  void clean_up() {
115  if (pid == DEAD_PID) return;
116 #ifdef HAVE_FORK
117  int status;
118  while (waitpid(pid, &status, 0) == -1 && errno == EINTR) { }
119  // Other possible error from waitpid is ECHILD, which it seems can
120  // only mean that the child has already exited and SIGCHLD was set
121  // to SIG_IGN. If we did somehow see that, it seems reasonable to
122  // treat the child as successfully cleaned up.
123 #elif defined __WIN32__
124  WaitForSingleObject(handle, INFINITE);
125  CloseHandle(handle);
126 #endif
127  }
128 
129  bool kill_remote(const void* dbi) {
130  if (pid == DEAD_PID || dbi != db_internal) return false;
131 #ifdef HAVE_FORK
132  // Kill the process group that we put the server in so that we kill
133  // the server itself and not just the /bin/sh that launched it.
134  if (kill(-pid, SIGKILL) < 0) {
135  throw Xapian::DatabaseError("Couldn't kill remote server",
136  errno);
137  }
138 #elif defined __WIN32__
139  // We want to kill the whole process group so we need to use
140  // GenerateConsoleCtrlEvent() - TerminateProcess() can only
141  // terminate one process given its handle.
142  if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)) {
143  throw Xapian::DatabaseError("Couldn't kill remote server",
144  -int(GetLastError()));
145  }
146 #endif
147  clean_up();
148  pid = DEAD_PID;
149  return true;
150  }
151 };
152 
153 // We can't dynamically resize this on demand (e.g. by using a std::vector)
154 // because it would confuse the leak detector. We clean up after each testcase
155 // so this only needs to store the children launched by a single testcase,
156 // which is at most 6 currently, but the entries are tiny so allocate enough
157 // that future testcases shouldn't hit the limit either.
158 //
159 // We need to linear scan up to first_unused_server_data entries to implement
160 // BackendManagerRemoteTcp::kill_remote(), but with a small fixed bound on the
161 // number of entries that's effectively O(1); also very few testcases use this
162 // feature anyway.
164 
165 static unsigned first_unused_server_data = 0;
166 
167 #ifdef HAVE_FORK
168 
169 static std::pair<int, ServerData&>
170 launch_xapian_tcpsrv(const string & args)
171 {
172  int port = DEFAULT_PORT;
173 
174 try_next_port:
175  string cmd = XAPIAN_TCPSRV " --one-shot --interface " LOCALHOST " --port ";
176  cmd += str(port);
177  cmd += " ";
178  cmd += args;
179 #ifdef HAVE_VALGRIND
180  if (RUNNING_ON_VALGRIND) cmd = "./runsrv " + cmd;
181 #endif
182  int fds[2];
183  if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, PF_UNSPEC, fds) < 0) {
184  string msg("Couldn't create socketpair: ");
185  errno_to_string(errno, msg);
186  throw msg;
187  }
188 
189  pid_t child = fork();
190  if (child == 0) {
191  // Put this process into its own process group so that we can kill the
192  // server itself easily by killing the process group. Just killing
193  // `child` only kills the /bin/sh and leaves the server running.
194  setpgid(0, 0);
195  // Child process.
196  close(fds[0]);
197  // Connect stdout and stderr to the socket.
198  //
199  // Make sure the socket isn't fd 1 or 2. We need to ensure that
200  // FD_CLOEXEC isn't set for stdout or stderr (which creating them with
201  // dup2() achieves), and that we close fds[1]. The cleanest way to
202  // address this seems to be to turn the unusual situation into the
203  // usual one.
204  if (fds[1] == 1 || fds[1] == 2) {
205  dup2(fds[1], 3);
206  fds[1] = 3;
207  }
208  dup2(fds[1], 1);
209  dup2(fds[1], 2);
210  close(fds[1]);
211  execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), static_cast<void*>(0));
212  _exit(-1);
213  }
214 
215  close(fds[1]);
216  if (child == -1) {
217  // Couldn't fork.
218  int fork_errno = errno;
219  close(fds[0]);
220  string msg("Couldn't fork: ");
221  errno_to_string(fork_errno, msg);
222  throw msg;
223  }
224 
225  // Parent process.
226 
227  // Wrap the file descriptor in a FILE * so we can read lines using fgets().
228  FILE * fh = fdopen(fds[0], "r");
229  if (fh == NULL) {
230  string msg("Failed to run command '");
231  msg += cmd;
232  msg += "': ";
233  errno_to_string(errno, msg);
234  throw msg;
235  }
236 
237  string output;
238  while (true) {
239  char buf[256];
240  if (fgets(buf, sizeof(buf), fh) == NULL) {
241  fclose(fh);
242  // Wait for the child to exit.
243  int status;
244  if (waitpid(child, &status, 0) == -1) {
245  string msg("waitpid failed: ");
246  errno_to_string(errno, msg);
247  throw msg;
248  }
249  if (++port < 65536 && status != 0) {
250  if (WIFEXITED(status) &&
251  WEXITSTATUS(status) == EX_UNAVAILABLE) {
252  // Exit code EX_UNAVAILABLE from xapian-tcpsrv means the
253  // specified port was in use.
254  goto try_next_port;
255  }
256  }
257  string msg("Failed to get 'Listening...' from command '");
258  msg += cmd;
259  msg += "' (output: ";
260  msg += output;
261  msg += ")";
262  throw msg;
263  }
264  if (strcmp(buf, "Listening...\n") == 0) break;
265  output += buf;
266  }
267  fclose(fh);
268 
269  if (first_unused_server_data >= std::size(server_data)) {
270  // We used to quietly ignore not finding a slot, but it's helpful to
271  // know if we haven't allocated enough.
272  throw Xapian::DatabaseError("Not enough ServerData slots");
273  }
274 
275  auto& data = server_data[first_unused_server_data++];
276  data.init(child);
277  return {port, data};
278 }
279 
280 #elif defined __WIN32__
281 
282 [[noreturn]]
283 static void win32_throw_error_string(const char * str)
284 {
285  string msg(str);
286  char * error = 0;
287  DWORD len;
288  len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,
289  0, GetLastError(), 0, (CHAR*)&error, 0, 0);
290  if (error) {
291  // Remove any trailing \r\n from output of FormatMessage.
292  if (len >= 2 && error[len - 2] == '\r' && error[len - 1] == '\n')
293  len -= 2;
294  if (len) {
295  msg += ": ";
296  msg.append(error, len);
297  }
298  LocalFree(error);
299  }
300  throw msg;
301 }
302 
303 // This implementation uses the WIN32 API to start xapian-tcpsrv as a child
304 // process and read its output using a pipe.
305 static std::pair<int, ServerData&>
306 launch_xapian_tcpsrv(const string & args)
307 {
308  int port = DEFAULT_PORT;
309 
310 try_next_port:
311  string cmd = XAPIAN_TCPSRV " --one-shot --interface " LOCALHOST " --port ";
312  cmd += str(port);
313  cmd += " ";
314  cmd += args;
315 
316  // Create a pipe so we can read stdout/stderr from the child process.
317  HANDLE hRead, hWrite;
318  if (!CreatePipe(&hRead, &hWrite, 0, 0))
319  win32_throw_error_string("Couldn't create pipe");
320 
321  // Set the write handle to be inherited by the child process.
322  SetHandleInformation(hWrite, HANDLE_FLAG_INHERIT, 1);
323 
324  // Create the child process.
325  PROCESS_INFORMATION procinfo;
326  memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
327 
328  STARTUPINFO startupinfo;
329  memset(&startupinfo, 0, sizeof(STARTUPINFO));
330  startupinfo.cb = sizeof(STARTUPINFO);
331  startupinfo.hStdError = hWrite;
332  startupinfo.hStdOutput = hWrite;
333  startupinfo.hStdInput = INVALID_HANDLE_VALUE;
334  startupinfo.dwFlags |= STARTF_USESTDHANDLES;
335 
336  // For some reason Windows wants a modifiable command line string
337  // so pass a pointer to the first character rather than using c_str().
338  if (!CreateProcess(XAPIAN_TCPSRV, &cmd[0], 0, 0, TRUE,
339  CREATE_NEW_PROCESS_GROUP, 0, 0,
340  &startupinfo, &procinfo)) {
341  win32_throw_error_string("Couldn't create child process");
342  }
343 
344  CloseHandle(hWrite);
345  CloseHandle(procinfo.hThread);
346 
347  string output;
348  FILE *fh = fdopen(_open_osfhandle(intptr_t(hRead), O_RDONLY), "r");
349  while (true) {
350  char buf[256];
351  if (fgets(buf, sizeof(buf), fh) == NULL) {
352  fclose(fh);
353  DWORD rc;
354  // This doesn't seem to be necessary on the machine I tested on,
355  // but I guess it could be on a slow machine...
356  while (GetExitCodeProcess(procinfo.hProcess, &rc) && rc == STILL_ACTIVE) {
357  Sleep(100);
358  }
359  CloseHandle(procinfo.hProcess);
360  if (++port < 65536 && rc == EX_UNAVAILABLE) {
361  // Exit code EX_UNAVAILABLE from xapian-tcpsrv means the
362  // specified port was in use.
363  goto try_next_port;
364  }
365  string msg("Failed to get 'Listening...' from command '");
366  msg += cmd;
367  msg += "' (output: ";
368  msg += output;
369  msg += ")";
370  throw msg;
371  }
372  if (strcmp(buf, "Listening...\r\n") == 0) break;
373  output += buf;
374  }
375  fclose(fh);
376 
377  if (first_unused_server_data >= std::size(server_data)) {
378  // We used to quietly ignore not finding a slot, but it's helpful to
379  // know if we haven't allocated enough.
380  throw Xapian::DatabaseError("Not enough ServerData slots");
381  }
382 
383  auto& data = server_data[first_unused_server_data++];
384  data.init(procinfo.dwProcessId, procinfo.hProcess);
385  return {port, data};
386 }
387 
388 #else
389 # error Neither HAVE_FORK nor __WIN32__ is defined
390 #endif
391 
392 static Xapian::Database
393 get_remotetcp_db(const string& args, int* port_ptr = nullptr)
394 {
395  auto [port, server] = launch_xapian_tcpsrv(args);
396  if (port_ptr) *port_ptr = port;
397  auto db = Xapian::Remote::open(LOCALHOST, port);
398  server.set_db_internal(db.internal.get());
399  return db;
400 }
401 
403 get_remotetcp_writable_db(const string& args)
404 {
405  auto [port, server] = launch_xapian_tcpsrv(args);
406  auto db = Xapian::Remote::open_writable(LOCALHOST, port);
407  server.set_db_internal(db.internal.get());
408  return db;
409 }
410 
413 }
414 
416 BackendManagerRemoteTcp::do_get_database(const vector<string> & files)
417 {
418  // Default to a long (5 minute) timeout so that tests won't fail just
419  // because the host is slow or busy.
421  nullptr);
422 }
423 
426  const string & file)
427 {
428  return get_remotetcp_writable_db(get_writable_database_args(name, file));
429 }
430 
432 BackendManagerRemoteTcp::get_remote_database(const vector<string> & files,
433  unsigned int timeout,
434  int* port_ptr)
435 {
436  return get_remotetcp_db(get_remote_database_args(files, timeout), port_ptr);
437 }
438 
441 {
442  return get_remotetcp_db(get_remote_database_args(path, 300000));
443 }
444 
447 {
448  return get_remotetcp_db(get_writable_database_as_database_args());
449 }
450 
453 {
454  return get_remotetcp_writable_db(get_writable_database_again_args());
455 }
456 
457 void
459 {
460  const void* db_internal = db.internal.get();
461  for (unsigned i = 0; i != first_unused_server_data; ++i) {
462  if (server_data[i].kill_remote(db_internal))
463  return;
464  }
465  throw Xapian::DatabaseError("No known server for remote DB");
466 }
467 
468 void
470 {
471  while (first_unused_server_data) {
473  }
474 }
void kill_remote(const Xapian::Database &db)
Kill the server associated with remote database db.
Definition: apitest.cc:120
#define XAPIAN_TCPSRV
#define DEFAULT_PORT
static Xapian::Database get_remotetcp_db(const string &args, int *port_ptr=nullptr)
static Xapian::WritableDatabase get_remotetcp_writable_db(const string &args)
static unsigned first_unused_server_data
static ServerData server_data[16]
#define LOCALHOST
BackendManager subclass for remotetcp databases.
Xapian::Database get_writable_database_as_database()
Create a Database object for the last opened WritableDatabase.
void clean_up()
Called after each test, to perform any necessary cleanup.
Xapian::WritableDatabase get_writable_database(const std::string &name, const std::string &file)
Create a RemoteTcp Xapian::WritableDatabase object indexing a single file.
Xapian::WritableDatabase get_writable_database_again()
Create a WritableDatabase object for the last opened WritableDatabase.
void kill_remote(const Xapian::Database &db)
Kill the remote server associated with db.
Xapian::Database get_remote_database(const std::vector< std::string > &files, unsigned int timeout, int *port_ptr)
Create a RemoteTcp Xapian::Database with the specified timeout.
Xapian::Database do_get_database(const std::vector< std::string > &files)
Create a Xapian::Database object indexing multiple files.
Xapian::Database get_database_by_path(const std::string &path)
Get a RemoteTcp Xapian::Database instance of the database at path.
void set_db_internal(const void *dbi)
bool kill_remote(const void *dbi)
const void * db_internal
The internal pointer of the Database object.
void init(pid_type pid_)
pid_type pid
The remote server process ID.
DatabaseError indicates some sort of database related error.
Definition: error.h:355
An indexed database of documents.
Definition: database.h:75
Xapian::Internal::intrusive_ptr_nonnull< Internal > internal
Definition: database.h:95
This class provides read/write access to a database.
Definition: database.h:964
void errno_to_string(int e, string &s)
Convert errno value to std::string, thread-safe if possible.
int close(FD &fd)
Definition: fd.h:63
string str(int value)
Convert int to std::string.
Definition: str.cc:91
WritableDatabase open_writable(std::string_view host, unsigned int port, unsigned timeout=0, unsigned connect_timeout=10000, int flags=0)
Construct a WritableDatabase object for update access to a remote database accessed via a TCP connect...
Database open(std::string_view host, unsigned int port, unsigned timeout=10000, unsigned connect_timeout=10000)
Construct a Database object for read-only access to a remote database accessed via a TCP connection.
include <fcntl.h>, but working around broken platforms.
include <sysexits.h> with portability workarounds.
#define EX_UNAVAILABLE
Definition: safesysexits.h:33
include <sys/socket.h> with portability workarounds.
#define SOCK_CLOEXEC
include <windows.h> without all the bloat and damage.
Convert types to std::string.
Definition: header.h:215
Public interfaces for the Xapian library.