From 68e2449ffa9c18a8f4c61dff875c8f9405bca518 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Wed, 25 Feb 2026 10:20:12 +0100 Subject: [PATCH 1/4] Fix proc_close() to return 128+signal when child is killed by signal proc_close() previously returned the raw waitpid status when a child process was killed by a signal, making it impossible to distinguish a signal death from a normal exit with the same code. Now follows the bash convention: return 128 + signal_number for signal-terminated processes (e.g. 139 for SIGSEGV, 143 for SIGTERM). Closes GH-21292 --- ext/standard/proc_open.c | 10 ++-- .../proc_close_signal_exit_code.phpt | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 4fd24058f7cb..133201a9754b 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -317,11 +317,13 @@ static void proc_open_rsrc_dtor(zend_resource *rsrc) if (wait_pid <= 0) { FG(pclose_ret) = -1; + } else if (WIFEXITED(wstatus)) { + FG(pclose_ret) = WEXITSTATUS(wstatus); + } else if (WIFSIGNALED(wstatus)) { + /* Follow the bash/shell convention: 128 + signal number */ + FG(pclose_ret) = 128 + WTERMSIG(wstatus); } else { - if (WIFEXITED(wstatus)) { - wstatus = WEXITSTATUS(wstatus); - } - FG(pclose_ret) = wstatus; + FG(pclose_ret) = -1; } #else diff --git a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt new file mode 100644 index 000000000000..12a68a98a760 --- /dev/null +++ b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt @@ -0,0 +1,48 @@ +--TEST-- +proc_close() returns 128+signal when child is killed by a signal +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(143) +int(42) \ No newline at end of file From e04da3228f5148ecbeeb9738741ecbf68e7800d5 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Wed, 25 Feb 2026 11:09:10 +0100 Subject: [PATCH 2/4] Fix test: use SIGKILL via /bin/sh instead of posix_kill PHP catches SIGTERM internally and does exit(255), so the child process never actually dies from the signal. Use /bin/sh with kill -9 (SIGKILL) which cannot be caught. --- .../proc_close_signal_exit_code.phpt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt index 12a68a98a760..7f5a6e100c2b 100644 --- a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt +++ b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt @@ -3,16 +3,14 @@ proc_close() returns 128+signal when child is killed by a signal --SKIPIF-- --FILE-- --EXPECT-- -int(143) +int(137) int(42) \ No newline at end of file From 460d9a83b10b3c16fdfc281e16c0431721a4e394 Mon Sep 17 00:00:00 2001 From: Alies Lapatsin Date: Thu, 26 Feb 2026 21:18:08 +0100 Subject: [PATCH 3/4] Add missing trailing newline in test file --- .../tests/general_functions/proc_close_signal_exit_code.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt index 7f5a6e100c2b..ad1765150c15 100644 --- a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt +++ b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt @@ -41,4 +41,4 @@ var_dump($exitCode2); ?> --EXPECT-- int(137) -int(42) \ No newline at end of file +int(42) From 0f49bb0b2c8b8efb5d7e1d5b3af0ad786fab06dd Mon Sep 17 00:00:00 2001 From: Alies Lapatsin <5278175+alies-dev@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:40:32 +0200 Subject: [PATCH 4/4] Cache signaled exit status in waitpid_cached(), document GH-21292 BC change proc_get_status() reaping a signaled child left proc_close() with ECHILD and a -1 return value. Cache terminal signal statuses the same way as normal exits, rename the test to gh21292.phpt and cover that case. Add UPGRADING and NEWS entries. --- NEWS | 2 + UPGRADING | 10 ++++ ext/standard/proc_open.c | 4 +- .../tests/general_functions/bug39322.phpt | 2 +- .../tests/general_functions/gh21292.phpt | 56 +++++++++++++++++++ .../proc_close_signal_exit_code.phpt | 44 --------------- .../tests/general_functions/proc_open02.phpt | 2 +- 7 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 ext/standard/tests/general_functions/gh21292.phpt delete mode 100644 ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt diff --git a/NEWS b/NEWS index e027d6c139ee..cf2e1ab2e536 100644 --- a/NEWS +++ b/NEWS @@ -123,6 +123,8 @@ PHP NEWS in a php://filter URL. (Sjoerd Langkemper) . Improved performance of array_chunk() when not preserving keys. (mehmetcansahin) + . Fixed bug GH-21292 (proc_close() returns raw signal number, + indistinguishable from normal exit code). (Alies Lapatsin) - URI: . Fix casing of enum cases in UriHostType and UrlHostType to match the RFC diff --git a/UPGRADING b/UPGRADING index 46cf4cafab12..9f35859138dc 100644 --- a/UPGRADING +++ b/UPGRADING @@ -31,6 +31,16 @@ PHP 8.7 UPGRADE NOTES . The number of filters that can be chained in a php://filter URL is limited to 16 by default. Set the stream context option max_filter_count to change this. + . On Unix systems, proc_close() now reports termination by a signal as + 128 + the signal number, following the shell convention, instead of the + raw wait status (e.g. 137 instead of 9 for SIGKILL). This also applies when + proc_get_status() observed the termination first; previously proc_close() + returned -1 in that case. Normal exit codes are unchanged. + . On Unix systems, proc_get_status() now also caches the status of a child + terminated by a signal, as it does for normal exits since PHP 8.3. + Repeated calls keep reporting "signaled" => true and the "termsig" value, + and "cached" is true. Previously, calls after the first one reported + "signaled" => false and "termsig" => 0. ======================================== 2. New Features diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 133201a9754b..9ecc57bcbac4 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -256,10 +256,10 @@ static pid_t waitpid_cached(php_process_handle *proc, int *wait_status, int opti pid_t wait_pid = waitpid(proc->child, wait_status, options); - /* The "exit" status is the final status of the process. + /* The "exit" and "terminated by signal" statuses are final statuses of the process. * If we were to cache the status unconditionally, * we would return stale statuses in the future after the process continues. */ - if (wait_pid > 0 && WIFEXITED(*wait_status)) { + if (wait_pid > 0 && (WIFEXITED(*wait_status) || WIFSIGNALED(*wait_status))) { proc->has_cached_exit_wait_status = true; proc->cached_exit_wait_status_value = *wait_status; } diff --git a/ext/standard/tests/general_functions/bug39322.phpt b/ext/standard/tests/general_functions/bug39322.phpt index 162f2babdaac..cfa56de1264a 100644 --- a/ext/standard/tests/general_functions/bug39322.phpt +++ b/ext/standard/tests/general_functions/bug39322.phpt @@ -30,7 +30,7 @@ array(9) { ["pid"]=> int(%d) ["cached"]=> - bool(false) + bool(true) ["running"]=> bool(false) ["signaled"]=> diff --git a/ext/standard/tests/general_functions/gh21292.phpt b/ext/standard/tests/general_functions/gh21292.phpt new file mode 100644 index 000000000000..43c3dd00c7aa --- /dev/null +++ b/ext/standard/tests/general_functions/gh21292.phpt @@ -0,0 +1,56 @@ +--TEST-- +GH-21292 (proc_close() returns raw signal number, indistinguishable from normal exit code) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Killed by SIGKILL: +int(137) +Normal exit: +int(42) +int(9) +int(137) +Killed by SIGKILL, status polled before proc_close(): +bool(true) +int(9) +bool(true) +bool(true) +int(9) +int(137) diff --git a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt b/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt deleted file mode 100644 index ad1765150c15..000000000000 --- a/ext/standard/tests/general_functions/proc_close_signal_exit_code.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -proc_close() returns 128+signal when child is killed by a signal ---SKIPIF-- - ---FILE-- - ---EXPECT-- -int(137) -int(42) diff --git a/ext/standard/tests/general_functions/proc_open02.phpt b/ext/standard/tests/general_functions/proc_open02.phpt index dd2e6902a411..864d1001930a 100644 --- a/ext/standard/tests/general_functions/proc_open02.phpt +++ b/ext/standard/tests/general_functions/proc_open02.phpt @@ -59,7 +59,7 @@ array(9) { ["pid"]=> int(%d) ["cached"]=> - bool(false) + bool(true) ["running"]=> bool(false) ["signaled"]=>