diff options
Diffstat (limited to 'butl/process.cxx')
-rw-r--r-- | butl/process.cxx | 1440 |
1 files changed, 0 insertions, 1440 deletions
diff --git a/butl/process.cxx b/butl/process.cxx deleted file mode 100644 index 940560c..0000000 --- a/butl/process.cxx +++ /dev/null @@ -1,1440 +0,0 @@ -// file : butl/process.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include <butl/process> - -#ifndef _WIN32 -# include <unistd.h> // execvp, fork, dup2, pipe, chdir, *_FILENO, getpid -# include <sys/wait.h> // waitpid -# include <sys/types.h> // _stat -# include <sys/stat.h> // _stat(), S_IS* -#else -# include <butl/win32-utility> - -# include <psapi.h> // EnumProcessModules(), etc - -# include <io.h> // _get_osfhandle(), _close() -# include <stdlib.h> // _MAX_PATH -# include <sys/types.h> // stat -# include <sys/stat.h> // stat(), S_IS* - -# ifdef _MSC_VER // Unlikely to be fixed in newer versions. -# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) - -# define STDIN_FILENO 0 -# define STDOUT_FILENO 1 -# define STDERR_FILENO 2 -# endif // _MSC_VER - -# include <cstdlib> // getenv(), __argv[] - -# include <butl/small-vector> -#endif - -#include <errno.h> - -#include <ios> // ios_base::failure -#include <cassert> -#include <cstddef> // size_t -#include <cstring> // strlen(), strchr() -#include <utility> // move() -#include <ostream> - -#include <butl/utility> // casecmp() -#include <butl/fdstream> // fdnull() -#include <butl/process-details> - -#include <iostream> - -using namespace std; - -#ifdef _WIN32 -using namespace butl::win32; -#endif - -namespace butl -{ - shared_mutex process_spawn_mutex; - - // process - // - static process_path - path_search (const char*, const dir_path&); - - process_path process:: - path_search (const char* f, bool init, const dir_path& fb) - { - process_path r (try_path_search (f, init, fb)); - - if (r.empty ()) - throw process_error (ENOENT); - - return r; - } - - process_path process:: - try_path_search (const char* f, bool init, const dir_path& fb) - { - process_path r (butl::path_search (f, fb)); - - if (!init && !r.empty ()) - { - path& rp (r.recall); - r.initial = (rp.empty () ? (rp = path (f)) : rp).string ().c_str (); - } - - return r; - } - - void process:: - print (ostream& o, const char* const args[], size_t n) - { - size_t m (0); - const char* const* p (args); - do - { - if (m != 0) - o << " |"; // Trailing space will be added inside the loop. - - for (m++; *p != nullptr; p++, m++) - { - if (p != args) - o << ' '; - - // Quote if empty or contains spaces. - // - bool q (**p == '\0' || strchr (*p, ' ') != nullptr); - - if (q) - o << '"'; - - o << *p; - - if (q) - o << '"'; - } - - if (m < n) // Can we examine the next element? - { - p++; - m++; - } - - } while (*p != nullptr); - } - - process:: - process (const char* cwd, - const process_path& pp, const char* args[], - process& in, int out, int err) - : process (cwd, pp, args, in.in_ofd.get (), out, err) - { - assert (in.in_ofd.get () != -1); // Should be a pipe. - in.in_ofd.reset (); // Close it on our side. - } - -#ifndef _WIN32 - - static process_path - path_search (const char* f, const dir_path& fb) - { - // Note that there is a similar version for Win32. - - typedef path::traits traits; - - size_t fn (strlen (f)); - - process_path r (f, path (), path ()); // Make sure it is not empty. - path& rp (r.recall); - path& ep (r.effect); - - // Check that the file exists and has at least one executable bit set. - // This way we get a bit closer to the "continue search on EACCES" - // semantics (see below). - // - auto exists = [] (const char* f) -> bool - { - struct stat si; - return (stat (f, &si) == 0 && - S_ISREG (si.st_mode) && - (si.st_mode & (S_IEXEC | S_IXGRP | S_IXOTH)) != 0); - }; - - auto search = [&ep, f, fn, &exists] (const char* d, - size_t dn, - bool norm = false) -> bool - { - string s (move (ep).string ()); // Reuse buffer. - - if (dn != 0) - { - s.assign (d, dn); - - if (!traits::is_separator (s.back ())) - s += traits::directory_separator; - } - - s.append (f, fn); - ep = path (move (s)); // Move back into result. - - if (norm) - ep.normalize (); - - return exists (ep.string ().c_str ()); - }; - - // If there is a directory component in the file, then the PATH search - // does not apply. If the path is relative, then prepend CWD. In both - // cases make sure the file actually exists. - // - if (traits::find_separator (f, fn) != nullptr) - { - if (traits::absolute (f, fn)) - { - if (exists (f)) - return r; - } - else - { - const string& d (traits::current_directory ()); - - if (search (d.c_str (), d.size (), true)) - return r; - } - - return process_path (); - } - - // The search order is documented in exec(3). Some of the differences - // compared to exec*p() functions: - // - // 1. If there no PATH, we don't default to current directory/_CS_PATH. - // 2. We do not continue searching on EACCES from execve(). - // 3. We do not execute via default shell on ENOEXEC from execve(). - // - for (const char* b (getenv ("PATH")), *e; - b != nullptr; - b = (e != nullptr ? e + 1 : e)) - { - e = strchr (b, traits::path_separator); - - // Empty path (i.e., a double colon or a colon at the beginning or end - // of PATH) means search in the current dirrectory. Silently skip - // invalid paths. - // - try - { - if (search (b, e != nullptr ? e - b : strlen (b))) - return r; - } - catch (const invalid_path&) - { - } - } - - // If we were given a fallback, try that. - // - if (!fb.empty ()) - { - if (search (fb.string ().c_str (), fb.string ().size ())) - { - // In this case we have to set the recall path. And we know from - // search() implementation that it will be the same as effective. - // Which means we can just move effective to recall. - // - rp.swap (ep); - - return r; - } - } - - // Did not find anything. - // - return process_path (); - } - - process:: - process (const char* cwd, - const process_path& pp, const char* args[], - int in, int out, int err) - { - fdpipe out_fd; - fdpipe in_ofd; - fdpipe in_efd; - - auto fail = [] (bool child) - { - if (child) - throw process_child_error (errno); - else - throw process_error (errno); - }; - - auto open_pipe = [] () -> fdpipe - { - try - { - return fdopen_pipe (); - } - catch (const ios_base::failure&) - { - // Translate to process_error. - // - // For old versions of g++ (as of 4.9) ios_base::failure is not derived - // from system_error and so we cannot recover the errno value. On the - // other hand the only possible values are EMFILE and ENFILE. Lets use - // EMFILE as the more probable. This is a temporary code after all. - // - throw process_error (EMFILE); - } - }; - - auto open_null = [] () -> auto_fd - { - try - { - return fdnull (); - } - catch (const ios_base::failure& e) - { - // Translate to process_error. - // - // For old versions of g++ (as of 4.9) ios_base::failure is not derived - // from system_error and so we cannot recover the errno value. Lets use - // EIO in this case. This is a temporary code after all. - // - const system_error* se (dynamic_cast<const system_error*> (&e)); - - throw process_error (se != nullptr - ? se->code ().value () - : EIO); - } - }; - - // If we are asked to open null (-2) then open "half-pipe". - // - if (in == -1) - out_fd = open_pipe (); - else if (in == -2) - out_fd.in = open_null (); - - if (out == -1) - in_ofd = open_pipe (); - else if (out == -2) - in_ofd.out = open_null (); - - if (err == -1) - in_efd = open_pipe (); - else if (err == -2) - in_efd.out = open_null (); - - { - ulock l (process_spawn_mutex); // Will not be released in child. - handle = fork (); - - if (handle == -1) - fail (false); - - if (handle == 0) - { - // Child. - // - // 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 pipe afterwards. - // - auto duplicate = [&fail] (int sd, int fd, fdpipe& pd) - { - if (fd == -1 || fd == -2) - fd = (sd == STDIN_FILENO ? pd.in : pd.out).get (); - - assert (fd > -1); - if (dup2 (fd, sd) == -1) - fail (true); - - pd.in.reset (); // Silently close. - pd.out.reset (); // Silently close. - }; - - if (in != STDIN_FILENO) - duplicate (STDIN_FILENO, in, out_fd); - - // If stdout is redirected to stderr (out == 2) we need to duplicate it - // after duplicating stderr to pickup the proper fd. Otherwise keep the - // "natual" order of duplicate() calls, so if stderr is redirected to - // stdout it picks up the proper fd as well. - // - if (out == STDERR_FILENO) - { - if (err != STDERR_FILENO) - duplicate (STDERR_FILENO, err, in_efd); - - if (out != STDOUT_FILENO) - duplicate (STDOUT_FILENO, out, in_ofd); - } - else - { - if (out != STDOUT_FILENO) - duplicate (STDOUT_FILENO, out, in_ofd); - - if (err != STDERR_FILENO) - duplicate (STDERR_FILENO, err, in_efd); - } - - // Change current working directory if requested. - // - if (cwd != nullptr && *cwd != '\0' && chdir (cwd) != 0) - fail (true); - - if (execv (pp.effect_string (), const_cast<char**> (&args[0])) == -1) - fail (true); - } - } // Release the lock in parent. - - assert (handle != 0); // Shouldn't get here unless in the parent process. - - this->out_fd = move (out_fd.out); - this->in_ofd = move (in_ofd.in); - this->in_efd = move (in_efd.in); - } - - bool process:: - wait (bool ie) - { - if (handle != 0) - { - int es; - int r (waitpid (handle, &es, 0)); - handle = 0; // We have tried. - - if (r == -1) - { - // If ignore errors then just leave exit nullopt, so it has "no exit - // information available" semantics. - // - if (!ie) - throw process_error (errno); - } - else - exit = process_exit (es, process_exit::as_status); - } - - return exit && exit->normal () && exit->code () == 0; - } - - bool process:: - try_wait () - { - if (handle != 0) - { - int es; - int r (waitpid (handle, &es, WNOHANG)); - - if (r == 0) // Not exited yet. - return false; - - handle = 0; // We have tried. - - if (r == -1) - throw process_error (errno); - - exit = process_exit (es, process_exit::as_status); - } - - return true; - } - - process::id_type process:: - current_id () - { - return getpid (); - } - - // process_exit - // - process_exit:: - process_exit (code_type c) - // - // Note that such an initialization is not portable as POSIX doesn't - // specify the bits layout for the value returned by waitpid(). However - // for the major POSIX systems (Linux, FreeBSD, MacOS) it is the - // following: - // - // [0, 7) - terminating signal - // [7, 8) - coredump flag - // [8, 16) - program exit code - // - // Also the lowest 7 bits value is used to distinguish the normal and - // abnormal process terminations. If it is zero then the program exited - // normally and the exit code is available. - // - : status (c << 8) - { - } - - // Make sure the bits layout we stick to (read above) correlates to the W*() - // macros implementations for the current platform. - // - namespace details - { - // W* macros may require an argument to be lvalue (for example for glibc). - // - static const process_exit::status_type status_code (0xFF00); - - static_assert (WIFEXITED (status_code) && - WEXITSTATUS (status_code) == 0xFF && - !WIFSIGNALED (status_code), - "unexpected process exit status bits layout"); - } - - bool process_exit:: - normal () const - { - return WIFEXITED (status); - } - - process_exit::code_type process_exit:: - code () const - { - assert (normal ()); - return WEXITSTATUS (status); - } - - int process_exit:: - signal () const - { - assert (!normal ()); - - // WEXITSTATUS() and WIFSIGNALED() can both return false for the same - // status, so we have neither exit code nor signal. We return zero for - // such a case. - // - return WIFSIGNALED (status) ? WTERMSIG (status) : 0; - } - - bool process_exit:: - core () const - { - assert (!normal ()); - - // Not a POSIX macro (available on Linux, FreeBSD, MacOS). - // -#ifdef WCOREDUMP - return WIFSIGNALED (status) && WCOREDUMP (status); -#else - return false; -#endif - } - - string process_exit:: - description () const - { - assert (!normal ()); - - // It would be convenient to use strsignal() or sys_siglist[] to obtain a - // signal name for the number, but the function is not thread-safe and the - // array is not POSIX. So we will use the custom mapping of POSIX signals - // (IEEE Std 1003.1-2008, 2016 Edition) to their names (as they appear in - // glibc). - // - switch (signal ()) - { - case SIGHUP: return "hangup (SIGHUP)"; - case SIGINT: return "interrupt (SIGINT)"; - case SIGQUIT: return "quit (SIGQUIT)"; - case SIGILL: return "illegal instruction (SIGILL)"; - case SIGABRT: return "aborted (SIGABRT)"; - case SIGFPE: return "floating point exception (SIGFPE)"; - case SIGKILL: return "killed (SIGKILL)"; - case SIGSEGV: return "segmentation fault (SIGSEGV)"; - case SIGPIPE: return "broken pipe (SIGPIPE)"; - case SIGALRM: return "alarm clock (SIGALRM)"; - case SIGTERM: return "terminated (SIGTERM)"; - case SIGUSR1: return "user defined signal 1 (SIGUSR1)"; - case SIGUSR2: return "user defined signal 2 (SIGUSR2)"; - case SIGCHLD: return "child exited (SIGCHLD)"; - case SIGCONT: return "continued (SIGCONT)"; - case SIGSTOP: return "stopped (process; SIGSTOP)"; - case SIGTSTP: return "stopped (typed at terminal; SIGTSTP)"; - case SIGTTIN: return "stopped (tty input; SIGTTIN)"; - case SIGTTOU: return "stopped (tty output; SIGTTOU)"; - case SIGBUS: return "bus error (SIGBUS)"; - - // Unavailabe on MacOS 10.11. - // -#ifdef SIGPOLL - case SIGPOLL: return "I/O possible (SIGPOLL)"; -#endif - - case SIGPROF: return "profiling timer expired (SIGPROF)"; - case SIGSYS: return "bad system call (SIGSYS)"; - case SIGTRAP: return "trace/breakpoint trap (SIGTRAP)"; - case SIGURG: return "urgent I/O condition (SIGURG)"; - case SIGVTALRM: return "virtual timer expired (SIGVTALRM)"; - case SIGXCPU: return "CPU time limit exceeded (SIGXCPU)"; - case SIGXFSZ: return "file size limit exceeded (SIGXFSZ)"; - - case 0: return "status unknown"; - default: return "unknown signal " + to_string (signal ()); - } - } - -#else // _WIN32 - - static process_path - path_search (const char* f, const dir_path& fb) - { - // Note that there is a similar version for Win32. - - typedef path::traits traits; - - size_t fn (strlen (f)); - - // Unless there is already the .exe/.bat extension, then we will need to - // add it. - // - bool ext; - { - const char* e (traits::find_extension (f, fn)); - ext = (e == nullptr || - (casecmp (e, ".exe") != 0 && - casecmp (e, ".bat") != 0 && - casecmp (e, ".cmd") != 0)); - } - - process_path r (f, path (), path ()); // Make sure it is not empty. - path& rp (r.recall); - path& ep (r.effect); - - // Check that the file exists. Since the executable mode is set according - // to the file extension, we don't check for that. - // - auto exists = [] (const char* f) -> bool - { - struct _stat si; - return _stat (f, &si) == 0 && S_ISREG (si.st_mode); - }; - - // Check with extensions: .exe, .cmd, and .bat. - // - auto exists_ext = [&exists] (string& s) -> bool - { - size_t i (s.size () + 1); // First extension letter. - - s += ".exe"; - if (exists (s.c_str ())) - return true; - - s[i] = 'c'; s[i + 1] = 'm'; s[i + 2] = 'd'; - if (exists (s.c_str ())) - return true; - - s[i] = 'b'; s[i + 1] = 'a'; s[i + 2] = 't'; - return exists (s.c_str ()); - }; - - auto search = [&ep, f, fn, ext, &exists, &exists_ext] ( - const char* d, size_t dn, bool norm = false) -> bool - { - string s (move (ep).string ()); // Reuse buffer. - - if (dn != 0) - { - s.assign (d, dn); - - if (!traits::is_separator (s.back ())) - s += traits::directory_separator; - } - - s.append (f, fn); - ep = path (move (s)); // Move back into result. - - if (norm) - ep.normalize (); - - if (!ext) - return exists (ep.string ().c_str ()); - - // Try with the extensions. - // - s = move (ep).string (); - bool e (exists_ext (s)); - ep = path (move (s)); - return e; - }; - - // If there is a directory component in the file, then the PATH search - // does not apply. If the path is relative, then prepend CWD. In both - // cases we may still need to append the extension and make sure the file - // actually exists. - // - if (traits::find_separator (f, fn) != nullptr) - { - if (traits::absolute (f, fn)) - { - bool e; - if (!ext) - e = exists (r.effect_string ()); - else - { - string s (f, fn); - e = exists_ext (s); - ep = path (move (s)); - } - - if (e) - return r; - } - else - { - const string& d (traits::current_directory ()); - - if (search (d.c_str (), d.size (), true)) // Appends extension. - return r; - } - - return process_path (); - } - - // The search order is documented in CreateProcess(). First we look in the - // directory of the parent executable. - // - { - char d[_MAX_PATH + 1]; - DWORD n (GetModuleFileName (NULL, d, _MAX_PATH + 1)); - - if (n == 0 || n == _MAX_PATH + 1) // Failed or truncated. - throw process_error (last_error_msg ()); - - const char* p (traits::rfind_separator (d, n)); - assert (p != nullptr); - - if (search (d, p - d + 1)) // Include trailing slash. - { - // In this case we have to set the recall path. - // - // Note that the directory we have extracted is always absolute but - // the parent's recall path (argv[0]) might be relative. It seems, - // ideally, we would want to use parent's argv[0] dir (if any) to form - // the recall path. In particular, if the parent has no directory, - // then it means it was found via the standard search (e.g., PATH) and - // then so should the child. - // - // How do we get the parent's argv[0]? Luckily, here is __argv on - // Windows. - // - const char* d (__argv[0]); - size_t n (strlen (d)); - if (const char* p = traits::rfind_separator (d, n)) - { - string s (d, p - d + 1); // Include trailing slash. - s.append (f, fn); - rp = path (move (s)); - - // If recall is the same as effective, then set effective to empty. - // - if (rp == ep) - ep.clear (); - } - - return r; - } - } - - // Next look in the current working directory. Crazy, I know. - // - // The recall path is the same as initial, though it might not be a bad - // idea to prepend .\ for clarity. - // - { - const string& d (traits::current_directory ()); - - if (search (d.c_str (), d.size ())) - return r; - } - - // Now search in PATH. Recall is unchanged. - // - for (const char* b (getenv ("PATH")), *e; - b != nullptr; - b = (e != nullptr ? e + 1 : e)) - { - e = strchr (b, traits::path_separator); - - // Empty path (i.e., a double colon or a colon at the beginning or end - // of PATH) means search in the current directory. Silently skip invalid - // paths. - // - try - { - if (search (b, e != nullptr ? e - b : strlen (b))) - return r; - } - catch (const invalid_path&) - { - } - } - - // Finally, if we were given a fallback, try that. This case is similar to - // searching in the parent executable's directory. - // - if (!fb.empty ()) - { - // I would have been nice to preserve trailing slash (by using - // representation() instead of string()), but that would involve a - // copy. Oh, well, can't always win. - // - if (search (fb.string ().c_str (), fb.string ().size ())) - { - // In this case we have to set the recall path. At least here we got - // to keep the original slash. - // - rp = fb; - rp /= f; - - // If recall is the same as effective, then set effective to empty. - // - if (rp == ep) - ep.clear (); - - return r; - } - } - - // Did not find anything. - // - return process_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 (handle_ != INVALID_HANDLE_VALUE) - { - 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_; - }; - - // Make handles inheritable. The process_spawn_mutex must be pre-acquired for - // exclusive access. Revert handles inheritability state in destructor. - // - // There is a period of time when the process ctor makes file handles it - // passes to the child to be inheritable, that otherwise are not inheritable - // by default. During this time these handles can also be inherited by other - // (irrelevant) child processed spawned from other threads. That can lead to - // some unwanted consequences, such as inability to delete a file - // corresponding to such a handle until all childs, that the handle leaked - // into, terminate. To prevent this behavior the specific sequence of steps - // (that involves making handles inheritable, spawning process and reverting - // handles to non-inheritable state back) will be performed after aquiring - // the process_spawn_mutex (that is released afterwards). - // - class inheritability_guard - { - public: - // Require the proof that the mutex is pre-acquired for exclusive access. - // - inheritability_guard (const ulock&) {} - - ~inheritability_guard () - { - for (auto h: handles_) - inheritable (h, false); // Can't throw. - } - - void - inheritable (HANDLE h) - { - inheritable (h, true); // Can throw. - handles_.push_back (h); - } - - private: - void - inheritable (HANDLE h, bool state) - { - if (!SetHandleInformation ( - h, HANDLE_FLAG_INHERIT, state ? HANDLE_FLAG_INHERIT : 0)) - { - if (state) - throw process_error (last_error_msg ()); - - // We should be able to successfully reset the HANDLE_FLAG_INHERIT flag - // that we successfully set, unless something is severely damaged. - // - assert (false); - } - } - - private: - small_vector<HANDLE, 3> handles_; - }; - - process:: - process (const char* cwd, - const process_path& pp, const char* args[], - int in, int out, int err) - { - // Figure out if this is a batch file since running them requires starting - // cmd.exe and passing the batch file as an argument (see CreateProcess() - // for deails). - // - const char* batch (nullptr); - { - const char* p (pp.effect_string ()); - const char* e (path::traits::find_extension (p, strlen (p))); - if (e != nullptr && (casecmp (e, ".bat") == 0 || - casecmp (e, ".cmd") == 0)) - { - batch = getenv ("COMSPEC"); - - if (batch == nullptr) - batch = "C:\\Windows\\System32\\cmd.exe"; - } - } - - fdpipe out_fd; - fdpipe in_ofd; - fdpipe in_efd; - - auto open_pipe = [] () -> fdpipe - { - try - { - return fdopen_pipe (); - } - catch (const ios_base::failure&) - { - // Translate to process_error. - // - // For old versions of g++ (as of 4.9) ios_base::failure is not derived - // from system_error and so we cannot recover the errno value. On the - // other hand the only possible values are EMFILE and ENFILE. Lets use - // EMFILE as the more probable. Also let's make no distinction for VC. - // This is a temporary code after all. - // - throw process_error (EMFILE); - } - }; - - auto fail = [](const char* m = nullptr) - { - throw process_error (m == nullptr ? last_error_msg () : m); - }; - - auto open_null = [] () -> auto_fd - { - // Note that we are using a faster, temporary file-based emulation of - // NUL since we have no way of making sure the child buffers things - // properly (and by default they seem no to). - // - try - { - return fdnull (true); - } - catch (const ios_base::failure& e) - { - // Translate to process_error. - // - // For old versions of g++ (as of 4.9) ios_base::failure is not derived - // from system_error and so we cannot recover the errno value. Lets use - // EIO in this case. This is a temporary code after all. - // - const system_error* se (dynamic_cast<const system_error*> (&e)); - - throw process_error (se != nullptr - ? se->code ().value () - : EIO); - } - }; - - // If we are asked to open null (-2) then open "half-pipe". - // - if (in == -1) - out_fd = open_pipe (); - else if (in == -2) - out_fd.in = open_null (); - - if (out == -1) - in_ofd = open_pipe (); - else if (out == -2) - in_ofd.out = open_null (); - - if (err == -1) - in_efd = open_pipe (); - else if (err == -2) - in_efd.out = open_null (); - - // Create the process. - // - - // Serialize the arguments to string. - // - string cmd_line; - { - auto append = [&cmd_line] (const string& a) - { - if (!cmd_line.empty ()) - 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. - // - bool quote (a.empty () || 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 += '"'; - }; - - if (batch != nullptr) - { - append (batch); - append ("/c"); - append (pp.effect_string ()); - } - - for (const char* const* p (args + (batch != nullptr ? 1 : 0)); - *p != 0; - ++p) - append (*p); - } - - // Prepare other process information. - // - STARTUPINFO si; - PROCESS_INFORMATION pi; - memset (&si, 0, sizeof (STARTUPINFO)); - memset (&pi, 0, sizeof (PROCESS_INFORMATION)); - - si.cb = sizeof (STARTUPINFO); - si.dwFlags |= STARTF_USESTDHANDLES; - - { - ulock l (process_spawn_mutex); - inheritability_guard ig (l); - - // Resolve file descriptor to HANDLE and make sure it is inherited. Note - // that the handle is closed either when CloseHandle() is called for it - // or when _close() is called for the associated file descriptor. Make - // sure that either the original file descriptor or the resulting HANDLE - // is closed but not both of them. - // - auto get_osfhandle = [&fail, &ig] (int fd) -> HANDLE - { - HANDLE h (reinterpret_cast<HANDLE> (_get_osfhandle (fd))); - if (h == INVALID_HANDLE_VALUE) - fail ("unable to obtain file handle"); - - // Make the handle inheritable by the child unless it is already - // inheritable. - // - DWORD f; - if (!GetHandleInformation (h, &f)) - fail (); - - // Note that the flag check is essential as SetHandleInformation() - // fails for standard handles and their duplicates. - // - if ((f & HANDLE_FLAG_INHERIT) == 0) - ig.inheritable (h); - - return h; - }; - - si.hStdInput = in == -1 || in == -2 - ? get_osfhandle (out_fd.in.get ()) - : (in == STDIN_FILENO - ? GetStdHandle (STD_INPUT_HANDLE) - : get_osfhandle (in)); - - si.hStdOutput = out == -1 || out == -2 - ? get_osfhandle (in_ofd.out.get ()) - : (out == STDOUT_FILENO - ? GetStdHandle (STD_OUTPUT_HANDLE) - : get_osfhandle (out)); - - si.hStdError = err == -1 || err == -2 - ? get_osfhandle (in_efd.out.get ()) - : (err == STDERR_FILENO - ? GetStdHandle (STD_ERROR_HANDLE) - : get_osfhandle (err)); - - // Perform standard stream redirection if requested. - // - if (err == STDOUT_FILENO) - si.hStdError = si.hStdOutput; - else if (out == STDERR_FILENO) - si.hStdOutput = si.hStdError; - - if (err == STDIN_FILENO || - out == STDIN_FILENO || - in == STDOUT_FILENO || - in == STDERR_FILENO) - fail ("invalid file descriptor"); - - // Ready for some "Fun with Windows"(TM)? Here is what's in today's - // episode: MSYS2 (actually, Cygwin) tries to emulate POSIX fork() on - // Win32 via some pretty heavy hackery. As a result it makes a bunch of - // assumptions such as that the child process will have the same virtual - // memory position as the parent and that nobody interferes in its - // child-parent dance. - // - // This, however, doesn't always pan out: for reasons unknown Windows - // sometimes decides to start the child somewhere else (or, as Cygwin - // FAQ puts it: "sometimes Windows sets up a process environment that is - // even more hostile to fork() than usual"). Also things like Windows - // Defender (collectively called Big List Of Dodgy Apps/BLODA in Cygwin - // speak) do interfere in all kinds of ways. - // - // We also observe another issue that seem related: if we run multiple - // MSYS2-based applications in parallel (either from the same process - // or from several processes), then they sometimes terminate abnormally - // (but quietly, without printing any of the cygheap/fork diagnostics) - // with status 0xC0000142 (STATUS_DLL_INIT_FAILED). - // - // Cygwin FAQ suggests the following potential solutions: - // - // 1. Restart the process hoping things will pan out next time around. - // - // 2. Eliminate/disable programs from BLODA (disabling Defender helps - // a lot but not entirely). - // - // 3. Apparently switching from 32 to 64-bit should help (less chance - // for address collisions). - // - // 4. Rebase all the Cygwin DLLs (this is a topic for a another episode). - // - // To add to this list, we also have our own remedy (which is not - // generally applicable): - // - // 5. Make sure processes that you start don't need to fork. A good - // example would be tar that runs gz/bzip2/xz. Instead, we start and - // pipe them ourselves. - // - // So what's coming next is a hack that implements remedy #1: after - // starting the process we wait a bit (50ms) and check if it has - // terminated with STATUS_DLL_INIT_FAILED (the assumption here is that - // if this happens, it happens quickly). We then retry starting the - // process for up to a second. - // - // One way to improve this implementation would be to only do it for - // MSYS2-based programs, for example, by checking (EnumProcessModules()) - // if the process loaded the msys-2.0.dll (not clear though if it will - // be in the returned list if it has failed to initialize). With this - // improvement we could then wait longer and try harder. - // - optional<bool> msys; // Absent if we don't know. - - for (size_t ret (0); ret != 5; ++ret) - { - if (!CreateProcess ( - batch != nullptr ? batch : pp.effect_string (), - 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. - cwd != nullptr && *cwd != '\0' ? cwd : nullptr, - &si, - &pi)) - fail (); - - auto_handle (pi.hThread).reset (); // Close. - - // Detect if this is an MSYS2 process by checking if the process has - // loaded msys-2.0.dll. - // - size_t wait (200); - - if (!msys) - { - // Wait a bit for the process to load its DLLs. - // - if (WaitForSingleObject (pi.hProcess, 50) == WAIT_TIMEOUT) - { - wait -= 50; - - DWORD mn; - HMODULE ms[32]; // Normally it is one of the first. - - if (EnumProcessModules (pi.hProcess, ms, sizeof (ms), &mn)) - { - for (DWORD i (0); !msys && i != mn / sizeof (HMODULE); ++i) - { - char p[_MAX_PATH + 1]; - if (GetModuleFileNameExA (pi.hProcess, ms[i], p, sizeof (p))) - { - size_t n (strlen (p)); - if (n >= 12 && casecmp (p + n - 12, "msys-2.0.dll") == 0) - msys = true; - } - } - - if (!msys) - msys = false; - } - // EnumProcessModules() failed (presumably because the process has - // already exited), fall through. - } - // Process exited, fall through. - } - - if (msys && !*msys) - break; - - // Wait a bit longer and check if the process has terminated. If it is - // still running then we assume all is good. Otherwise, retry if this - // is the DLL initialization error. - // - DWORD s; - if (WaitForSingleObject (pi.hProcess, wait) != WAIT_OBJECT_0 || - !GetExitCodeProcess (pi.hProcess, &s) || - s != STATUS_DLL_INIT_FAILED) - break; - } - } // Revert handles back to non-inheritable and release the lock. - - // 0 has a special meaning denoting a terminated process handle. - // - this->handle = pi.hProcess; - assert (this->handle != 0 && this->handle != INVALID_HANDLE_VALUE); - - this->out_fd = move (out_fd.out); - this->in_ofd = move (in_ofd.in); - this->in_efd = move (in_efd.in); - } - - bool process:: - wait (bool ie) - { - if (handle != 0) - { - DWORD es; - DWORD e (NO_ERROR); - if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 || - !GetExitCodeProcess (handle, &es)) - e = GetLastError (); - - auto_handle h (handle); // Auto-deleter. - handle = 0; // We have tried. - - if (e == NO_ERROR) - { - exit = process_exit (); - exit->status = es; - } - else - { - // If ignore errors then just leave exit nullopt, so it has "no exit - // information available" semantics. - // - if (!ie) - throw process_error (error_msg (e)); - } - } - - return exit && exit->normal () && exit->code () == 0; - } - - bool process:: - try_wait () - { - if (handle != 0) - { - DWORD r (WaitForSingleObject (handle, 0)); - if (r == WAIT_TIMEOUT) - return false; - - DWORD es; - DWORD e (NO_ERROR); - if (r != WAIT_OBJECT_0 || !GetExitCodeProcess (handle, &es)) - e = GetLastError (); - - auto_handle h (handle); - handle = 0; // We have tried. - - if (e != NO_ERROR) - throw process_error (error_msg (e)); - - exit = process_exit (); - exit->status = es; - } - - return true; - } - - process::id_type process:: - id () const - { - id_type r (GetProcessId (handle)); - - if (r == 0) - throw process_error (last_error_msg ()); - - return r; - } - - process::id_type process:: - current_id () - { - return GetCurrentProcessId (); - } - - // process_exit - // - process_exit:: - process_exit (code_type c) - // - // The NTSTATUS value returned by GetExitCodeProcess() has the following - // layout of bits: - // - // [ 0, 16) - program exit code or exception code - // [16, 29) - facility - // [29, 30) - flag indicating if the status value is customer-defined - // [30, 31) - severity (00 -success, 01 - informational, 10 - warning, - // 11 - error) - // - : status (c) - { - } - - bool process_exit:: - normal () const - { - // We consider status values with severities other than 0 not being - // returned by the process and so denoting the abnormal termination. - // - return ((status >> 30) & 0x3) == 0; - } - - process_exit::code_type process_exit:: - code () const - { - assert (normal ()); - return status & 0xFFFF; - } - - string process_exit:: - description () const - { - assert (!normal ()); - - // Error codes (or, as MSDN calls them, exception codes) are defined in - // ntstatus.h. It is possible to obtain message descriptions for them - // using FormatMessage() with the FORMAT_MESSAGE_FROM_HMODULE flag and the - // handle returned by LoadLibrary("NTDLL.DLL") call. However, the returned - // messages are pretty much useless being format strings. For example for - // STATUS_ACCESS_VIOLATION error code the message string is "The - // instruction at 0x%p referenced memory at 0x%p. The memory could not be - // %s.". Also under Wine (1.9.8) it is not possible to obtain such a - // descriptions at all for some reason. - // - // Let's use a custom code-to-message mapping for the most common error - // codes, and extend it as needed. - // - // Note that the error code most likely will be messed up if the abnormal - // termination of a process is intercepted with the "searching for - // available solution" message box or debugger invocation. Also note that - // the same failure can result in different exit codes for a process being - // run on Windows nativelly and under Wine. For example under Wine 1.9.8 a - // process that fails due to the stack overflow exits normally with 0 - // status but prints the "err:seh:setup_exception stack overflow ..." - // message to stderr. - // - switch (status) - { - case STATUS_ACCESS_VIOLATION: return "access violation"; - case STATUS_DLL_INIT_FAILED: return "DLL initialization failed"; - case STATUS_INTEGER_DIVIDE_BY_ZERO: return "integer divided by zero"; - - // VC-compiled program that calls abort() terminates with this error code - // (0xC0000409). That differs from MinGW GCC-compiled one, that exits - // normally with status 3 (conforms to MSDN). Under Wine (1.9.8) such a - // program exits with status 3 for both VC and MinGW GCC. Sounds weird. - // - case STATUS_STACK_BUFFER_OVERRUN: return "stack buffer overrun"; - case STATUS_STACK_OVERFLOW: return "stack overflow"; - - default: - { - string desc ("unknown error 0x"); - - // Add error code hex representation (as it is defined in ntstatus.h). - // - // Strange enough, there is no easy way to convert a number into the - // hex string representation (not using streams). - // - const char digits[] = "0123456789ABCDEF"; - bool skip (true); // Skip leading zeros. - - auto add = [&desc, &digits, &skip] (unsigned char d, bool force) - { - if (d != 0 || !skip || force) - { - desc += digits[d]; - skip = false; - } - }; - - for (int i (sizeof (status) - 1); i >= 0 ; --i) - { - unsigned char c ((status >> (i * 8)) & 0xFF); - add ((c >> 4) & 0xF, false); // Convert the high 4 bits to a digit. - add (c & 0xF, i == 0); // Convert the low 4 bits to a digit. - } - - return desc; - } - } - } - -#endif // _WIN32 -} |