From c61d6e14c08fec7658dbdc33c16b5feeece08fbf Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 29 Dec 2016 03:32:05 +0300 Subject: Add process_exit --- butl/process.cxx | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 266 insertions(+), 26 deletions(-) (limited to 'butl/process.cxx') diff --git a/butl/process.cxx b/butl/process.cxx index df9fb1f..74b370d 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -25,19 +25,18 @@ # define STDERR_FILENO 2 # endif // _MSC_VER -# include // unique_ptr -# include // __argv[] +# include // __argv[] # include #endif #include -#include // ios_base::failure +#include // ios_base::failure #include -#include // size_t -#include // strlen(), strchr() -#include // move() +#include // size_t +#include // strlen(), strchr() +#include // move() #include #include // casecmp() @@ -51,6 +50,8 @@ using namespace butl::win32; namespace butl { + // process + // static process_path path_search (const char*, const dir_path&); @@ -374,23 +375,23 @@ namespace butl { if (handle != 0) { - status_type es; + int es; int r (waitpid (handle, &es, 0)); handle = 0; // We have tried. if (r == -1) { - // If ignore errors then just leave status nullopt, so it has the same - // semantics as for abnormally terminated process. + // If ignore errors then just leave exit nullopt, so it has "no exit + // information available" semantics. // if (!ie) throw process_error (errno, false); } - else if (WIFEXITED (es)) - status = WEXITSTATUS (es); + else + exit = process_exit (es, process_exit::as_status); } - return status && *status == 0; + return exit && exit->normal () && exit->code () == 0; } bool process:: @@ -398,7 +399,7 @@ namespace butl { if (handle != 0) { - status_type es; + int es; int r (waitpid (handle, &es, WNOHANG)); if (r == 0) // Not exited yet. @@ -409,11 +410,10 @@ namespace butl if (r == -1) throw process_error (errno, false); - if (WIFEXITED (es)) - status = WEXITSTATUS (es); + exit = process_exit (es, process_exit::as_status); } - s = status && *status == 0; + s = exit && exit->normal () && exit->code () == 0; return true; } @@ -423,6 +423,135 @@ namespace butl 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 @@ -864,28 +993,31 @@ namespace butl { if (handle != 0) { - DWORD s; + DWORD es; DWORD e (NO_ERROR); if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 || - !GetExitCodeProcess (handle, &s)) + !GetExitCodeProcess (handle, &es)) e = GetLastError (); auto_handle h (handle); // Auto-deleter. handle = 0; // We have tried. if (e == NO_ERROR) - status = s; + { + exit = process_exit (); + exit->status = es; + } else { - // If ignore errors then just leave status nullopt, so it has the same - // semantics as for abnormally terminated process. + // 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 status && *status == 0; + return exit && exit->normal () && exit->code () == 0; } bool process:: @@ -897,9 +1029,9 @@ namespace butl if (r == WAIT_TIMEOUT) return false; - DWORD s; + DWORD es; DWORD e (NO_ERROR); - if (r != WAIT_OBJECT_0 || !GetExitCodeProcess (handle, &s)) + if (r != WAIT_OBJECT_0 || !GetExitCodeProcess (handle, &es)) e = GetLastError (); auto_handle h (handle); @@ -908,10 +1040,11 @@ namespace butl if (e != NO_ERROR) throw process_error (error_msg (e)); - status = s; + exit = process_exit (); + exit->status = es; } - s = status && *status == 0; + s = exit && exit->normal () && exit->code () == 0; return true; } @@ -921,5 +1054,112 @@ namespace butl 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_STACK_OVERFLOW: return "stack overflow"; + 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"; + + 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 } -- cgit v1.1