Skip to content

Commit e705603

Browse files
authored
util: add convertProcessSignalToExitCode utility
Add convertProcessSignalToExitCode() to convert signal names to POSIX exit codes (128 + signal number). Exposed in public util API. Refs: #60720 PR-URL: #60963 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Moshe Atlow <[email protected]>
1 parent 180c717 commit e705603

File tree

5 files changed

+125
-0
lines changed

5 files changed

+125
-0
lines changed

doc/api/child_process.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,10 @@ re-raise the handled signal.
15571557
15581558
See waitpid(2).
15591559
1560+
When `code` is `null` due to signal termination, you can use
1561+
[`util.convertProcessSignalToExitCode()`][] to convert the signal to a POSIX
1562+
exit code.
1563+
15601564
### Event: `'message'`
15611565
15621566
<!-- YAML
@@ -1671,6 +1675,11 @@ within the child process to close the IPC channel as well.
16711675
The `subprocess.exitCode` property indicates the exit code of the child process.
16721676
If the child process is still running, the field will be `null`.
16731677
1678+
When the child process is terminated by a signal, `subprocess.exitCode` will be
1679+
`null` and [`subprocess.signalCode`][] will be set. To get the corresponding
1680+
POSIX exit code, use
1681+
[`util.convertProcessSignalToExitCode(subprocess.signalCode)`][`util.convertProcessSignalToExitCode()`].
1682+
16741683
### `subprocess.kill([signal])`
16751684
16761685
<!-- YAML
@@ -2107,6 +2116,10 @@ connection to the child.
21072116
The `subprocess.signalCode` property indicates the signal received by
21082117
the child process if any, else `null`.
21092118
2119+
When the child process is terminated by a signal, [`subprocess.exitCode`][] will be `null`.
2120+
To get the corresponding POSIX exit code, use
2121+
[`util.convertProcessSignalToExitCode(subprocess.signalCode)`][`util.convertProcessSignalToExitCode()`].
2122+
21102123
### `subprocess.spawnargs`
21112124
21122125
* Type: {Array}
@@ -2387,12 +2400,15 @@ or [`child_process.fork()`][].
23872400
[`stdio`]: #optionsstdio
23882401
[`subprocess.connected`]: #subprocessconnected
23892402
[`subprocess.disconnect()`]: #subprocessdisconnect
2403+
[`subprocess.exitCode`]: #subprocessexitcode
23902404
[`subprocess.kill()`]: #subprocesskillsignal
23912405
[`subprocess.send()`]: #subprocesssendmessage-sendhandle-options-callback
2406+
[`subprocess.signalCode`]: #subprocesssignalcode
23922407
[`subprocess.stderr`]: #subprocessstderr
23932408
[`subprocess.stdin`]: #subprocessstdin
23942409
[`subprocess.stdio`]: #subprocessstdio
23952410
[`subprocess.stdout`]: #subprocessstdout
2411+
[`util.convertProcessSignalToExitCode()`]: util.md#utilconvertprocesssignaltoexitcodesignalcode
23962412
[`util.promisify()`]: util.md#utilpromisifyoriginal
23972413
[synchronous counterparts]: #synchronous-process-creation
23982414
[v8.serdes]: v8.md#serialization-api

doc/api/util.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,38 @@ callbackFunction((err, ret) => {
8989
});
9090
```
9191

92+
## `util.convertProcessSignalToExitCode(signalCode)`
93+
94+
<!-- YAML
95+
added: REPLACEME
96+
-->
97+
98+
* `signalCode` {string} A signal name (e.g., `'SIGTERM'`, `'SIGKILL'`).
99+
* Returns: {number|null} The exit code, or `null` if the signal is invalid.
100+
101+
The `util.convertProcessSignalToExitCode()` method converts a signal name to its
102+
corresponding POSIX exit code. Following the POSIX standard, the exit code
103+
for a process terminated by a signal is calculated as `128 + signal number`.
104+
105+
```mjs
106+
import { convertProcessSignalToExitCode } from 'node:util';
107+
108+
console.log(convertProcessSignalToExitCode('SIGTERM')); // 143 (128 + 15)
109+
console.log(convertProcessSignalToExitCode('SIGKILL')); // 137 (128 + 9)
110+
console.log(convertProcessSignalToExitCode('INVALID')); // null
111+
```
112+
113+
```cjs
114+
const { convertProcessSignalToExitCode } = require('node:util');
115+
116+
console.log(convertProcessSignalToExitCode('SIGTERM')); // 143 (128 + 15)
117+
console.log(convertProcessSignalToExitCode('SIGKILL')); // 137 (128 + 9)
118+
console.log(convertProcessSignalToExitCode('INVALID')); // null
119+
```
120+
121+
This is particularly useful when working with processes to determine
122+
the exit code based on the signal that terminated the process.
123+
92124
## `util.debuglog(section[, callback])`
93125

94126
<!-- YAML

lib/internal/util.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
ObjectGetOwnPropertyDescriptor,
1717
ObjectGetOwnPropertyDescriptors,
1818
ObjectGetPrototypeOf,
19+
ObjectKeys,
1920
ObjectPrototypeHasOwnProperty,
2021
ObjectSetPrototypeOf,
2122
ObjectValues,
@@ -106,6 +107,7 @@ function isError(e) {
106107
const codesWarned = new SafeSet();
107108

108109
let validateString;
110+
let validateOneOf;
109111

110112
function getDeprecationWarningEmitter(
111113
code, msg, deprecated, useEmitSync,
@@ -393,6 +395,17 @@ function convertToValidSignal(signal) {
393395
throw new ERR_UNKNOWN_SIGNAL(signal);
394396
}
395397

398+
function convertProcessSignalToExitCode(signalCode) {
399+
// Lazy-load to avoid a circular dependency.
400+
if (validateOneOf === undefined)
401+
({ validateOneOf } = require('internal/validators'));
402+
403+
validateOneOf(signalCode, 'signalCode', ObjectKeys(signals));
404+
405+
// POSIX standard: exit code for signal termination is 128 + signal number.
406+
return 128 + signals[signalCode];
407+
}
408+
396409
function getConstructorOf(obj) {
397410
while (obj) {
398411
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
@@ -956,6 +969,7 @@ module.exports = {
956969
assignFunctionName,
957970
cachedResult,
958971
constructSharedArrayBuffer,
972+
convertProcessSignalToExitCode,
959973
convertToValidSignal,
960974
createClassWrapper,
961975
decorateErrorStack,

lib/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const { getOptionValue } = require('internal/options');
8484
const binding = internalBinding('util');
8585

8686
const {
87+
convertProcessSignalToExitCode,
8788
deprecate: internalDeprecate,
8889
getLazy,
8990
getSystemErrorMap,
@@ -472,6 +473,7 @@ module.exports = {
472473
'The `util._extend` API is deprecated. Please use Object.assign() instead.',
473474
'DEP0060'),
474475
callbackify,
476+
convertProcessSignalToExitCode,
475477
debug: debuglog,
476478
debuglog,
477479
deprecate,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { mustCall, mustNotCall, isWindows } from '../common/index.mjs';
2+
import assert from 'assert';
3+
import { convertProcessSignalToExitCode } from 'util';
4+
import { spawn } from 'child_process';
5+
import { constants } from 'os';
6+
const { signals } = constants;
7+
8+
{
9+
10+
assert.strictEqual(convertProcessSignalToExitCode('SIGTERM'), 128 + signals.SIGTERM);
11+
assert.strictEqual(convertProcessSignalToExitCode('SIGKILL'), 128 + signals.SIGKILL);
12+
assert.strictEqual(convertProcessSignalToExitCode('SIGINT'), 128 + signals.SIGINT);
13+
assert.strictEqual(convertProcessSignalToExitCode('SIGHUP'), 128 + signals.SIGHUP);
14+
assert.strictEqual(convertProcessSignalToExitCode('SIGABRT'), 128 + signals.SIGABRT);
15+
}
16+
17+
{
18+
[
19+
'INVALID',
20+
'',
21+
'SIG',
22+
undefined,
23+
null,
24+
123,
25+
true,
26+
false,
27+
{},
28+
[],
29+
Symbol('test'),
30+
() => {},
31+
].forEach((value) => {
32+
assert.throws(
33+
() => convertProcessSignalToExitCode(value),
34+
{
35+
code: 'ERR_INVALID_ARG_VALUE',
36+
name: 'TypeError',
37+
}
38+
);
39+
});
40+
}
41+
42+
{
43+
const cat = spawn(isWindows ? 'cmd' : 'cat');
44+
cat.stdout.on('end', mustCall());
45+
cat.stderr.on('data', mustNotCall());
46+
cat.stderr.on('end', mustCall());
47+
48+
cat.on('exit', mustCall((code, signal) => {
49+
assert.strictEqual(code, null);
50+
assert.strictEqual(signal, 'SIGTERM');
51+
assert.strictEqual(cat.signalCode, 'SIGTERM');
52+
53+
const exitCode = convertProcessSignalToExitCode(signal);
54+
assert.strictEqual(exitCode, 143);
55+
}));
56+
57+
assert.strictEqual(cat.signalCode, null);
58+
assert.strictEqual(cat.killed, false);
59+
cat[Symbol.dispose]();
60+
assert.strictEqual(cat.killed, true);
61+
}

0 commit comments

Comments
 (0)