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