LCOV - code coverage report
Current view: top level - tests/harness - backendmanager_remotetcp.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core c2b6f1024d3a Lines: 84 107 78.5 %
Date: 2019-05-16 09:13:18 Functions: 10 10 100.0 %
Branches: 71 156 45.5 %

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

Generated by: LCOV version 1.11