aboutsummaryrefslogtreecommitdiff
path: root/butl/process.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-05-15 17:11:27 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-05-31 18:42:55 +0300
commit61ef82ec2b2ca396667f92a4e5c6ceb729c42086 (patch)
tree57ca5868483f361a9da28bbfc32f0cc838787b3e /butl/process.cxx
parent79bb0331cb93a736193e733b5ae26d040931a1aa (diff)
Port to MinGW
Diffstat (limited to 'butl/process.cxx')
-rw-r--r--butl/process.cxx564
1 files changed, 402 insertions, 162 deletions
diff --git a/butl/process.cxx b/butl/process.cxx
index f00f03a..1a795ea 100644
--- a/butl/process.cxx
+++ b/butl/process.cxx
@@ -11,9 +11,16 @@
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
-# include <windows.h> // CreatePipe, CreateProcess
-# include <io.h> // _open_osfhandle
+# include <windows.h> // CreatePipe(), CreateProcess()
+# include <io.h> // _open_osfhandle(), _get_osfhandle(), _close()
# include <fcntl.h> // _O_TEXT
+# include <stdlib.h> // getenv()
+# include <sys/types.h> // stat
+# include <sys/stat.h> // stat(), S_IS*
+
+# include <memory> // unique_ptr
+
+# include <butl/path>
#endif
#include <cassert>
@@ -22,79 +29,133 @@ using namespace std;
namespace butl
{
+ class auto_fd
+ {
+ public:
+ explicit
+ auto_fd (int fd = -1) noexcept: fd_ (fd) {}
+
+ auto_fd (const auto_fd&) = delete;
+ auto_fd& operator= (const auto_fd&) = delete;
+
+ ~auto_fd () noexcept {reset ();}
+
+ int
+ get () const noexcept {return fd_;}
+
+ int
+ release () noexcept
+ {
+ int r (fd_);
+ fd_ = -1;
+ return r;
+ }
+
+ void
+ reset (int fd = -1) noexcept
+ {
+ if (fd_ != -1)
+ {
+#ifndef _WIN32
+ int r (close (fd_));
+#else
+ int r (_close (fd_));
+#endif
+ // The valid file descriptor that has no IO operations being
+ // performed on it should close successfully, unless something is
+ // severely damaged.
+ //
+ assert (r != -1);
+ }
+
+ fd_ = fd;
+ }
+
+ private:
+ int fd_;
+ };
+
#ifndef _WIN32
process::
process (const char* cwd, char const* const args[], int in, int out, int err)
{
- int out_fd[2] = {in, 0};
- int in_ofd[2] = {0, out};
- int in_efd[2] = {0, err};
+ using pipe = auto_fd[2];
+
+ pipe out_fd;
+ pipe in_ofd;
+ pipe in_efd;
- if ((in == -1 && pipe (out_fd) == -1) ||
- (out == -1 && pipe (in_ofd) == -1) ||
- (err == -1 && pipe (in_efd) == -1))
- throw process_error (errno, false);
+ auto fail = [](bool child) {throw process_error (errno, child);};
+
+ auto create_pipe = [&fail](pipe& p)
+ {
+ int pd[2];
+ if (::pipe (pd) == -1)
+ fail (false);
+
+ p[0].reset (pd[0]);
+ p[1].reset (pd[1]);
+ };
+
+ if (in == -1)
+ create_pipe (out_fd);
+
+ if (out == -1)
+ create_pipe (in_ofd);
+
+ if (err == -1)
+ create_pipe (in_efd);
handle = fork ();
if (handle == -1)
- throw process_error (errno, false);
+ fail (false);
if (handle == 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.
+ // Child.
//
- if (in != STDIN_FILENO)
+ // Duplicate the user-supplied (fd != -1) or the created pipe descriptor
+ // to the standard stream descriptor (read end for STDIN_FILENO, write
+ // end otherwise). Close the the pipe afterwards.
+ //
+ auto duplicate = [&fail](int sd, int fd, pipe& pd)
{
- if ((in == -1 && close (out_fd[1]) == -1) ||
- dup2 (out_fd[0], STDIN_FILENO) == -1 ||
- (in == -1 && close (out_fd[0]) == -1))
- throw process_error (errno, true);
- }
+ if (fd == -1)
+ fd = pd[sd == STDIN_FILENO ? 0 : 1].get ();
+
+ assert (fd != -1);
+ if (dup2 (fd, sd) == -1)
+ fail (true);
+
+ pd[0].reset (); // Close.
+ pd[1].reset (); // Close.
+ };
+
+ if (in != STDIN_FILENO)
+ duplicate (STDIN_FILENO, in, out_fd);
- // Do the same for the stdout if requested.
- //
if (out != STDOUT_FILENO)
- {
- if ((out == -1 && close (in_ofd[0]) == -1) ||
- dup2 (in_ofd[1], STDOUT_FILENO) == -1 ||
- (out == -1 && close (in_ofd[1]) == -1))
- throw process_error (errno, true);
- }
+ duplicate (STDOUT_FILENO, out, in_ofd);
- // Do the same for the stderr if requested.
- //
if (err != STDERR_FILENO)
- {
- if ((err == -1 && close (in_efd[0]) == -1) ||
- dup2 (in_efd[1], STDERR_FILENO) == -1 ||
- (err == -1 && close (in_efd[1]) == -1))
- throw process_error (errno, true);
- }
+ duplicate (STDERR_FILENO, err, in_efd);
// Change current working directory if requested.
//
if (cwd != nullptr && *cwd != '\0' && chdir (cwd) != 0)
- throw process_error (errno, true);
+ fail (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 == -1 && close (out_fd[0]) == -1) ||
- (out == -1 && close (in_ofd[1]) == -1) ||
- (err == -1 && close (in_efd[1]) == -1))
- throw process_error (errno, false);
+ fail (true);
}
- this->out_fd = in == -1 ? out_fd[1] : -1;
- this->in_ofd = out == -1 ? in_ofd[0] : -1;
- this->in_efd = err == -1 ? in_efd[0] : -1;
+ assert (handle != 0); // Shouldn't get here unless in the parent process.
+
+ this->out_fd = out_fd[1].release ();
+ this->in_ofd = in_ofd[0].release ();
+ this->in_efd = in_efd[0].release ();
}
process::
@@ -149,76 +210,190 @@ namespace butl
#else // _WIN32
- process::id_type process::
- current_id ()
+ struct msg_deleter
{
- return GetCurrentProcessId ();
- }
+ void operator() (char* p) const {LocalFree (p);}
+ };
- static void
- print_error (char const* name)
+ static string
+ error (DWORD e)
{
- LPTSTR msg;
- DWORD e (GetLastError());
-
- if (!FormatMessage(
+ char* msg;
+ if (!FormatMessageA (
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
0,
e,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPTSTR) &msg,
+ (char*)&msg,
0,
0))
- {
- cerr << name << ": error: unknown error code " << e << endl;
- return;
- }
+ return "unknown error code " + to_string (e);
- cerr << name << ": error: " << msg << endl;
- LocalFree (msg);
+ unique_ptr<char, msg_deleter> m (msg);
+ return msg;
}
- static process_info
- start_process (char const* args[], char const* name, bool err, bool out)
+ static inline string
+ last_error ()
{
- HANDLE out_h[2];
- HANDLE in_eh[2];
- HANDLE in_oh[2];
- SECURITY_ATTRIBUTES sa;
+ return error (GetLastError ());
+ }
- sa.nLength = sizeof (SECURITY_ATTRIBUTES);
- sa.bInheritHandle = true;
- sa.lpSecurityDescriptor = 0;
+ static path
+ path_search (const path& f)
+ {
+ typedef path::traits traits;
+
+ // If there is a directory component in the file, then the PATH search
+ // does not apply.
+ //
+ if (!f.directory ().empty ())
+ return f;
+
+ string paths;
- if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) ||
- !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0))
+ // If there is no PATH in the environment then the default search path is
+ // the current directory.
+ //
+ if (const char* s = getenv ("PATH"))
{
- print_error (name);
- throw process_failure ();
+ paths = s;
+
+ // Also check the current directory.
+ //
+ paths += traits::path_separator;
}
+ else
+ paths = traits::path_separator;
+
+ struct stat info;
- if (err)
+ for (size_t b (0), e (paths.find (traits::path_separator));
+ b != string::npos;)
{
- if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) ||
- !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0))
+ path p (string (paths, b, e != string::npos ? e - b : e));
+
+ // Empty path (i.e., a double colon or a colon at the beginning or end
+ // of PATH) means search in the current dirrectory.
+ //
+ if (p.empty ())
+ p = path (".");
+
+ path dp (p / f);
+
+ // Just check that the file exist without checking for permissions, etc.
+ //
+ if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
+ return dp;
+
+ // Also try the path with the .exe extension.
+ //
+ dp += ".exe";
+
+ if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
+ return dp;
+
+ if (e == string::npos)
+ b = e;
+ else
{
- print_error (name);
- throw process_failure ();
+ b = e + 1;
+ e = paths.find (traits::path_separator, b);
}
}
- if (out)
+ return path ();
+ }
+
+ class auto_handle
+ {
+ public:
+ explicit
+ auto_handle (HANDLE h = INVALID_HANDLE_VALUE) noexcept: handle_ (h) {}
+
+ auto_handle (const auto_handle&) = delete;
+ auto_handle& operator= (const auto_handle&) = delete;
+
+ ~auto_handle () noexcept {reset ();}
+
+ HANDLE
+ get () const noexcept {return handle_;}
+
+ HANDLE
+ release () noexcept
+ {
+ HANDLE r (handle_);
+ handle_ = INVALID_HANDLE_VALUE;
+ return r;
+ }
+
+ void
+ reset (HANDLE h = INVALID_HANDLE_VALUE) noexcept
{
- if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) ||
- !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0))
+ if (handle_ != INVALID_HANDLE_VALUE)
{
- print_error (name);
- throw process_failure ();
+ bool r (CloseHandle (handle_));
+
+ // The valid process, thread or file handle that has no IO operations
+ // being performed on it should close successfully, unless something
+ // is severely damaged.
+ //
+ assert (r);
}
+
+ handle_ = h;
}
+ private:
+ HANDLE handle_;
+ };
+
+ process::
+ process (const char* cwd, char const* const args[], int in, int out, int err)
+ {
+ using pipe = auto_handle[2];
+
+ pipe out_h;
+ pipe in_oh;
+ pipe in_eh;
+
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof (SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = true;
+ sa.lpSecurityDescriptor = 0;
+
+ auto fail = [](const char* m = nullptr)
+ {
+ throw process_error (m == nullptr ? last_error () : m);
+ };
+
+ // Create a pipe and clear the inherit flag on the parent side.
+ //
+ auto create_pipe = [&sa, &fail](pipe& p, int parent)
+ {
+ HANDLE ph[2];
+ if (!CreatePipe (&ph[0], &ph[1], &sa, 0))
+ fail ();
+
+ p[0].reset (ph[0]);
+ p[1].reset (ph[1]);
+
+ if (!SetHandleInformation (p[parent].get (), HANDLE_FLAG_INHERIT, 0))
+ fail ();
+ };
+
+ if (in == -1)
+ create_pipe (out_h, 1);
+
+ if (out == -1)
+ create_pipe (in_oh, 0);
+
+ if (err == -1)
+ create_pipe (in_eh, 0);
+
// Create the process.
//
path file (args[0]);
@@ -229,23 +404,19 @@ namespace butl
file = path_search (file);
if (file.empty ())
- {
- cerr << args[0] << ": error: file not found" << endl;
- throw process_failure ();
- }
+ fail ("file not found");
// Serialize the arguments to string.
//
string cmd_line;
- for (char const** p (args); *p != 0; ++p)
+ for (char const* 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.
+ // 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);
@@ -265,7 +436,7 @@ namespace butl
cmd_line += '"';
}
- // Prepare other info.
+ // Prepare other process information.
//
STARTUPINFO si;
PROCESS_INFORMATION pi;
@@ -273,20 +444,62 @@ namespace butl
memset (&si, 0, sizeof (STARTUPINFO));
memset (&pi, 0, sizeof (PROCESS_INFORMATION));
- si.cb = sizeof(STARTUPINFO);
+ si.cb = sizeof (STARTUPINFO);
+ si.dwFlags |= STARTF_USESTDHANDLES;
- if (err)
- si.hStdError = in_eh[1];
- else
- si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ // Resolve file descriptor to HANDLE and make sure it is inherited. Note
+ // that the handle is closed when _close() is called for the associated
+ // file descriptor.
+ //
+ auto get_osfhandle = [&fail](int fd) -> HANDLE
+ {
+ HANDLE h (reinterpret_cast<HANDLE> (_get_osfhandle (fd)));
+ if (h == INVALID_HANDLE_VALUE)
+ fail ("unable to obtain file handle");
- if (out)
- si.hStdOutput = in_oh[1];
- else
- si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
+ // SetHandleInformation() fails for standard handles. We assume they are
+ // inherited by default.
+ //
+ if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
+ {
+ if (!SetHandleInformation (
+ h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
+ fail ();
+ }
- si.hStdInput = out_h[0];
- si.dwFlags |= STARTF_USESTDHANDLES;
+ return h;
+ };
+
+ si.hStdInput = in == -1
+ ? out_h[0].get ()
+ : in == STDIN_FILENO
+ ? GetStdHandle (STD_INPUT_HANDLE)
+ : get_osfhandle (in);
+
+ si.hStdOutput = out == -1
+ ? in_oh[1].get ()
+ : out == STDOUT_FILENO
+ ? GetStdHandle (STD_OUTPUT_HANDLE)
+ : get_osfhandle (out);
+
+ si.hStdError = err == -1
+ ? in_eh[1].get ()
+ : err == STDERR_FILENO
+ ? GetStdHandle (STD_ERROR_HANDLE)
+ : get_osfhandle (err);
+
+ // Perform standard stream redirection if requested.
+ //
+ if (si.hStdError == GetStdHandle (STD_OUTPUT_HANDLE))
+ si.hStdError = si.hStdOutput;
+ else if (si.hStdOutput == GetStdHandle (STD_ERROR_HANDLE))
+ si.hStdOutput = si.hStdError;
+
+ if (si.hStdError == GetStdHandle (STD_INPUT_HANDLE) ||
+ si.hStdOutput == GetStdHandle (STD_INPUT_HANDLE) ||
+ si.hStdInput == GetStdHandle (STD_OUTPUT_HANDLE) ||
+ si.hStdInput == GetStdHandle (STD_ERROR_HANDLE))
+ fail ("invalid file descriptor");
if (!CreateProcess (
file.string ().c_str (),
@@ -296,80 +509,107 @@ namespace butl
true, // Inherit handles.
0, // Creation flags.
0, // Use our environment.
- 0, // Use our current directory.
+ cwd != nullptr && *cwd != '\0' ? cwd : nullptr,
&si,
&pi))
+ fail ();
+
+ auto_handle (pi.hThread).reset (); // Close.
+ auto_handle process (pi.hProcess);
+
+ // Convert file handles to file descriptors. Note that the handle is
+ // closed when _close() is called for the returned file descriptor.
+ //
+ auto open_osfhandle = [&fail](auto_handle& h) -> int
{
- print_error (name);
- throw process_failure ();
- }
+ int fd (
+ _open_osfhandle (reinterpret_cast<intptr_t> (h.get ()), _O_TEXT));
- CloseHandle (pi.hThread);
- CloseHandle (out_h[0]);
+ if (fd == -1)
+ fail ("unable to convert file handle to file descriptor");
- if (err)
- CloseHandle (in_eh[1]);
+ h.release ();
+ return fd;
+ };
- if (out)
- CloseHandle (in_oh[1]);
+ auto_fd out_fd (in == -1 ? open_osfhandle (out_h[1]) : -1);
+ auto_fd in_ofd (out == -1 ? open_osfhandle (in_oh[0]) : -1);
+ auto_fd in_efd (err == -1 ? open_osfhandle (in_eh[0]) : -1);
- process_info r;
- r.id = pi.hProcess;
- r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0);
+ this->out_fd = out_fd.release ();
+ this->in_ofd = in_ofd.release ();
+ this->in_efd = in_efd.release ();
- if (r.out_fd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
+ this->handle = process.release ();
- if (err)
- {
- // Pass _O_TEXT to get newline translation.
- //
- r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT);
+ // 0 has a special meaning denoting a terminated process handle.
+ //
+ assert (this->handle != 0 && this->handle != INVALID_HANDLE_VALUE);
+ }
- if (r.in_efd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
- }
- else
- r.in_efd = 0;
+ process::
+ process (const char* cwd, char const* const args[],
+ process& in, int out, int err)
+ : process (cwd, args, in.in_ofd, out, err)
+ {
+ assert (in.in_ofd != -1); // Should be a pipe.
+ _close (in.in_ofd); // Close it on our side.
+ }
- if (out)
+ bool process::
+ wait ()
+ {
+ if (handle != 0)
{
- // Pass _O_TEXT to get newline translation.
- //
- r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT);
+ DWORD s;
+ DWORD e (NO_ERROR);
+ if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 ||
+ !GetExitCodeProcess (handle, &s))
+ e = GetLastError ();
- if (r.in_ofd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
+ auto_handle h (handle); // Auto-deleter.
+ handle = 0; // We have tried.
+
+ if (e != NO_ERROR)
+ throw process_error (error (e));
+
+ status = s;
}
- else
- r.in_ofd = 0;
- return r;
+ return status == 0;
}
- static bool
- wait_process (process_info pi, char const* name)
+ bool process::
+ try_wait (bool& s)
{
- DWORD status;
-
- if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 ||
- !GetExitCodeProcess (pi.id, &status))
+ if (handle != 0)
{
- print_error (name);
- throw process_failure ();
+ DWORD r (WaitForSingleObject (handle, 0));
+ if (r == WAIT_TIMEOUT)
+ return false;
+
+ DWORD s;
+ DWORD e (NO_ERROR);
+ if (r != WAIT_OBJECT_0 || !GetExitCodeProcess (handle, &s))
+ e = GetLastError ();
+
+ auto_handle h (handle);
+ handle = 0; // We have tried.
+
+ if (e != NO_ERROR)
+ throw process_error (error (e));
+
+ status = s;
}
- CloseHandle (pi.id);
- return status == 0;
+ s = status == 0;
+ return true;
+ }
+
+ process::id_type process::
+ current_id ()
+ {
+ return GetCurrentProcessId ();
}
#endif // _WIN32