diff options
Diffstat (limited to 'butl')
-rw-r--r-- | butl/buildfile | 2 | ||||
-rw-r--r-- | butl/process | 66 | ||||
-rw-r--r-- | butl/process.cxx | 326 |
3 files changed, 393 insertions, 1 deletions
diff --git a/butl/buildfile b/butl/buildfile index da6af95..c289532 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -2,6 +2,6 @@ # copyright : Copyright (c) 2014-2015 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -lib{butl}: cxx{fdstream} +lib{butl}: cxx{fdstream process} cxx.poptions += -I$src_root lib{butl}: cxx.export.poptions = -I$src_root diff --git a/butl/process b/butl/process new file mode 100644 index 0000000..1d16285 --- /dev/null +++ b/butl/process @@ -0,0 +1,66 @@ +// file : butl/process -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_PROCESS +#define BUTL_PROCESS + +#ifndef _WIN32 +# include <sys/types.h> // pid_t +#endif + +#include <cassert> +#include <system_error> + +namespace butl +{ + struct process_error: std::system_error + { + process_error (int e, bool child) + : system_error (e, std::system_category ()), child_ (child) {} + + bool + child () const {return child_;} + + private: + bool child_; + }; + + struct process + { + // Start another process using the specified command line. Connect the + // newly created process' stdin to out_fd. Also if connect_* are true, + // connect the created process' stdout and stderr to in_*fd. Throw + // process_error if anything goes wrong. + // + // Note that some of the exceptions (e.g., if exec() failed) can be + // thrown in the child version of us. + // + process (char const* args[], + bool connect_stdin = false, + bool connect_stderr = false, + bool connect_stdout = false); + + // Wait for the process to terminate. Return true if the process + // terminated normally and with the zero exit status. Throw + // process_error if anything goes wrong. + // + bool + wait (); + + ~process () {assert (id == 0);} + +#ifndef _WIN32 + typedef pid_t id_type; +#else + typedef void* id_type; // Win32 HANDLE. +#endif + + id_type id; + int out_fd; // Write to this fd to send to the new process' stdin. + int in_efd; // Read from this fd to receive from the new process' stderr. + int in_ofd; // Read from this fd to receive from the new process' stdout. + }; +} + +#endif // BUTL_PROCESS diff --git a/butl/process.cxx b/butl/process.cxx new file mode 100644 index 0000000..35ca26d --- /dev/null +++ b/butl/process.cxx @@ -0,0 +1,326 @@ +// file : butl/process.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <butl/process> + +#ifndef _WIN32 +# include <unistd.h> // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO +# include <sys/wait.h> // waitpid +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <windows.h> // CreatePipe, CreateProcess +# include <io.h> // _open_osfhandle +# include <fcntl.h> // _O_TEXT +#endif + +using namespace std; + +namespace butl +{ +#ifndef _WIN32 + + process:: + process (char const* args[], bool in, bool err, bool out) + { + int out_fd[2]; + int in_efd[2]; + int in_ofd[2]; + + if ((in && pipe (out_fd) == -1) || + (err && pipe (in_efd) == -1) || + (out && pipe (in_ofd) == -1)) + throw process_error (errno, false); + + id = fork (); + + if (id == -1) + throw process_error (errno, false); + + if (id == 0) + { + // Child. If requested, close the write end of the pipe and duplicate + // the read end to stdin. Then close the original read end descriptor. + // + if (in) + { + if (close (out_fd[1]) == -1 || + dup2 (out_fd[0], STDIN_FILENO) == -1 || + close (out_fd[0]) == -1) + throw process_error (errno, true); + } + + // Do the same for the stderr if requested. + // + if (err) + { + if (close (in_efd[0]) == -1 || + dup2 (in_efd[1], STDERR_FILENO) == -1 || + close (in_efd[1]) == -1) + throw process_error (errno, true); + } + + // Do the same for the stdout if requested. + // + if (out) + { + if (close (in_ofd[0]) == -1 || + dup2 (in_ofd[1], STDOUT_FILENO) == -1 || + close (in_ofd[1]) == -1) + throw process_error (errno, true); + } + + if (execvp (args[0], const_cast<char**> (&args[0])) == -1) + throw process_error (errno, true); + } + else + { + // Parent. Close the other ends of the pipes. + // + if ((in && close (out_fd[0]) == -1) || + (err && close (in_efd[1]) == -1) || + (out && close (in_ofd[1]) == -1)) + throw process_error (errno, false); + } + + this->out_fd = in ? out_fd[1] : 0; + this->in_efd = err ? in_efd[0] : 0; + this->in_ofd = out ? in_ofd[0] : 0; + } + + bool process:: + wait () + { + int status; + int r (waitpid (id, &status, 0)); + id = 0; // We have tried. + + if (r == -1) + throw process_error (errno, false); + + return WIFEXITED (status) && WEXITSTATUS (status) == 0; + } + +#else // _WIN32 + + static void + print_error (char const* name) + { + LPTSTR msg; + DWORD e (GetLastError()); + + if (!FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + e, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msg, + 0, + 0)) + { + cerr << name << ": error: unknown error code " << e << endl; + return; + } + + cerr << name << ": error: " << msg << endl; + LocalFree (msg); + } + + static process_info + start_process (char const* args[], char const* name, bool err, bool out) + { + HANDLE out_h[2]; + HANDLE in_eh[2]; + HANDLE in_oh[2]; + SECURITY_ATTRIBUTES sa; + + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = 0; + + if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) || + !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + + if (err) + { + if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) || + !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + if (out) + { + if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) || + !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0)) + { + print_error (name); + throw process_failure (); + } + } + + // Create the process. + // + path file (args[0]); + + // Do PATH search. + // + if (file.directory ().empty ()) + file = path_search (file); + + if (file.empty ()) + { + cerr << args[0] << ": error: file not found" << endl; + throw process_failure (); + } + + // Serialize the arguments to string. + // + string cmd_line; + + for (char const** p (args); *p != 0; ++p) + { + if (p != args) + cmd_line += ' '; + + // On Windows we need to protect values with spaces using quotes. + // Since there could be actual quotes in the value, we need to + // escape them. + // + string a (*p); + bool quote (a.find (' ') != string::npos); + + if (quote) + cmd_line += '"'; + + for (size_t i (0); i < a.size (); ++i) + { + if (a[i] == '"') + cmd_line += "\\\""; + else + cmd_line += a[i]; + } + + if (quote) + cmd_line += '"'; + } + + // Prepare other info. + // + STARTUPINFO si; + PROCESS_INFORMATION pi; + + memset (&si, 0, sizeof (STARTUPINFO)); + memset (&pi, 0, sizeof (PROCESS_INFORMATION)); + + si.cb = sizeof(STARTUPINFO); + + if (err) + si.hStdError = in_eh[1]; + else + si.hStdError = GetStdHandle (STD_ERROR_HANDLE); + + if (out) + si.hStdOutput = in_oh[1]; + else + si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE); + + si.hStdInput = out_h[0]; + si.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcess ( + file.string ().c_str (), + const_cast<char*> (cmd_line.c_str ()), + 0, // Process security attributes. + 0, // Primary thread security attributes. + true, // Inherit handles. + 0, // Creation flags. + 0, // Use our environment. + 0, // Use our current directory. + &si, + &pi)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.hThread); + CloseHandle (out_h[0]); + + if (err) + CloseHandle (in_eh[1]); + + if (out) + CloseHandle (in_oh[1]); + + process_info r; + r.id = pi.hProcess; + r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0); + + if (r.out_fd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + + if (err) + { + // Pass _O_TEXT to get newline translation. + // + r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT); + + if (r.in_efd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_efd = 0; + + if (out) + { + // Pass _O_TEXT to get newline translation. + // + r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT); + + if (r.in_ofd == -1) + { + cerr << name << ": error: unable to obtain C file handle" << endl; + throw process_failure (); + } + } + else + r.in_ofd = 0; + + return r; + } + + static bool + wait_process (process_info pi, char const* name) + { + DWORD status; + + if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 || + !GetExitCodeProcess (pi.id, &status)) + { + print_error (name); + throw process_failure (); + } + + CloseHandle (pi.id); + return status == 0; + } + +#endif // _WIN32 +} |