net/progclient.cc

Go to the documentation of this file.
00001 /* progclient.cc: implementation of NetClient which spawns a program.
00002  *
00003  * Copyright 1999,2000,2001 BrightStation PLC
00004  * Copyright 2002 Ananova Ltd
00005  * Copyright 2003,2004,2005,2006,2007 Olly Betts
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License as
00009  * published by the Free Software Foundation; either version 2 of the
00010  * License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
00020  * USA
00021  */
00022 
00023 #include <config.h>
00024 
00025 #include "safeerrno.h"
00026 #include "safefcntl.h"
00027 
00028 #include "progclient.h"
00029 #include <xapian/error.h>
00030 #include "omdebug.h"
00031 
00032 #include <string>
00033 #include <vector>
00034 
00035 #include <sys/types.h>
00036 #ifndef __WIN32__
00037 # include <sys/socket.h>
00038 # include <sys/wait.h>
00039 #else
00040 # include <io.h>
00041 #endif
00042 
00043 using namespace std;
00044 
00045 #ifndef __WIN32__
00046 
00049 static void
00050 split_words(const string &text, vector<string> &words, char ws = ' ')
00051 {
00052     size_t i = 0;
00053     if (i < text.length() && text[0] == ws) {
00054         i = text.find_first_not_of(ws, i);
00055     }
00056     while (i < text.length()) {
00057         size_t j = text.find_first_of(ws, i);
00058         words.push_back(text.substr(i, j - i));
00059         i = text.find_first_not_of(ws, j);
00060     }
00061 }
00062 #endif
00063 
00064 ProgClient::ProgClient(const string &progname, const string &args,
00065                        int msecs_timeout, bool writable)
00066         : RemoteDatabase(run_program(progname, args
00067 #ifndef __WIN32__
00068                                                    , pid
00069 #endif
00070         ),
00071                          msecs_timeout,
00072                          get_progcontext(progname, args),
00073                          writable)
00074 {
00075     DEBUGCALL(DB, void, "ProgClient::ProgClient", progname << ", " << args <<
00076               ", " << msecs_timeout << ", " << writable);
00077 }
00078 
00079 string
00080 ProgClient::get_progcontext(const string &progname, const string &args)
00081 {
00082     DEBUGCALL_STATIC(DB, string, "ProgClient::get_progcontext", progname <<
00083                      ", " << args);
00084     RETURN("remote:prog(" + progname + " " + args);
00085 }
00086 
00087 int
00088 ProgClient::run_program(const string &progname, const string &args
00089 #ifndef __WIN32__
00090                         , pid_t &pid
00091 #endif
00092                         )
00093 {
00094 #if defined HAVE_SOCKETPAIR && defined HAVE_FORK
00095     DEBUGCALL_STATIC(DB, int, "ProgClient::run_program", progname << ", " <<
00096                      args << ", [&pid]");
00097     /* socketpair() returns two sockets.  We keep sv[0] and give
00098      * sv[1] to the child process.
00099      */
00100     int sv[2];
00101 
00102     if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
00103         throw Xapian::NetworkError(string("socketpair failed"), get_progcontext(progname, args), errno);
00104     }
00105 
00106     pid = fork();
00107 
00108     if (pid < 0) {
00109         throw Xapian::NetworkError(string("fork failed"), get_progcontext(progname, args), errno);
00110     }
00111 
00112     if (pid != 0) {
00113         // parent
00114         // close the child's end of the socket
00115         close(sv[1]);
00116         return sv[0];
00117     }
00118 
00119     /* child process:
00120      *   set up file descriptors and exec program
00121      */
00122 
00123     // replace stdin and stdout with the socket
00124     // FIXME: check return values.
00125     close(0);
00126     close(1);
00127     dup2(sv[1], 0);
00128     dup2(sv[1], 1);
00129 
00130     // close unnecessary file descriptors
00131     // FIXME: Probably a bit excessive...
00132     for (int fd = 2; fd < 256; ++fd) {
00133         close(fd);
00134     }
00135 
00136     // Redirect stderr to /dev/null
00137     int stderrfd = open("/dev/null", O_WRONLY);
00138     if (stderrfd == -1) {
00139         throw Xapian::NetworkError(string("Redirecting stderr to /dev/null failed"), get_progcontext(progname, args), errno);
00140     }
00141     if (stderrfd != 2) {
00142         // Not sure why it wouldn't be 2, but handle the situation anyway.
00143         dup2(stderrfd, 2);
00144         close(stderrfd);
00145     }
00146 
00147     vector<string> argvec;
00148     split_words(args, argvec);
00149 
00150     // We never explicitly free this memory, but that's OK as we're about
00151     // to either execvp() or _exit().
00152     const char **new_argv = new const char *[argvec.size() + 2];
00153 
00154     new_argv[0] = progname.c_str();
00155     for (vector<string>::size_type i = 0; i < argvec.size(); ++i) {
00156         new_argv[i + 1] = argvec[i].c_str();
00157     }
00158     new_argv[argvec.size() + 1] = 0;
00159     execvp(progname.c_str(), const_cast<char *const *>(new_argv));
00160 
00161     // if we get here, then execvp failed.
00162     /* throwing an exception is a bad idea, since we're
00163      * not the original process. */
00164     _exit(-1);
00165 #ifdef __sgi
00166     // Avoid "missing return statement" warning.
00167     return 0;
00168 #endif
00169 #elif defined __WIN32__
00170     DEBUGCALL_STATIC(DB, int, "ProgClient::run_program", progname << ", " <<
00171                      args);
00172 
00173     static unsigned int pipecount = 0;
00174     char pipename[256];
00175     sprintf(pipename, "\\\\.\\pipe\\xapian-remote-%lx-%lx-%x",
00176             (unsigned long)GetCurrentProcessId(),
00177             (unsigned long)GetCurrentThreadId(), pipecount++);
00178     // Create a pipe so we can read stdout from the child process.
00179     HANDLE hPipe = CreateNamedPipe(pipename,
00180                                    PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
00181                                    0,
00182                                    1, 4096, 4096, NMPWAIT_USE_DEFAULT_WAIT,
00183                                    NULL);
00184 
00185     if (hPipe == INVALID_HANDLE_VALUE) {
00186         throw Xapian::NetworkError("CreateNamedPipe failed",
00187                                    get_progcontext(progname, args),
00188                                    -(int)GetLastError());
00189     }
00190 
00191     HANDLE hClient = CreateFile(pipename,
00192                                 GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
00193                                 FILE_FLAG_OVERLAPPED, NULL);
00194 
00195     if (hClient == INVALID_HANDLE_VALUE) {
00196         throw Xapian::NetworkError("CreateFile failed",
00197                                    get_progcontext(progname, args),
00198                                    -(int)GetLastError());
00199     }
00200 
00201     if (!ConnectNamedPipe(hPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
00202         throw Xapian::NetworkError("ConnectNamedPipe failed",
00203                                    get_progcontext(progname, args),
00204                                    -(int)GetLastError());
00205     }
00206 
00207     // Set the appropriate handles to be inherited by the child process.
00208     SetHandleInformation(hClient, HANDLE_FLAG_INHERIT, 1);
00209 
00210     // Create the child process.
00211     PROCESS_INFORMATION procinfo;
00212     memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
00213 
00214     STARTUPINFO startupinfo;
00215     memset(&startupinfo, 0, sizeof(STARTUPINFO));
00216     startupinfo.cb = sizeof(STARTUPINFO);
00217     startupinfo.hStdError = hClient;
00218     startupinfo.hStdOutput = hClient;
00219     startupinfo.hStdInput = hClient;
00220     startupinfo.dwFlags |= STARTF_USESTDHANDLES;
00221 
00222     // For some reason Windows wants a modifiable copy!
00223     BOOL ok;
00224     char * cmdline = strdup((progname + ' ' + args).c_str());
00225     ok = CreateProcess(0, cmdline, 0, 0, TRUE, 0, 0, 0, &startupinfo, &procinfo);
00226     free(cmdline);
00227     if (!ok) {
00228         throw Xapian::NetworkError("CreateProcess failed",
00229                                    get_progcontext(progname, args),
00230                                    -(int)GetLastError());
00231     }
00232 
00233     CloseHandle(hClient);
00234     CloseHandle(procinfo.hThread);
00235     return _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
00236 #endif
00237 }
00238 
00239 ProgClient::~ProgClient()
00240 {
00241     // Close the socket and reap the child.
00242     do_close();
00243 #ifndef __WIN32__
00244     waitpid(pid, 0, 0);
00245 #endif
00246 }

Documentation for Xapian (version 1.0.10).
Generated on 24 Dec 2008 by Doxygen 1.5.2.