diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.WaitPid.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.WaitPid.cs index 2f4eae4584c0a9..0474a11b0bde9e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.WaitPid.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.WaitPid.cs @@ -18,6 +18,6 @@ internal static partial class Sys /// 4) on error, -1 is returned. /// [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitPidExitedNoHang", SetLastError = true)] - internal static partial int WaitPidExitedNoHang(int pid, out int exitCode); + internal static partial int WaitPidExitedNoHang(int pid, out int exitCode, out int terminatingSignal); } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs index 6abe345f996f0c..b385cd6df6f7d1 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs @@ -223,11 +223,11 @@ public ProcessModule? MainModule /// Checks whether the process has exited and updates state accordingly. private void UpdateHasExited() { - int? exitCode; - _exited = GetWaitState().GetExited(out exitCode, refresh: true); - if (_exited && exitCode != null) + ProcessExitStatus? exitStatus; + _exited = GetWaitState().GetExited(out exitStatus, refresh: true); + if (_exited && exitStatus is not null) { - _exitCode = exitCode.Value; + _exitCode = exitStatus.ExitCode; } } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs index ab1dd7e46bf2f6..2890f0b421d97d 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs @@ -208,8 +208,8 @@ internal void ReleaseRef() /// Whether the associated process exited. private bool _exited; - /// If the process exited, it's exit code, or null if we were unable to determine one. - private int? _exitCode; + /// If the process exited, its exit status, or null if we were unable to determine one. + private ProcessExitStatus? _exitStatus; /// /// The approximate time the process exited. We do not have the ability to know exact time a process /// exited, so we approximate it by storing the time that we discovered it exited. @@ -310,14 +310,14 @@ internal bool HasExited } } - internal bool GetExited(out int? exitCode, bool refresh) + internal bool GetExited(out ProcessExitStatus? exitStatus, bool refresh) { lock (_gate) { // Have we already exited? If so, return the cached results. if (_exited) { - exitCode = _exitCode; + exitStatus = _exitStatus; return true; } @@ -325,7 +325,7 @@ internal bool GetExited(out int? exitCode, bool refresh) // and that task owns the right to call CheckForNonChildExit. if (!_waitInProgress.IsCompleted) { - exitCode = null; + exitStatus = null; return false; } @@ -338,7 +338,7 @@ internal bool GetExited(out int? exitCode, bool refresh) // We now have an up-to-date snapshot for whether we've exited, // and if we have, what the exit code is (if we were able to find out). - exitCode = _exitCode; + exitStatus = _exitStatus; return _exited; } } @@ -539,13 +539,14 @@ private async Task WaitForExitAsync(CancellationToken cancellationToken) } } - private void ChildReaped(int exitCode, bool configureConsole) + private void ChildReaped(int exitCode, int terminatingSignal, bool configureConsole) { lock (_gate) { Debug.Assert(!_exited); - _exitCode = exitCode; + PosixSignal? signal = terminatingSignal != 0 ? (PosixSignal)terminatingSignal : null; + _exitStatus = new ProcessExitStatus(exitCode, canceled: false, signal); if (_usesTerminal) { @@ -568,11 +569,12 @@ private bool TryReapChild(bool configureConsole) // Try to get the state of the child process int exitCode; - int waitResult = Interop.Sys.WaitPidExitedNoHang(_processId, out exitCode); + int terminatingSignal; + int waitResult = Interop.Sys.WaitPidExitedNoHang(_processId, out exitCode, out terminatingSignal); if (waitResult == _processId) { - ChildReaped(exitCode, configureConsole); + ChildReaped(exitCode, terminatingSignal, configureConsole); return true; } else if (waitResult == 0) @@ -673,7 +675,8 @@ internal static void CheckChildren(bool reapAll, bool configureConsole) do { int exitCode; - pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode); + int terminatingSignal; + pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode, out terminatingSignal); if (pid <= 0) { break; @@ -682,7 +685,7 @@ internal static void CheckChildren(bool reapAll, bool configureConsole) // Check if the process is a child that has just terminated. if (s_childProcessWaitStates.TryGetValue(pid, out ProcessWaitState? pws)) { - pws.ChildReaped(exitCode, configureConsole); + pws.ChildReaped(exitCode, terminatingSignal, configureConsole); pws.ReleaseRef(); } } while (true); diff --git a/src/native/libs/System.Native/pal_process.c b/src/native/libs/System.Native/pal_process.c index 82c00925143113..349b02ec7ae665 100644 --- a/src/native/libs/System.Native/pal_process.c +++ b/src/native/libs/System.Native/pal_process.c @@ -3,6 +3,7 @@ #include "pal_config.h" #include "pal_process.h" +#include "pal_signal.h" #include "pal_io.h" #include "pal_utilities.h" @@ -929,9 +930,10 @@ int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void) return result; } -int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode) +int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal) { assert(exitCode != NULL); + assert(terminatingSignal != NULL); int32_t result; int status; @@ -942,11 +944,16 @@ int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode) { // the child terminated normally. *exitCode = WEXITSTATUS(status); + *terminatingSignal = 0; } else if (WIFSIGNALED(status)) { // child process was terminated by a signal. - *exitCode = 128 + WTERMSIG(status); + int sig = WTERMSIG(status); + *exitCode = 128 + sig; + PosixSignal posixSignal = PosixSignalInvalid; + TryConvertSignalCodeToPosixSignal(sig, &posixSignal); + *terminatingSignal = (int32_t)posixSignal; } else { diff --git a/src/native/libs/System.Native/pal_process.h b/src/native/libs/System.Native/pal_process.h index 00bddcd17d78f4..abcc7d87439cd7 100644 --- a/src/native/libs/System.Native/pal_process.h +++ b/src/native/libs/System.Native/pal_process.h @@ -186,8 +186,12 @@ PALEXPORT int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void); * 2) if pid is not a child or there are no unwaited-for children, -1 is returned (errno=ECHILD) * 3) if the child has not yet terminated, 0 is returned * 4) on error, -1 is returned. + * + * exitCode: set to WEXITSTATUS on normal exit, or 128 + signal number on signal termination. + * terminatingSignal: set to 0 on normal exit, or the PosixSignal value on signal termination. + * For signals not in the known PosixSignal set, this is the raw signal number. */ -PALEXPORT int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode); +PALEXPORT int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal); /** * Gets the configurable limit or variable for system path or file descriptor options. diff --git a/src/native/libs/System.Native/pal_process_wasi.c b/src/native/libs/System.Native/pal_process_wasi.c index ff32ba0a39bba5..1ad9f888bbc04c 100644 --- a/src/native/libs/System.Native/pal_process_wasi.c +++ b/src/native/libs/System.Native/pal_process_wasi.c @@ -76,7 +76,7 @@ int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void) return -1; } -int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode) +int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal) { return -1; } diff --git a/src/native/libs/System.Native/pal_signal.c b/src/native/libs/System.Native/pal_signal.c index 18254d590d9fe9..3e02b2466fc65d 100644 --- a/src/native/libs/System.Native/pal_signal.c +++ b/src/native/libs/System.Native/pal_signal.c @@ -83,7 +83,7 @@ static bool IsSigIgn(struct sigaction* action) action->sa_handler == SIG_IGN; } -static bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal) +bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal) { assert(posixSignal != NULL); diff --git a/src/native/libs/System.Native/pal_signal.h b/src/native/libs/System.Native/pal_signal.h index 074c77315f5edc..801251746031c9 100644 --- a/src/native/libs/System.Native/pal_signal.h +++ b/src/native/libs/System.Native/pal_signal.h @@ -83,6 +83,14 @@ PALEXPORT void SystemNative_DisablePosixSignalHandling(int signalCode); */ PALEXPORT void SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode); +/** + * Converts a native signal code to PosixSignal. + * + * Returns true if the signal code was mapped to a known PosixSignal. + * Returns false if the signal is not in the known set (posixSignal is set to the raw signal code). + */ +bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal); + typedef void (*ConsoleSigTtouHandler)(void); /**