From 61ef82ec2b2ca396667f92a4e5c6ceb729c42086 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sun, 15 May 2016 17:11:27 +0300 Subject: Port to MinGW --- butl/process.cxx | 564 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 402 insertions(+), 162 deletions(-) (limited to 'butl/process.cxx') 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 // CreatePipe, CreateProcess -# include // _open_osfhandle +# include // CreatePipe(), CreateProcess() +# include // _open_osfhandle(), _get_osfhandle(), _close() # include // _O_TEXT +# include // getenv() +# include // stat +# include // stat(), S_IS* + +# include // unique_ptr + +# include #endif #include @@ -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 (&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 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 (_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 (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 -- cgit v1.1