LCOV - code coverage report
Current view: top level - harness - backendmanager_remotetcp.cc (source / functions) Hit Total Coverage
Test: Test Coverage for xapian-core r Lines: 69 100 69.0 %
Date: 2011-08-21 Functions: 10 12 83.3 %
Branches: 24 54 44.4 %

           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 : }

Generated by: LCOV version 1.8