X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fyoink;a=blobdiff_plain;f=src%2Fstlplus%2Fportability%2Fsubprocesses.cpp;h=0c7d76f1de89f3399b7eefd9d8a4616baa4d5d4f;hp=a8139c238468b8ff6665234fd56ce25d6eedcfeb;hb=5846afb00833cc72fe72422ca896d2387c712cb4;hpb=a97500609dc3c1b11f9786d32bc458eb00de4c36 diff --git a/src/stlplus/portability/subprocesses.cpp b/src/stlplus/portability/subprocesses.cpp index a8139c2..0c7d76f 100644 --- a/src/stlplus/portability/subprocesses.cpp +++ b/src/stlplus/portability/subprocesses.cpp @@ -1,2077 +1,2077 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -// Bug fix by Alistair Low: kill on Windows now kills grandchild processes as -// well as the child process. This is done using jobs - which has to be -// enabled by stating that the version of Windows is at least 5.0 -#if defined(_WIN32) || defined(_WIN32_WCE) -#define _WIN32_WINNT 0x0500 -#endif - -#include "subprocesses.hpp" -#include "file_system.hpp" -#include "dprintf.hpp" -#include -#include -#include - -#ifdef MSWINDOWS -#else -#include -#include -#include -#include -#include -#endif - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // argument-vector related stuff - - static void skip_white (const std::string& command, unsigned& i) - { - while(i < command.size() && isspace(command[i])) - i++; - } - - // get_argument is the main function for breaking a string down into separate command arguments - // it mimics the way shells break down a command into an argv[] and unescapes the escaped characters on the way - - static std::string get_argument (const std::string& command, unsigned& i) - { - std::string result; -#ifdef MSWINDOWS - - // as far as I know, there is only double-quoting and no escape character in DOS - // so, how do you include a double-quote in an argument??? - - bool dquote = false; - for ( ; i < command.size(); i++) - { - char ch = command[i]; - if (!dquote && isspace(ch)) break; - if (dquote) - { - if (ch == '\"') - dquote = false; - else - result += ch; - } - else if (ch == '\"') - dquote = true; - else - result += ch; - } -#else - bool squote = false; - bool dquote = false; - bool escaped = false; - for ( ; i < command.size(); i++) - { - char ch = command[i]; - if (!squote && !dquote && !escaped && isspace(ch)) break; - if (escaped) - { - result += ch; - escaped = false; - } - else if (squote) - { - if (ch == '\'') - squote = false; - else - result += ch; - } - else if (ch == '\\') - escaped = true; - else if (dquote) - { - if (ch == '\"') - dquote = false; - else - result += ch; - } - else if (ch == '\'') - squote = true; - else if (ch == '\"') - dquote = true; - else - result += ch; - } -#endif - - return result; - } - - - // this function performs the reverse of the above on a single argument - // it escapes special characters and quotes the argument if necessary ready for shell interpretation - - static std::string make_argument (const std::string& arg) - { - std::string result; - bool needs_quotes = false; - - for (unsigned i = 0; i < arg.size(); i++) - { - switch (arg[i]) - { - // set of characters requiring escapes -#ifdef MSWINDOWS -#else - case '\\': case '\'': case '\"': case '`': case '(': case ')': - case '&': case '|': case '<': case '>': case '*': case '?': case '!': - result += '\\'; - result += arg[i]; - break; -#endif - // set of whitespace characters that force quoting - case ' ': - result += arg[i]; - needs_quotes = true; - break; - default: - result += arg[i]; - break; - } - } - - if (needs_quotes) - { - result.insert(result.begin(), '"'); - result += '"'; - } - return result; - } - - static char* copy_string (const char* str) - { - char* result = new char[strlen(str)+1]; - strcpy(result,str); - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - - arg_vector::arg_vector (void) - { - m_argv = 0; - } - - arg_vector::arg_vector (const arg_vector& a) - { - m_argv = 0; - *this = a; - } - - arg_vector::arg_vector (char** a) - { - m_argv = 0; - *this = a; - } - - arg_vector::arg_vector (const std::string& command) - { - m_argv = 0; - *this = command; - } - - arg_vector::arg_vector (const char* command) - { - m_argv = 0; - *this = command; - } - - arg_vector::~arg_vector (void) - { - clear(); - } - - arg_vector& arg_vector::operator = (const arg_vector& a) - { - return *this = a.m_argv; - } - - arg_vector& arg_vector::operator = (char** argv) - { - clear(); - for (unsigned i = 0; argv[i]; i++) - operator += (argv[i]); - return *this; - } - - arg_vector& arg_vector::operator = (const std::string& command) - { - clear(); - for (unsigned i = 0; i < command.size(); ) - { - std::string argument = get_argument(command, i); - operator += (argument); - skip_white(command, i); - } - return *this; - } - - arg_vector& arg_vector::operator = (const char* command) - { - return operator = (std::string(command)); - } - - arg_vector& arg_vector::operator += (const std::string& str) - { - insert(size(), str); - return *this; - } - - arg_vector& arg_vector::operator -= (const std::string& str) - { - insert(0, str); - return *this; - } - - void arg_vector::insert (unsigned index, const std::string& str) throw(std::out_of_range) - { - if (index > size()) throw std::out_of_range("arg_vector::insert"); - // copy up to but not including index, then add the new argument, then copy the rest - char** new_argv = new char*[size()+2]; - unsigned i = 0; - for ( ; i < index; i++) - new_argv[i] = copy_string(m_argv[i]); - new_argv[index] = copy_string(str.c_str()); - for ( ; i < size(); i++) - new_argv[i+1] = copy_string(m_argv[i]); - new_argv[i+1] = 0; - clear(); - m_argv = new_argv; - } - - void arg_vector::clear (unsigned index) throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::clear"); - // copy up to index, skip it, then copy the rest - char** new_argv = new char*[size()]; - unsigned i = 0; - for ( ; i < index; i++) - new_argv[i] = copy_string(m_argv[i]); - i++; - for ( ; i < size(); i++) - new_argv[i-1] = copy_string(m_argv[i]); - new_argv[i-1] = 0; - clear(); - m_argv = new_argv; - } - - void arg_vector::clear(void) - { - if (m_argv) - { - for (unsigned i = 0; m_argv[i]; i++) - delete[] m_argv[i]; - delete[] m_argv; - m_argv = 0; - } - } - - unsigned arg_vector::size (void) const - { - unsigned i = 0; - if (m_argv) - while (m_argv[i]) - i++; - return i; - } - - arg_vector::operator char** (void) const - { - return m_argv; - } - - char** arg_vector::argv (void) const - { - return m_argv; - } - - char* arg_vector::operator [] (unsigned index) const throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::operator[]"); - return m_argv[index]; - } - - char* arg_vector::argv0 (void) const throw(std::out_of_range) - { - return operator [] (0); - } - - std::string arg_vector::image (void) const - { - std::string result; - for (unsigned i = 0; i < size(); i++) - { - if (i) result += ' '; - result += make_argument(m_argv[i]); - } - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // environment-vector - - // Windoze environment is a single string containing null-terminated - // name=value strings and the whole terminated by a null - - // Unix environment is a null-terminated vector of pointers to null-terminated strings - - //////////////////////////////////////////////////////////////////////////////// - // platform specifics - -#ifdef MSWINDOWS - // Windows utilities - - // Windows environment variables are case-insensitive and I do comparisons by converting to lowercase - static std::string lowercase(const std::string& val) - { - std::string text = val; - for (unsigned i = 0; i < text.size(); i++) - text[i] = tolower(text[i]); - return text; - } - - static unsigned envp_size(const char* envp) - { - unsigned size = 0; - while (envp[size] || (size > 0 && envp[size-1])) size++; - size++; - return size; - } - - static void envp_extract(std::string& name, std::string& value, const char* envp, unsigned& envi) - { - name.erase(); - value.erase(); - if (!envp[envi]) return; - // some special variables start with '=' so ensure at least one character in the name - name += envp[envi++]; - while(envp[envi] != '=') - name += envp[envi++]; - envi++; - while(envp[envi]) - value += envp[envi++]; - envi++; - } - - static void envp_append(const std::string& name, const std::string& value, char* envp, unsigned& envi) - { - for (unsigned i = 0; i < name.size(); i++) - envp[envi++] = name[i]; - envp[envi++] = '='; - for (unsigned j = 0; j < value.size(); j++) - envp[envi++] = value[j]; - envp[envi++] = '\0'; - envp[envi] = '\0'; - } - - static char* envp_copy(const char* envp) - { - unsigned size = envp_size(envp); - char* result = new char[size]; - result[0] = '\0'; - unsigned i = 0; - unsigned j = 0; - while(envp[i]) - { - std::string name; - std::string value; - envp_extract(name, value, envp, i); - envp_append(name, value, result, j); - } - return result; - } - - static void envp_clear(char*& envp) - { - if (envp) - { - delete[] envp; - envp = 0; - } - } - - static bool envp_equal(const std::string& left, const std::string& right) - { - return lowercase(left) == lowercase(right); - } - - static bool envp_less(const std::string& left, const std::string& right) - { - return lowercase(left) < lowercase(right); - } - -#else - // Unix utilities - - extern char** environ; - - static unsigned envp_size(char* const* envp) - { - unsigned size = 0; - while(envp[size]) size++; - size++; - return size; - } - - static void envp_extract(std::string& name, std::string& value, char* const* envp, unsigned& envi) - { - name = ""; - value = ""; - if (!envp[envi]) return; - unsigned i = 0; - while(envp[envi][i] != '=') - name += envp[envi][i++]; - i++; - while(envp[envi][i]) - value += envp[envi][i++]; - envi++; - } - - static void envp_append(const std::string& name, const std::string& value, char** envp, unsigned& envi) - { - std::string entry = name + "=" + value; - envp[envi] = copy_string(entry.c_str()); - envi++; - envp[envi] = 0; - } - - static char** envp_copy(char* const* envp) - { - unsigned size = envp_size(envp); - char** result = new char*[size]; - unsigned i = 0; - unsigned j = 0; - while(envp[i]) - { - std::string name; - std::string value; - envp_extract(name, value, envp, i); - envp_append(name, value, result, j); - } - return result; - } - - static void envp_clear(char**& envp) - { - if (envp) - { - for (unsigned i = 0; envp[i]; i++) - delete[] envp[i]; - delete[] envp; - envp = 0; - } - } - - static bool envp_equal(const std::string& left, const std::string& right) - { - return left == right; - } - - static bool envp_less(const std::string& left, const std::string& right) - { - return left < right; - } - -#endif - //////////////////////////////////////////////////////////////////////////////// - - env_vector::env_vector(void) - { -#ifdef MSWINDOWS - char* env = (char*)GetEnvironmentStringsA(); - m_env = envp_copy(env); - FreeEnvironmentStringsA(env); -#else - m_env = envp_copy(::environ); -#endif - } - - env_vector::env_vector (const env_vector& a) - { - m_env = 0; - *this = a; - } - - env_vector::~env_vector (void) - { - clear(); - } - - env_vector& env_vector::operator = (const env_vector& a) - { - clear(); - m_env = envp_copy(a.m_env); - return *this; - } - - void env_vector::clear(void) - { - envp_clear(m_env); - } - - void env_vector::add(const std::string& name, const std::string& value) - { - // the trick is to add the value in alphabetic order - // this is done by copying the existing environment string to a new - // string, inserting the new value when a name greater than it is found - unsigned size = envp_size(m_env); -#ifdef MSWINDOWS - unsigned new_size = size + name.size() + value.size() + 2; - char* new_v = new char[new_size]; - new_v[0] = '\0'; -#else - unsigned new_size = size + 1; - char** new_v = new char*[new_size]; - new_v[0] = 0; -#endif - // now extract each name=value pair and check the ordering - bool added = false; - unsigned i = 0; - unsigned j = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - { - // replace an existing value - envp_append(name, value, new_v, j); - } - else if (!added && envp_less(name,current_name)) - { - // add the new value first, then the existing one - envp_append(name, value, new_v, j); - envp_append(current_name, current_value, new_v, j); - added = true; - } - else - { - // just add the existing value - envp_append(current_name, current_value, new_v, j); - } - } - if (!added) - envp_append(name, value, new_v, j); - envp_clear(m_env); - m_env = new_v; - } - - - bool env_vector::remove (const std::string& name) - { - bool result = false; - // this is done by copying the existing environment string to a new string, but excluding the specified name - unsigned size = envp_size(m_env); -#ifdef MSWINDOWS - char* new_v = new char[size]; - new_v[0] = '\0'; -#else - char** new_v = new char*[size]; - new_v[0] = 0; -#endif - unsigned i = 0; - unsigned j = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - result = true; - else - envp_append(current_name, current_value, new_v, j); - } - envp_clear(m_env); - m_env = new_v; - return result; - } - - std::string env_vector::operator [] (const std::string& name) const - { - return get(name); - } - - std::string env_vector::get (const std::string& name) const - { - unsigned i = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - return current_value; - } - return std::string(); - } - - unsigned env_vector::size (void) const - { - unsigned i = 0; -#ifdef MSWINDOWS - unsigned offset = 0; - while(m_env[offset]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, offset); - i++; - } -#else - while(m_env[i]) - i++; -#endif - - return i; - } - - std::pair env_vector::operator [] (unsigned index) const throw(std::out_of_range) - { - return get(index); - } - - std::pair env_vector::get (unsigned index) const throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::get"); - unsigned j = 0; - for (unsigned i = 0; i < index; i++) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, j); - } - std::string name; - std::string value; - envp_extract(name, value, m_env, j); - return std::make_pair(name,value); - } - - ENVIRON_TYPE env_vector::envp (void) const - { - return m_env; - } - - //////////////////////////////////////////////////////////////////////////////// - // Synchronous subprocess - // Win32 implementation mostly cribbed from MSDN examples and then made (much) more readable - // Unix implementation mostly from man pages and bitter experience - //////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS - - subprocess::subprocess(void) - { - m_pid.hProcess = 0; - m_job = 0; - m_child_in = 0; - m_child_out = 0; - m_child_err = 0; - m_err = 0; - m_status = 0; - } - -#else - - subprocess::subprocess(void) - { - m_pid = -1; - m_child_in = -1; - m_child_out = -1; - m_child_err = -1; - m_err = 0; - m_status = 0; - } - -#endif - -#ifdef MSWINDOWS - - subprocess::~subprocess(void) - { - if (m_pid.hProcess != 0) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - WaitForSingleObject(m_pid.hProcess, INFINITE); - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - } - -#else - - subprocess::~subprocess(void) - { - if (m_pid != -1) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - for (;;) - { - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - } - } - -#endif - - void subprocess::add_variable(const std::string& name, const std::string& value) - { - m_env.add(name, value); - } - - bool subprocess::remove_variable(const std::string& name) - { - return m_env.remove(name); - } - -#ifdef MSWINDOWS - - bool subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - // If no pipes requested, then connect to the parent stdin/out/err - // for some reason you have to create a pipe handle, then duplicate it - // This is not well explained in MSDN but seems to work - PIPE_TYPE parent_stdin = 0; - if (!connect_stdin) - parent_stdin = GetStdHandle(STD_INPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stdout = 0; - if (!connect_stdout) - parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stderr = 0; - if (!connect_stderr) - parent_stderr = GetStdHandle(STD_ERROR_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - // Now create the subprocess - // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work - // Note that the child will inherit a copy of the pipe handles - STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, - STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, - parent_stdin,parent_stdout,parent_stderr}; - bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; - // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them - if (connect_stdin) CloseHandle(parent_stdin); - if (connect_stdout) CloseHandle(parent_stdout); - if (connect_stderr) CloseHandle(parent_stderr); - if (!created) - { - m_err = GetLastError(); - close_stdin(); - close_stdout(); - close_stderr(); - result = false; - } - else - { - m_job = CreateJobObject(NULL, NULL); - AssignProcessToJobObject(m_job, m_pid.hProcess); - ResumeThread(m_pid.hThread); - - // The child process is now running so call the user's callback - // The convention is that the callback can return false, in which case this will kill the child (if its still running) - if (!callback()) - { - result = false; - kill(); - } - close_stdin(); - close_stdout(); - close_stderr(); - // wait for the child to finish - // TODO - kill the child if a timeout happens - WaitForSingleObject(m_pid.hProcess, INFINITE); - DWORD exit_status = 0; - if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) - { - m_err = GetLastError(); - result = false; - } - else if (exit_status != 0) - result = false; - m_status = (int)exit_status; - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - m_pid.hProcess = 0; - return result; - } - -#else - - bool subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - - int stdin_pipe [2] = {-1, -1}; - if (connect_stdin) - pipe(stdin_pipe); - - int stdout_pipe [2] = {-1, -1}; - if (connect_stdout) - pipe(stdout_pipe); - - int stderr_pipe [2] = {-1, -1}; - if (connect_stderr) - pipe(stderr_pipe); - - // now create the subprocess - // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec - m_pid = ::fork(); - switch(m_pid) - { - case -1: // failed to fork - m_err = errno; - if (connect_stdin) - { - ::close(stdin_pipe[0]); - ::close(stdin_pipe[1]); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - ::close(stdout_pipe[1]); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - ::close(stderr_pipe[1]); - } - result = false; - break; - case 0: // in child; - { - // for each pipe, close the end of the duplicated pipe that is being used by the parent - // and connect the child's end of the pipe to the appropriate standard I/O device - if (connect_stdin) - { - ::close(stdin_pipe[1]); - dup2(stdin_pipe[0],STDIN_FILENO); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - dup2(stdout_pipe[1],STDOUT_FILENO); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - dup2(stderr_pipe[1],STDERR_FILENO); - } - execve(path.c_str(), argv.argv(), m_env.envp()); - // will only ever get here if the exec() failed completely - *must* now exit the child process - // by using errno, the parent has some chance of diagnosing the cause of the problem - exit(errno); - } - break; - default: // in parent - { - // for each pipe, close the end of the duplicated pipe that is being used by the child - // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback - if (connect_stdin) - { - ::close(stdin_pipe[0]); - m_child_in = stdin_pipe[1]; - } - if (connect_stdout) - { - ::close(stdout_pipe[1]); - m_child_out = stdout_pipe[0]; - } - if (connect_stderr) - { - ::close(stderr_pipe[1]); - m_child_err = stderr_pipe[0]; - } - // call the user's callback - if (!callback()) - { - result = false; - kill(); - } - // close the pipes and wait for the child to finish - // wait exits on a signal which may be the child signalling its exit or may be an interrupt - close_stdin(); - close_stdout(); - close_stderr(); - int wait_status = 0; - for (;;) - { - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - // establish whether an error occurred - if (WIFSIGNALED(wait_status)) - { - // set_error(errno); - m_status = WTERMSIG(wait_status); - result = false; - } - else if (WIFEXITED(wait_status)) - { - m_status = WEXITSTATUS(wait_status); - if (m_status != 0) - result = false; - } - m_pid = -1; - } - break; - } - return result; - } - -#endif - - bool subprocess::spawn(const std::string& command_line, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - arg_vector arguments = command_line; - if (arguments.size() == 0) return false; - std::string path = path_lookup(arguments.argv0()); - if (path.empty()) return false; - return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); - } - - bool subprocess::callback(void) - { - return true; - } - -#ifdef MSWINDOWS - - bool subprocess::kill (void) - { - if (!m_pid.hProcess) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (!TerminateJobObject(m_job, (UINT)-1)) - { - m_err = GetLastError(); - return false; - } - return true; - } - -#else - - bool subprocess::kill (void) - { - if (m_pid == -1) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (::kill(m_pid, SIGINT) == -1) - { - m_err = errno; - return false; - } - return true; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == 0) return -1; - // do a blocking write of the whole buffer - DWORD bytes = 0; - if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) - { - m_err = GetLastError(); - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#else - - int subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == -1) return -1; - // do a blocking write of the whole buffer - int bytes = write(m_child_in, buffer.c_str(), buffer.size()); - if (bytes == -1) - { - m_err = errno; - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == 0) return -1; - DWORD bytes = 0; - DWORD buffer_size = 256; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - m_err = GetLastError(); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == -1) return -1; - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_out, tmp, buffer_size); - if (bytes == -1) - { - m_err = errno; - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::read_stderr(std::string& buffer) - { - if (m_child_err == 0) return -1; - DWORD bytes = 0; - DWORD buffer_size = 256; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - m_err = GetLastError(); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == -1) return -1; - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_err, tmp, buffer_size); - if (bytes == -1) - { - m_err = errno; - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stdin (void) - { - if (m_child_in) - { - CloseHandle(m_child_in); - m_child_in = 0; - } - } - -#else - - void subprocess::close_stdin (void) - { - if (m_child_in != -1) - { - ::close(m_child_in); - m_child_in = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stdout (void) - { - if (m_child_out) - { - CloseHandle(m_child_out); - m_child_out = 0; - } - } - -#else - - void subprocess::close_stdout (void) - { - if (m_child_out != -1) - { - ::close(m_child_out); - m_child_out = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stderr (void) - { - if (m_child_err) - { - CloseHandle(m_child_err); - m_child_err = 0; - } - } - -#else - - void subprocess::close_stderr (void) - { - if (m_child_err != -1) - { - ::close(m_child_err); - m_child_err = -1; - } - } - -#endif - - bool subprocess::error(void) const - { - return m_err != 0; - } - - int subprocess::error_number(void) const - { - return m_err; - } - -#ifdef MSWINDOWS - - std::string subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* message; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - m_err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, - 0,0); - std::string result = message; - LocalFree(message); - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); - return result; - } - -#else - - std::string subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* text = strerror(m_err); - if (text) return std::string(text); - return "error number " + dformat("%d",m_err); - } - -#endif - - int subprocess::exit_status(void) const - { - return m_status; - } - - //////////////////////////////////////////////////////////////////////////////// - // backtick subprocess and operations - - backtick_subprocess::backtick_subprocess(void) : subprocess() - { - } - - bool backtick_subprocess::callback(void) - { - for (;;) - { - std::string buffer; - int read_size = read_stdout(buffer); - if (read_size < 0) break; - m_text += buffer; - } - return !error(); - } - - bool backtick_subprocess::spawn(const std::string& path, const arg_vector& argv) - { - return subprocess::spawn(path, argv, false, true, false); - } - - bool backtick_subprocess::spawn(const std::string& command_line) - { - return subprocess::spawn(command_line, false, true, false); - } - - std::vector backtick_subprocess::text(void) const - { - std::vector result; - // convert the raw text into a vector of strings, each corresponding to a line - // in the process, strip out platform-specific line-endings - for (unsigned i = 0; i < m_text.size(); i++) - { - // handle any kind of line-ending - Dos, Unix or MacOS - switch(m_text[i]) - { - case '\xd': // carriage-return - optionally followed by linefeed - { - // discard optional following linefeed - if ((i+1 < m_text.size()) && (m_text[i+1] == '\xa')) - i++; - // add a new line to the end of the vector - result.push_back(std::string()); - break; - } - case '\xa': // linefeed - { - // add a new line to the end of the vector - result.push_back(std::string()); - break; - } - default: - { - result.back() += m_text[i]; - break; - } - } - } - // tidy up - if the last line ended with a newline, the vector will end with an empty string - discard this - if ((result.size()) > 0 && result.back().empty()) - result.erase(result.end()-1); - return result; - } - - std::vector backtick(const std::string& path, const arg_vector& argv) - { - backtick_subprocess sub; - sub.spawn(path, argv); - return sub.text(); - } - - std::vector backtick(const std::string& command_line) - { - backtick_subprocess sub; - sub.spawn(command_line); - return sub.text(); - } - - //////////////////////////////////////////////////////////////////////////////// - // Asynchronous subprocess - -#ifdef MSWINDOWS - - async_subprocess::async_subprocess(void) - { - m_pid.hProcess = 0; - m_job = 0; - m_child_in = 0; - m_child_out = 0; - m_child_err = 0; - m_err = 0; - m_status = 0; - } - -#else - - async_subprocess::async_subprocess(void) - { - m_pid = -1; - m_child_in = -1; - m_child_out = -1; - m_child_err = -1; - m_err = 0; - m_status = 0; - } - -#endif - -#ifdef MSWINDOWS - - async_subprocess::~async_subprocess(void) - { - if (m_pid.hProcess != 0) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - WaitForSingleObject(m_pid.hProcess, INFINITE); - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - } - -#else - - async_subprocess::~async_subprocess(void) - { - if (m_pid != -1) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - for (;;) - { - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - } - } - -#endif - - void async_subprocess::set_error(int e) - { - m_err = e; - } - - void async_subprocess::add_variable(const std::string& name, const std::string& value) - { - m_env.add(name, value); - } - - bool async_subprocess::remove_variable(const std::string& name) - { - return m_env.remove(name); - } - -#ifdef MSWINDOWS - - bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - // If no pipes requested, then connect to the parent stdin/out/err - // for some reason you have to create a pipe handle, then duplicate it - // This is not well explained in MSDN but seems to work - PIPE_TYPE parent_stdin = 0; - if (!connect_stdin) - parent_stdin = GetStdHandle(STD_INPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stdout = 0; - if (!connect_stdout) - parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stderr = 0; - if (!connect_stderr) - parent_stderr = GetStdHandle(STD_ERROR_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - // Now create the subprocess - // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work - // Note that the child will inherit a copy of the pipe handles - STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, - STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, - parent_stdin,parent_stdout,parent_stderr}; - bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; - // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them - if (connect_stdin) CloseHandle(parent_stdin); - if (connect_stdout) CloseHandle(parent_stdout); - if (connect_stderr) CloseHandle(parent_stderr); - if (!created) - { - set_error(GetLastError()); - close_stdin(); - close_stdout(); - close_stderr(); - result = false; - } - else - { - m_job = CreateJobObject(NULL, NULL); - AssignProcessToJobObject(m_job, m_pid.hProcess); - ResumeThread(m_pid.hThread); - } - return result; - } - -#else - - bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - - int stdin_pipe [2] = {-1, -1}; - if (connect_stdin) - pipe(stdin_pipe); - - int stdout_pipe [2] = {-1, -1}; - if (connect_stdout) - pipe(stdout_pipe); - - int stderr_pipe [2] = {-1, -1}; - if (connect_stderr) - pipe(stderr_pipe); - - // now create the subprocess - // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec - m_pid = ::fork(); - switch(m_pid) - { - case -1: // failed to fork - set_error(errno); - if (connect_stdin) - { - ::close(stdin_pipe[0]); - ::close(stdin_pipe[1]); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - ::close(stdout_pipe[1]); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - ::close(stderr_pipe[1]); - } - result = false; - break; - case 0: // in child; - { - // for each pipe, close the end of the duplicated pipe that is being used by the parent - // and connect the child's end of the pipe to the appropriate standard I/O device - if (connect_stdin) - { - ::close(stdin_pipe[1]); - dup2(stdin_pipe[0],STDIN_FILENO); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - dup2(stdout_pipe[1],STDOUT_FILENO); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - dup2(stderr_pipe[1],STDERR_FILENO); - } - execve(path.c_str(), argv.argv(), m_env.envp()); - // will only ever get here if the exec() failed completely - *must* now exit the child process - // by using errno, the parent has some chance of diagnosing the cause of the problem - exit(errno); - } - break; - default: // in parent - { - // for each pipe, close the end of the duplicated pipe that is being used by the child - // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback - if (connect_stdin) - { - ::close(stdin_pipe[0]); - m_child_in = stdin_pipe[1]; - if (fcntl(m_child_in, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - if (connect_stdout) - { - ::close(stdout_pipe[1]); - m_child_out = stdout_pipe[0]; - if (fcntl(m_child_out, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - if (connect_stderr) - { - ::close(stderr_pipe[1]); - m_child_err = stderr_pipe[0]; - if (fcntl(m_child_err, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - } - break; - } - return result; - } - -#endif - - bool async_subprocess::spawn(const std::string& command_line, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - arg_vector arguments = command_line; - if (arguments.size() == 0) return false; - std::string path = path_lookup(arguments.argv0()); - if (path.empty()) return false; - return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); - } - - bool async_subprocess::callback(void) - { - return true; - } - -#ifdef MSWINDOWS - - bool async_subprocess::tick(void) - { - bool result = true; - if (!callback()) - kill(); - DWORD exit_status = 0; - if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) - { - set_error(GetLastError()); - result = false; - } - else if (exit_status != STILL_ACTIVE) - { - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - m_pid.hProcess = 0; - result = false; - } - m_status = (int)exit_status; - return result; - } - -#else - - bool async_subprocess::tick(void) - { - bool result = true; - if (!callback()) - kill(); - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, WNOHANG); - if (wait_ret_val == -1 && errno != EINTR) - { - set_error(errno); - result = false; - } - else if (wait_ret_val != 0) - { - // the only states that indicate a terminated child are WIFSIGNALLED and WIFEXITED - if (WIFSIGNALED(wait_status)) - { - // set_error(errno); - m_status = WTERMSIG(wait_status); - result = false; - } - else if (WIFEXITED(wait_status)) - { - // child has exited - m_status = WEXITSTATUS(wait_status); - result = false; - } - } - if (!result) - m_pid = -1; - return result; - } - -#endif - -#ifdef MSWINDOWS - - bool async_subprocess::kill(void) - { - if (!m_pid.hProcess) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (!TerminateJobObject(m_job, (UINT)-1)) - { - set_error(GetLastError()); - return false; - } - return true; - } - -#else - - bool async_subprocess::kill(void) - { - if (m_pid == -1) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (::kill(m_pid, SIGINT) == -1) - { - set_error(errno); - return false; - } - return true; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == 0) return -1; - // there doesn't seem to be a way of doing non-blocking writes under Windoze - DWORD bytes = 0; - if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) - { - set_error(GetLastError()); - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return (int)bytes; - } - -#else - - int async_subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == -1) return -1; - // relies on the pipe being non-blocking - // This does block under Windoze - int bytes = write(m_child_in, buffer.c_str(), buffer.size()); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - return 0; - } - if (bytes == -1) - { - // error on write - close the pipe and give up - set_error(errno); - close_stdin(); - return -1; - } - // successful write - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == 0) return -1; - // peek at the buffer to see how much data there is in the first place - DWORD buffer_size = 0; - if (!PeekNamedPipe(m_child_out, 0, 0, 0, &buffer_size, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - set_error(GetLastError()); - close_stdout(); - return -1; - } - if (buffer_size == 0) return 0; - DWORD bytes = 0; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) - { - set_error(GetLastError()); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int async_subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == -1) return -1; - // rely on the pipe being non-blocking - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_out, tmp, buffer_size); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - delete[] tmp; - return 0; - } - if (bytes == -1) - { - // error - set_error(errno); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - // successful read - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == 0) return -1; - // peek at the buffer to see how much data there is in the first place - DWORD buffer_size = 0; - if (!PeekNamedPipe(m_child_err, 0, 0, 0, &buffer_size, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - set_error(GetLastError()); - close_stderr(); - return -1; - } - if (buffer_size == 0) return 0; - DWORD bytes = 0; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) - { - set_error(GetLastError()); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int async_subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == -1) return -1; - // rely on the pipe being non-blocking - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_err, tmp, buffer_size); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - delete[] tmp; - return 0; - } - if (bytes == -1) - { - // error - set_error(errno); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - // successful read - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stdin (void) - { - if (m_child_in) - { - CloseHandle(m_child_in); - m_child_in = 0; - } - } - -#else - - void async_subprocess::close_stdin (void) - { - if (m_child_in != -1) - { - ::close(m_child_in); - m_child_in = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stdout (void) - { - if (m_child_out) - { - CloseHandle(m_child_out); - m_child_out = 0; - } - } - -#else - - void async_subprocess::close_stdout (void) - { - if (m_child_out != -1) - { - ::close(m_child_out); - m_child_out = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stderr (void) - { - if (m_child_err) - { - CloseHandle(m_child_err); - m_child_err = 0; - } - } - -#else - - void async_subprocess::close_stderr (void) - { - if (m_child_err != -1) - { - ::close(m_child_err); - m_child_err = -1; - } - } - -#endif - - bool async_subprocess::error(void) const - { - return m_err != 0; - } - - int async_subprocess::error_number(void) const - { - return m_err; - } - -#ifdef MSWINDOWS - - std::string async_subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* message; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - m_err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, - 0,0); - std::string result = message; - LocalFree(message); - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); - return result; - } - -#else - - std::string async_subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* text = strerror(m_err); - if (text) return std::string(text); - return "error number " + dformat("%d",m_err); - } - -#endif - - int async_subprocess::exit_status(void) const - { - return m_status; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004-2009 +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +// Bug fix by Alistair Low: kill on Windows now kills grandchild processes as +// well as the child process. This is done using jobs - which has to be +// enabled by stating that the version of Windows is at least 5.0 +#if defined(_WIN32) || defined(_WIN32_WCE) +#define _WIN32_WINNT 0x0500 +#endif + +#include "subprocesses.hpp" +#include "file_system.hpp" +#include "dprintf.hpp" +#include +#include +#include + +#ifdef MSWINDOWS +#else +#include +#include +#include +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // argument-vector related stuff + + static void skip_white (const std::string& command, unsigned& i) + { + while(i < command.size() && isspace(command[i])) + i++; + } + + // get_argument is the main function for breaking a string down into separate command arguments + // it mimics the way shells break down a command into an argv[] and unescapes the escaped characters on the way + + static std::string get_argument (const std::string& command, unsigned& i) + { + std::string result; +#ifdef MSWINDOWS + + // as far as I know, there is only double-quoting and no escape character in DOS + // so, how do you include a double-quote in an argument??? + + bool dquote = false; + for ( ; i < command.size(); i++) + { + char ch = command[i]; + if (!dquote && isspace(ch)) break; + if (dquote) + { + if (ch == '\"') + dquote = false; + else + result += ch; + } + else if (ch == '\"') + dquote = true; + else + result += ch; + } +#else + bool squote = false; + bool dquote = false; + bool escaped = false; + for ( ; i < command.size(); i++) + { + char ch = command[i]; + if (!squote && !dquote && !escaped && isspace(ch)) break; + if (escaped) + { + result += ch; + escaped = false; + } + else if (squote) + { + if (ch == '\'') + squote = false; + else + result += ch; + } + else if (ch == '\\') + escaped = true; + else if (dquote) + { + if (ch == '\"') + dquote = false; + else + result += ch; + } + else if (ch == '\'') + squote = true; + else if (ch == '\"') + dquote = true; + else + result += ch; + } +#endif + + return result; + } + + + // this function performs the reverse of the above on a single argument + // it escapes special characters and quotes the argument if necessary ready for shell interpretation + + static std::string make_argument (const std::string& arg) + { + std::string result; + bool needs_quotes = false; + + for (unsigned i = 0; i < arg.size(); i++) + { + switch (arg[i]) + { + // set of characters requiring escapes +#ifdef MSWINDOWS +#else + case '\\': case '\'': case '\"': case '`': case '(': case ')': + case '&': case '|': case '<': case '>': case '*': case '?': case '!': + result += '\\'; + result += arg[i]; + break; +#endif + // set of whitespace characters that force quoting + case ' ': + result += arg[i]; + needs_quotes = true; + break; + default: + result += arg[i]; + break; + } + } + + if (needs_quotes) + { + result.insert(result.begin(), '"'); + result += '"'; + } + return result; + } + + static char* copy_string (const char* str) + { + char* result = new char[strlen(str)+1]; + strcpy(result,str); + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + + arg_vector::arg_vector (void) + { + m_argv = 0; + } + + arg_vector::arg_vector (const arg_vector& a) + { + m_argv = 0; + *this = a; + } + + arg_vector::arg_vector (char** a) + { + m_argv = 0; + *this = a; + } + + arg_vector::arg_vector (const std::string& command) + { + m_argv = 0; + *this = command; + } + + arg_vector::arg_vector (const char* command) + { + m_argv = 0; + *this = command; + } + + arg_vector::~arg_vector (void) + { + clear(); + } + + arg_vector& arg_vector::operator = (const arg_vector& a) + { + return *this = a.m_argv; + } + + arg_vector& arg_vector::operator = (char** argv) + { + clear(); + for (unsigned i = 0; argv[i]; i++) + operator += (argv[i]); + return *this; + } + + arg_vector& arg_vector::operator = (const std::string& command) + { + clear(); + for (unsigned i = 0; i < command.size(); ) + { + std::string argument = get_argument(command, i); + operator += (argument); + skip_white(command, i); + } + return *this; + } + + arg_vector& arg_vector::operator = (const char* command) + { + return operator = (std::string(command)); + } + + arg_vector& arg_vector::operator += (const std::string& str) + { + insert(size(), str); + return *this; + } + + arg_vector& arg_vector::operator -= (const std::string& str) + { + insert(0, str); + return *this; + } + + void arg_vector::insert (unsigned index, const std::string& str) throw(std::out_of_range) + { + if (index > size()) throw std::out_of_range("arg_vector::insert"); + // copy up to but not including index, then add the new argument, then copy the rest + char** new_argv = new char*[size()+2]; + unsigned i = 0; + for ( ; i < index; i++) + new_argv[i] = copy_string(m_argv[i]); + new_argv[index] = copy_string(str.c_str()); + for ( ; i < size(); i++) + new_argv[i+1] = copy_string(m_argv[i]); + new_argv[i+1] = 0; + clear(); + m_argv = new_argv; + } + + void arg_vector::clear (unsigned index) throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::clear"); + // copy up to index, skip it, then copy the rest + char** new_argv = new char*[size()]; + unsigned i = 0; + for ( ; i < index; i++) + new_argv[i] = copy_string(m_argv[i]); + i++; + for ( ; i < size(); i++) + new_argv[i-1] = copy_string(m_argv[i]); + new_argv[i-1] = 0; + clear(); + m_argv = new_argv; + } + + void arg_vector::clear(void) + { + if (m_argv) + { + for (unsigned i = 0; m_argv[i]; i++) + delete[] m_argv[i]; + delete[] m_argv; + m_argv = 0; + } + } + + unsigned arg_vector::size (void) const + { + unsigned i = 0; + if (m_argv) + while (m_argv[i]) + i++; + return i; + } + + arg_vector::operator char** (void) const + { + return m_argv; + } + + char** arg_vector::argv (void) const + { + return m_argv; + } + + char* arg_vector::operator [] (unsigned index) const throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::operator[]"); + return m_argv[index]; + } + + char* arg_vector::argv0 (void) const throw(std::out_of_range) + { + return operator [] (0); + } + + std::string arg_vector::image (void) const + { + std::string result; + for (unsigned i = 0; i < size(); i++) + { + if (i) result += ' '; + result += make_argument(m_argv[i]); + } + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // environment-vector + + // Windoze environment is a single string containing null-terminated + // name=value strings and the whole terminated by a null + + // Unix environment is a null-terminated vector of pointers to null-terminated strings + + //////////////////////////////////////////////////////////////////////////////// + // platform specifics + +#ifdef MSWINDOWS + // Windows utilities + + // Windows environment variables are case-insensitive and I do comparisons by converting to lowercase + static std::string lowercase(const std::string& val) + { + std::string text = val; + for (unsigned i = 0; i < text.size(); i++) + text[i] = tolower(text[i]); + return text; + } + + static unsigned envp_size(const char* envp) + { + unsigned size = 0; + while (envp[size] || (size > 0 && envp[size-1])) size++; + size++; + return size; + } + + static void envp_extract(std::string& name, std::string& value, const char* envp, unsigned& envi) + { + name.erase(); + value.erase(); + if (!envp[envi]) return; + // some special variables start with '=' so ensure at least one character in the name + name += envp[envi++]; + while(envp[envi] != '=') + name += envp[envi++]; + envi++; + while(envp[envi]) + value += envp[envi++]; + envi++; + } + + static void envp_append(const std::string& name, const std::string& value, char* envp, unsigned& envi) + { + for (unsigned i = 0; i < name.size(); i++) + envp[envi++] = name[i]; + envp[envi++] = '='; + for (unsigned j = 0; j < value.size(); j++) + envp[envi++] = value[j]; + envp[envi++] = '\0'; + envp[envi] = '\0'; + } + + static char* envp_copy(const char* envp) + { + unsigned size = envp_size(envp); + char* result = new char[size]; + result[0] = '\0'; + unsigned i = 0; + unsigned j = 0; + while(envp[i]) + { + std::string name; + std::string value; + envp_extract(name, value, envp, i); + envp_append(name, value, result, j); + } + return result; + } + + static void envp_clear(char*& envp) + { + if (envp) + { + delete[] envp; + envp = 0; + } + } + + static bool envp_equal(const std::string& left, const std::string& right) + { + return lowercase(left) == lowercase(right); + } + + static bool envp_less(const std::string& left, const std::string& right) + { + return lowercase(left) < lowercase(right); + } + +#else + // Unix utilities + + extern char** environ; + + static unsigned envp_size(char* const* envp) + { + unsigned size = 0; + while(envp[size]) size++; + size++; + return size; + } + + static void envp_extract(std::string& name, std::string& value, char* const* envp, unsigned& envi) + { + name = ""; + value = ""; + if (!envp[envi]) return; + unsigned i = 0; + while(envp[envi][i] != '=') + name += envp[envi][i++]; + i++; + while(envp[envi][i]) + value += envp[envi][i++]; + envi++; + } + + static void envp_append(const std::string& name, const std::string& value, char** envp, unsigned& envi) + { + std::string entry = name + "=" + value; + envp[envi] = copy_string(entry.c_str()); + envi++; + envp[envi] = 0; + } + + static char** envp_copy(char* const* envp) + { + unsigned size = envp_size(envp); + char** result = new char*[size]; + unsigned i = 0; + unsigned j = 0; + while(envp[i]) + { + std::string name; + std::string value; + envp_extract(name, value, envp, i); + envp_append(name, value, result, j); + } + return result; + } + + static void envp_clear(char**& envp) + { + if (envp) + { + for (unsigned i = 0; envp[i]; i++) + delete[] envp[i]; + delete[] envp; + envp = 0; + } + } + + static bool envp_equal(const std::string& left, const std::string& right) + { + return left == right; + } + + static bool envp_less(const std::string& left, const std::string& right) + { + return left < right; + } + +#endif + //////////////////////////////////////////////////////////////////////////////// + + env_vector::env_vector(void) + { +#ifdef MSWINDOWS + char* env = (char*)GetEnvironmentStringsA(); + m_env = envp_copy(env); + FreeEnvironmentStringsA(env); +#else + m_env = envp_copy(::environ); +#endif + } + + env_vector::env_vector (const env_vector& a) + { + m_env = 0; + *this = a; + } + + env_vector::~env_vector (void) + { + clear(); + } + + env_vector& env_vector::operator = (const env_vector& a) + { + clear(); + m_env = envp_copy(a.m_env); + return *this; + } + + void env_vector::clear(void) + { + envp_clear(m_env); + } + + void env_vector::add(const std::string& name, const std::string& value) + { + // the trick is to add the value in alphabetic order + // this is done by copying the existing environment string to a new + // string, inserting the new value when a name greater than it is found + unsigned size = envp_size(m_env); +#ifdef MSWINDOWS + unsigned new_size = size + name.size() + value.size() + 2; + char* new_v = new char[new_size]; + new_v[0] = '\0'; +#else + unsigned new_size = size + 1; + char** new_v = new char*[new_size]; + new_v[0] = 0; +#endif + // now extract each name=value pair and check the ordering + bool added = false; + unsigned i = 0; + unsigned j = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + { + // replace an existing value + envp_append(name, value, new_v, j); + } + else if (!added && envp_less(name,current_name)) + { + // add the new value first, then the existing one + envp_append(name, value, new_v, j); + envp_append(current_name, current_value, new_v, j); + added = true; + } + else + { + // just add the existing value + envp_append(current_name, current_value, new_v, j); + } + } + if (!added) + envp_append(name, value, new_v, j); + envp_clear(m_env); + m_env = new_v; + } + + + bool env_vector::remove (const std::string& name) + { + bool result = false; + // this is done by copying the existing environment string to a new string, but excluding the specified name + unsigned size = envp_size(m_env); +#ifdef MSWINDOWS + char* new_v = new char[size]; + new_v[0] = '\0'; +#else + char** new_v = new char*[size]; + new_v[0] = 0; +#endif + unsigned i = 0; + unsigned j = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + result = true; + else + envp_append(current_name, current_value, new_v, j); + } + envp_clear(m_env); + m_env = new_v; + return result; + } + + std::string env_vector::operator [] (const std::string& name) const + { + return get(name); + } + + std::string env_vector::get (const std::string& name) const + { + unsigned i = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + return current_value; + } + return std::string(); + } + + unsigned env_vector::size (void) const + { + unsigned i = 0; +#ifdef MSWINDOWS + unsigned offset = 0; + while(m_env[offset]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, offset); + i++; + } +#else + while(m_env[i]) + i++; +#endif + + return i; + } + + std::pair env_vector::operator [] (unsigned index) const throw(std::out_of_range) + { + return get(index); + } + + std::pair env_vector::get (unsigned index) const throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::get"); + unsigned j = 0; + for (unsigned i = 0; i < index; i++) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, j); + } + std::string name; + std::string value; + envp_extract(name, value, m_env, j); + return std::make_pair(name,value); + } + + ENVIRON_TYPE env_vector::envp (void) const + { + return m_env; + } + + //////////////////////////////////////////////////////////////////////////////// + // Synchronous subprocess + // Win32 implementation mostly cribbed from MSDN examples and then made (much) more readable + // Unix implementation mostly from man pages and bitter experience + //////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS + + subprocess::subprocess(void) + { + m_pid.hProcess = 0; + m_job = 0; + m_child_in = 0; + m_child_out = 0; + m_child_err = 0; + m_err = 0; + m_status = 0; + } + +#else + + subprocess::subprocess(void) + { + m_pid = -1; + m_child_in = -1; + m_child_out = -1; + m_child_err = -1; + m_err = 0; + m_status = 0; + } + +#endif + +#ifdef MSWINDOWS + + subprocess::~subprocess(void) + { + if (m_pid.hProcess != 0) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + WaitForSingleObject(m_pid.hProcess, INFINITE); + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + } + +#else + + subprocess::~subprocess(void) + { + if (m_pid != -1) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + for (;;) + { + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + } + } + +#endif + + void subprocess::add_variable(const std::string& name, const std::string& value) + { + m_env.add(name, value); + } + + bool subprocess::remove_variable(const std::string& name) + { + return m_env.remove(name); + } + +#ifdef MSWINDOWS + + bool subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + // If no pipes requested, then connect to the parent stdin/out/err + // for some reason you have to create a pipe handle, then duplicate it + // This is not well explained in MSDN but seems to work + PIPE_TYPE parent_stdin = 0; + if (!connect_stdin) + parent_stdin = GetStdHandle(STD_INPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stdout = 0; + if (!connect_stdout) + parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stderr = 0; + if (!connect_stderr) + parent_stderr = GetStdHandle(STD_ERROR_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + // Now create the subprocess + // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work + // Note that the child will inherit a copy of the pipe handles + STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, + STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, + parent_stdin,parent_stdout,parent_stderr}; + bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; + // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them + if (connect_stdin) CloseHandle(parent_stdin); + if (connect_stdout) CloseHandle(parent_stdout); + if (connect_stderr) CloseHandle(parent_stderr); + if (!created) + { + m_err = GetLastError(); + close_stdin(); + close_stdout(); + close_stderr(); + result = false; + } + else + { + m_job = CreateJobObject(NULL, NULL); + AssignProcessToJobObject(m_job, m_pid.hProcess); + ResumeThread(m_pid.hThread); + + // The child process is now running so call the user's callback + // The convention is that the callback can return false, in which case this will kill the child (if its still running) + if (!callback()) + { + result = false; + kill(); + } + close_stdin(); + close_stdout(); + close_stderr(); + // wait for the child to finish + // TODO - kill the child if a timeout happens + WaitForSingleObject(m_pid.hProcess, INFINITE); + DWORD exit_status = 0; + if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) + { + m_err = GetLastError(); + result = false; + } + else if (exit_status != 0) + result = false; + m_status = (int)exit_status; + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + m_pid.hProcess = 0; + return result; + } + +#else + + bool subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + + int stdin_pipe [2] = {-1, -1}; + if (connect_stdin) + pipe(stdin_pipe); + + int stdout_pipe [2] = {-1, -1}; + if (connect_stdout) + pipe(stdout_pipe); + + int stderr_pipe [2] = {-1, -1}; + if (connect_stderr) + pipe(stderr_pipe); + + // now create the subprocess + // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec + m_pid = ::fork(); + switch(m_pid) + { + case -1: // failed to fork + m_err = errno; + if (connect_stdin) + { + ::close(stdin_pipe[0]); + ::close(stdin_pipe[1]); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::close(stdout_pipe[1]); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::close(stderr_pipe[1]); + } + result = false; + break; + case 0: // in child; + { + // for each pipe, close the end of the duplicated pipe that is being used by the parent + // and connect the child's end of the pipe to the appropriate standard I/O device + if (connect_stdin) + { + ::close(stdin_pipe[1]); + dup2(stdin_pipe[0],STDIN_FILENO); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + dup2(stdout_pipe[1],STDOUT_FILENO); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + dup2(stderr_pipe[1],STDERR_FILENO); + } + execve(path.c_str(), argv.argv(), m_env.envp()); + // will only ever get here if the exec() failed completely - *must* now exit the child process + // by using errno, the parent has some chance of diagnosing the cause of the problem + exit(errno); + } + break; + default: // in parent + { + // for each pipe, close the end of the duplicated pipe that is being used by the child + // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback + if (connect_stdin) + { + ::close(stdin_pipe[0]); + m_child_in = stdin_pipe[1]; + } + if (connect_stdout) + { + ::close(stdout_pipe[1]); + m_child_out = stdout_pipe[0]; + } + if (connect_stderr) + { + ::close(stderr_pipe[1]); + m_child_err = stderr_pipe[0]; + } + // call the user's callback + if (!callback()) + { + result = false; + kill(); + } + // close the pipes and wait for the child to finish + // wait exits on a signal which may be the child signalling its exit or may be an interrupt + close_stdin(); + close_stdout(); + close_stderr(); + int wait_status = 0; + for (;;) + { + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + // establish whether an error occurred + if (WIFSIGNALED(wait_status)) + { + // set_error(errno); + m_status = WTERMSIG(wait_status); + result = false; + } + else if (WIFEXITED(wait_status)) + { + m_status = WEXITSTATUS(wait_status); + if (m_status != 0) + result = false; + } + m_pid = -1; + } + break; + } + return result; + } + +#endif + + bool subprocess::spawn(const std::string& command_line, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + arg_vector arguments = command_line; + if (arguments.size() == 0) return false; + std::string path = path_lookup(arguments.argv0()); + if (path.empty()) return false; + return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); + } + + bool subprocess::callback(void) + { + return true; + } + +#ifdef MSWINDOWS + + bool subprocess::kill (void) + { + if (!m_pid.hProcess) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (!TerminateJobObject(m_job, (UINT)-1)) + { + m_err = GetLastError(); + return false; + } + return true; + } + +#else + + bool subprocess::kill (void) + { + if (m_pid == -1) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (::kill(m_pid, SIGINT) == -1) + { + m_err = errno; + return false; + } + return true; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == 0) return -1; + // do a blocking write of the whole buffer + DWORD bytes = 0; + if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) + { + m_err = GetLastError(); + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#else + + int subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == -1) return -1; + // do a blocking write of the whole buffer + int bytes = write(m_child_in, buffer.c_str(), buffer.size()); + if (bytes == -1) + { + m_err = errno; + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == 0) return -1; + DWORD bytes = 0; + DWORD buffer_size = 256; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + m_err = GetLastError(); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == -1) return -1; + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_out, tmp, buffer_size); + if (bytes == -1) + { + m_err = errno; + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::read_stderr(std::string& buffer) + { + if (m_child_err == 0) return -1; + DWORD bytes = 0; + DWORD buffer_size = 256; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + m_err = GetLastError(); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == -1) return -1; + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_err, tmp, buffer_size); + if (bytes == -1) + { + m_err = errno; + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stdin (void) + { + if (m_child_in) + { + CloseHandle(m_child_in); + m_child_in = 0; + } + } + +#else + + void subprocess::close_stdin (void) + { + if (m_child_in != -1) + { + ::close(m_child_in); + m_child_in = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stdout (void) + { + if (m_child_out) + { + CloseHandle(m_child_out); + m_child_out = 0; + } + } + +#else + + void subprocess::close_stdout (void) + { + if (m_child_out != -1) + { + ::close(m_child_out); + m_child_out = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stderr (void) + { + if (m_child_err) + { + CloseHandle(m_child_err); + m_child_err = 0; + } + } + +#else + + void subprocess::close_stderr (void) + { + if (m_child_err != -1) + { + ::close(m_child_err); + m_child_err = -1; + } + } + +#endif + + bool subprocess::error(void) const + { + return m_err != 0; + } + + int subprocess::error_number(void) const + { + return m_err; + } + +#ifdef MSWINDOWS + + std::string subprocess::error_text(void) const + { + if (m_err == 0) return std::string(); + char* message; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + m_err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, + 0,0); + std::string result = message; + LocalFree(message); + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); + return result; + } + +#else + + std::string subprocess::error_text(void) const + { + if (m_err == 0) return std::string(); + char* text = strerror(m_err); + if (text) return std::string(text); + return "error number " + dformat("%d",m_err); + } + +#endif + + int subprocess::exit_status(void) const + { + return m_status; + } + + //////////////////////////////////////////////////////////////////////////////// + // backtick subprocess and operations + + backtick_subprocess::backtick_subprocess(void) : subprocess() + { + } + + bool backtick_subprocess::callback(void) + { + for (;;) + { + std::string buffer; + int read_size = read_stdout(buffer); + if (read_size < 0) break; + m_text += buffer; + } + return !error(); + } + + bool backtick_subprocess::spawn(const std::string& path, const arg_vector& argv) + { + return subprocess::spawn(path, argv, false, true, false); + } + + bool backtick_subprocess::spawn(const std::string& command_line) + { + return subprocess::spawn(command_line, false, true, false); + } + + std::vector backtick_subprocess::text(void) const + { + std::vector result; + // convert the raw text into a vector of strings, each corresponding to a line + // in the process, strip out platform-specific line-endings + for (unsigned i = 0; i < m_text.size(); i++) + { + // handle any kind of line-ending - Dos, Unix or MacOS + switch(m_text[i]) + { + case '\xd': // carriage-return - optionally followed by linefeed + { + // discard optional following linefeed + if ((i+1 < m_text.size()) && (m_text[i+1] == '\xa')) + i++; + // add a new line to the end of the vector + result.push_back(std::string()); + break; + } + case '\xa': // linefeed + { + // add a new line to the end of the vector + result.push_back(std::string()); + break; + } + default: + { + result.back() += m_text[i]; + break; + } + } + } + // tidy up - if the last line ended with a newline, the vector will end with an empty string - discard this + if ((result.size()) > 0 && result.back().empty()) + result.erase(result.end()-1); + return result; + } + + std::vector backtick(const std::string& path, const arg_vector& argv) + { + backtick_subprocess sub; + sub.spawn(path, argv); + return sub.text(); + } + + std::vector backtick(const std::string& command_line) + { + backtick_subprocess sub; + sub.spawn(command_line); + return sub.text(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Asynchronous subprocess + +#ifdef MSWINDOWS + + async_subprocess::async_subprocess(void) + { + m_pid.hProcess = 0; + m_job = 0; + m_child_in = 0; + m_child_out = 0; + m_child_err = 0; + m_err = 0; + m_status = 0; + } + +#else + + async_subprocess::async_subprocess(void) + { + m_pid = -1; + m_child_in = -1; + m_child_out = -1; + m_child_err = -1; + m_err = 0; + m_status = 0; + } + +#endif + +#ifdef MSWINDOWS + + async_subprocess::~async_subprocess(void) + { + if (m_pid.hProcess != 0) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + WaitForSingleObject(m_pid.hProcess, INFINITE); + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + } + +#else + + async_subprocess::~async_subprocess(void) + { + if (m_pid != -1) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + for (;;) + { + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + } + } + +#endif + + void async_subprocess::set_error(int e) + { + m_err = e; + } + + void async_subprocess::add_variable(const std::string& name, const std::string& value) + { + m_env.add(name, value); + } + + bool async_subprocess::remove_variable(const std::string& name) + { + return m_env.remove(name); + } + +#ifdef MSWINDOWS + + bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + // If no pipes requested, then connect to the parent stdin/out/err + // for some reason you have to create a pipe handle, then duplicate it + // This is not well explained in MSDN but seems to work + PIPE_TYPE parent_stdin = 0; + if (!connect_stdin) + parent_stdin = GetStdHandle(STD_INPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stdout = 0; + if (!connect_stdout) + parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stderr = 0; + if (!connect_stderr) + parent_stderr = GetStdHandle(STD_ERROR_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + // Now create the subprocess + // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work + // Note that the child will inherit a copy of the pipe handles + STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, + STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, + parent_stdin,parent_stdout,parent_stderr}; + bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; + // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them + if (connect_stdin) CloseHandle(parent_stdin); + if (connect_stdout) CloseHandle(parent_stdout); + if (connect_stderr) CloseHandle(parent_stderr); + if (!created) + { + set_error(GetLastError()); + close_stdin(); + close_stdout(); + close_stderr(); + result = false; + } + else + { + m_job = CreateJobObject(NULL, NULL); + AssignProcessToJobObject(m_job, m_pid.hProcess); + ResumeThread(m_pid.hThread); + } + return result; + } + +#else + + bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + + int stdin_pipe [2] = {-1, -1}; + if (connect_stdin) + pipe(stdin_pipe); + + int stdout_pipe [2] = {-1, -1}; + if (connect_stdout) + pipe(stdout_pipe); + + int stderr_pipe [2] = {-1, -1}; + if (connect_stderr) + pipe(stderr_pipe); + + // now create the subprocess + // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec + m_pid = ::fork(); + switch(m_pid) + { + case -1: // failed to fork + set_error(errno); + if (connect_stdin) + { + ::close(stdin_pipe[0]); + ::close(stdin_pipe[1]); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::close(stdout_pipe[1]); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::close(stderr_pipe[1]); + } + result = false; + break; + case 0: // in child; + { + // for each pipe, close the end of the duplicated pipe that is being used by the parent + // and connect the child's end of the pipe to the appropriate standard I/O device + if (connect_stdin) + { + ::close(stdin_pipe[1]); + dup2(stdin_pipe[0],STDIN_FILENO); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + dup2(stdout_pipe[1],STDOUT_FILENO); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + dup2(stderr_pipe[1],STDERR_FILENO); + } + execve(path.c_str(), argv.argv(), m_env.envp()); + // will only ever get here if the exec() failed completely - *must* now exit the child process + // by using errno, the parent has some chance of diagnosing the cause of the problem + exit(errno); + } + break; + default: // in parent + { + // for each pipe, close the end of the duplicated pipe that is being used by the child + // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback + if (connect_stdin) + { + ::close(stdin_pipe[0]); + m_child_in = stdin_pipe[1]; + if (fcntl(m_child_in, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + if (connect_stdout) + { + ::close(stdout_pipe[1]); + m_child_out = stdout_pipe[0]; + if (fcntl(m_child_out, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + if (connect_stderr) + { + ::close(stderr_pipe[1]); + m_child_err = stderr_pipe[0]; + if (fcntl(m_child_err, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + } + break; + } + return result; + } + +#endif + + bool async_subprocess::spawn(const std::string& command_line, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + arg_vector arguments = command_line; + if (arguments.size() == 0) return false; + std::string path = path_lookup(arguments.argv0()); + if (path.empty()) return false; + return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); + } + + bool async_subprocess::callback(void) + { + return true; + } + +#ifdef MSWINDOWS + + bool async_subprocess::tick(void) + { + bool result = true; + if (!callback()) + kill(); + DWORD exit_status = 0; + if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) + { + set_error(GetLastError()); + result = false; + } + else if (exit_status != STILL_ACTIVE) + { + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + m_pid.hProcess = 0; + result = false; + } + m_status = (int)exit_status; + return result; + } + +#else + + bool async_subprocess::tick(void) + { + bool result = true; + if (!callback()) + kill(); + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, WNOHANG); + if (wait_ret_val == -1 && errno != EINTR) + { + set_error(errno); + result = false; + } + else if (wait_ret_val != 0) + { + // the only states that indicate a terminated child are WIFSIGNALLED and WIFEXITED + if (WIFSIGNALED(wait_status)) + { + // set_error(errno); + m_status = WTERMSIG(wait_status); + result = false; + } + else if (WIFEXITED(wait_status)) + { + // child has exited + m_status = WEXITSTATUS(wait_status); + result = false; + } + } + if (!result) + m_pid = -1; + return result; + } + +#endif + +#ifdef MSWINDOWS + + bool async_subprocess::kill(void) + { + if (!m_pid.hProcess) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (!TerminateJobObject(m_job, (UINT)-1)) + { + set_error(GetLastError()); + return false; + } + return true; + } + +#else + + bool async_subprocess::kill(void) + { + if (m_pid == -1) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (::kill(m_pid, SIGINT) == -1) + { + set_error(errno); + return false; + } + return true; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == 0) return -1; + // there doesn't seem to be a way of doing non-blocking writes under Windoze + DWORD bytes = 0; + if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) + { + set_error(GetLastError()); + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return (int)bytes; + } + +#else + + int async_subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == -1) return -1; + // relies on the pipe being non-blocking + // This does block under Windoze + int bytes = write(m_child_in, buffer.c_str(), buffer.size()); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + return 0; + } + if (bytes == -1) + { + // error on write - close the pipe and give up + set_error(errno); + close_stdin(); + return -1; + } + // successful write + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == 0) return -1; + // peek at the buffer to see how much data there is in the first place + DWORD buffer_size = 0; + if (!PeekNamedPipe(m_child_out, 0, 0, 0, &buffer_size, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stdout(); + return -1; + } + if (buffer_size == 0) return 0; + DWORD bytes = 0; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) + { + set_error(GetLastError()); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int async_subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == -1) return -1; + // rely on the pipe being non-blocking + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_out, tmp, buffer_size); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + delete[] tmp; + return 0; + } + if (bytes == -1) + { + // error + set_error(errno); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + // successful read + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == 0) return -1; + // peek at the buffer to see how much data there is in the first place + DWORD buffer_size = 0; + if (!PeekNamedPipe(m_child_err, 0, 0, 0, &buffer_size, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stderr(); + return -1; + } + if (buffer_size == 0) return 0; + DWORD bytes = 0; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) + { + set_error(GetLastError()); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int async_subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == -1) return -1; + // rely on the pipe being non-blocking + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_err, tmp, buffer_size); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + delete[] tmp; + return 0; + } + if (bytes == -1) + { + // error + set_error(errno); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + // successful read + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stdin (void) + { + if (m_child_in) + { + CloseHandle(m_child_in); + m_child_in = 0; + } + } + +#else + + void async_subprocess::close_stdin (void) + { + if (m_child_in != -1) + { + ::close(m_child_in); + m_child_in = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stdout (void) + { + if (m_child_out) + { + CloseHandle(m_child_out); + m_child_out = 0; + } + } + +#else + + void async_subprocess::close_stdout (void) + { + if (m_child_out != -1) + { + ::close(m_child_out); + m_child_out = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stderr (void) + { + if (m_child_err) + { + CloseHandle(m_child_err); + m_child_err = 0; + } + } + +#else + + void async_subprocess::close_stderr (void) + { + if (m_child_err != -1) + { + ::close(m_child_err); + m_child_err = -1; + } + } + +#endif + + bool async_subprocess::error(void) const + { + return m_err != 0; + } + + int async_subprocess::error_number(void) const + { + return m_err; + } + +#ifdef MSWINDOWS + + std::string async_subprocess::error_text(void) const + { + if (m_err == 0) return std::string(); + char* message; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + m_err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, + 0,0); + std::string result = message; + LocalFree(message); + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); + return result; + } + +#else + + std::string async_subprocess::error_text(void) const + { + if (m_err == 0) return std::string(); + char* text = strerror(m_err); + if (text) return std::string(text); + return "error number " + dformat("%d",m_err); + } + +#endif + + int async_subprocess::exit_status(void) const + { + return m_status; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus