aboutsummaryrefslogtreecommitdiff
path: root/butl/process.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-12-29 03:32:05 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-01-05 15:53:29 +0300
commitc61d6e14c08fec7658dbdc33c16b5feeece08fbf (patch)
treef090dbd27fadabebf0d685ad6bd3f9a899b18fa7 /butl/process.cxx
parent0e8c95a08f87922575c9f400399258dba54df1ca (diff)
Add process_exit
Diffstat (limited to 'butl/process.cxx')
-rw-r--r--butl/process.cxx292
1 files changed, 266 insertions, 26 deletions
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 <memory> // unique_ptr
-# include <cstdlib> // __argv[]
+# include <cstdlib> // __argv[]
# include <butl/win32-utility>
#endif
#include <errno.h>
-#include <ios> // ios_base::failure
+#include <ios> // ios_base::failure
#include <cassert>
-#include <cstddef> // size_t
-#include <cstring> // strlen(), strchr()
-#include <utility> // move()
+#include <cstddef> // size_t
+#include <cstring> // strlen(), strchr()
+#include <utility> // move()
#include <ostream>
#include <butl/utility> // 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
}