child_process: truncate output when maxBuffer is exceeded

Preserves truncated output for `child_process.exec()` when `maxBuffer`
is exceeded.

This is particularly useful for commands which have indistinguishable
error codes for what output they produce.

PR-URL: https://github.com/nodejs/node/pull/24951
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Jeremiah Senkpiel 2018-12-10 16:34:32 -08:00
parent 5f866821ad
commit e47f972d68
3 changed files with 45 additions and 16 deletions

View File

@ -147,8 +147,9 @@ changes:
`'/bin/sh'` on UNIX, `process.env.ComSpec` on Windows. `'/bin/sh'` on UNIX, `process.env.ComSpec` on Windows.
* `timeout` {number} **Default:** `0` * `timeout` {number} **Default:** `0`
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
stderr. If exceeded, the child process is terminated. See caveat at stderr. If exceeded, the child process is terminated and any output is
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. truncated. See caveat at [`maxBuffer` and Unicode][].
**Default:** `200 * 1024`.
* `killSignal` {string|integer} **Default:** `'SIGTERM'` * `killSignal` {string|integer} **Default:** `'SIGTERM'`
* `uid` {number} Sets the user identity of the process (see setuid(2)). * `uid` {number} Sets the user identity of the process (see setuid(2)).
* `gid` {number} Sets the group identity of the process (see setgid(2)). * `gid` {number} Sets the group identity of the process (see setgid(2)).
@ -245,8 +246,9 @@ changes:
* `encoding` {string} **Default:** `'utf8'` * `encoding` {string} **Default:** `'utf8'`
* `timeout` {number} **Default:** `0` * `timeout` {number} **Default:** `0`
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
stderr. If exceeded, the child process is terminated. See caveat at stderr. If exceeded, the child process is terminated and any output is
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. truncated. See caveat at [`maxBuffer` and Unicode][].
**Default:** `200 * 1024`.
* `killSignal` {string|integer} **Default:** `'SIGTERM'` * `killSignal` {string|integer} **Default:** `'SIGTERM'`
* `uid` {number} Sets the user identity of the process (see setuid(2)). * `uid` {number} Sets the user identity of the process (see setuid(2)).
* `gid` {number} Sets the group identity of the process (see setgid(2)). * `gid` {number} Sets the group identity of the process (see setgid(2)).
@ -779,8 +781,9 @@ changes:
* `killSignal` {string|integer} The signal value to be used when the spawned * `killSignal` {string|integer} The signal value to be used when the spawned
process will be killed. **Default:** `'SIGTERM'`. process will be killed. **Default:** `'SIGTERM'`.
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
stderr. If exceeded, the child process is terminated. See caveat at stderr. If exceeded, the child process is terminated and any output is
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. truncated. See caveat at [`maxBuffer` and Unicode][].
**Default:** `200 * 1024`.
* `encoding` {string} The encoding used for all stdio inputs and outputs. * `encoding` {string} The encoding used for all stdio inputs and outputs.
**Default:** `'buffer'`. **Default:** `'buffer'`.
* `windowsHide` {boolean} Hide the subprocess console window that would * `windowsHide` {boolean} Hide the subprocess console window that would
@ -842,8 +845,9 @@ changes:
* `killSignal` {string|integer} The signal value to be used when the spawned * `killSignal` {string|integer} The signal value to be used when the spawned
process will be killed. **Default:** `'SIGTERM'`. process will be killed. **Default:** `'SIGTERM'`.
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
stderr. If exceeded, the child process is terminated. See caveat at stderr. If exceeded, the child process is terminated and any output is
[`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. truncated. See caveat at [`maxBuffer` and Unicode][].
**Default:** `200 * 1024`.
* `encoding` {string} The encoding used for all stdio inputs and outputs. * `encoding` {string} The encoding used for all stdio inputs and outputs.
**Default:** `'buffer'`. **Default:** `'buffer'`.
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses * `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses

View File

@ -343,9 +343,15 @@ exports.execFile = function execFile(file /* , args, options, callback */) {
child.stdout.on('data', function onChildStdout(chunk) { child.stdout.on('data', function onChildStdout(chunk) {
var encoding = child.stdout._readableState.encoding; var encoding = child.stdout._readableState.encoding;
stdoutLen += encoding ? Buffer.byteLength(chunk, encoding) : chunk.length; const length = encoding ?
Buffer.byteLength(chunk, encoding) :
chunk.length;
stdoutLen += length;
if (stdoutLen > options.maxBuffer) { if (stdoutLen > options.maxBuffer) {
const truncatedLen = options.maxBuffer - (stdoutLen - length);
_stdout.push(chunk.slice(0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout'); ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout');
kill(); kill();
} else { } else {
@ -360,9 +366,15 @@ exports.execFile = function execFile(file /* , args, options, callback */) {
child.stderr.on('data', function onChildStderr(chunk) { child.stderr.on('data', function onChildStderr(chunk) {
var encoding = child.stderr._readableState.encoding; var encoding = child.stderr._readableState.encoding;
stderrLen += encoding ? Buffer.byteLength(chunk, encoding) : chunk.length; const length = encoding ?
Buffer.byteLength(chunk, encoding) :
chunk.length;
stderrLen += length;
if (stderrLen > options.maxBuffer) { if (stderrLen > options.maxBuffer) {
const truncatedLen = options.maxBuffer - (stderrLen - length);
_stderr.push(chunk.slice(0, truncatedLen));
ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr'); ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr');
kill(); kill();
} else { } else {

View File

@ -22,13 +22,13 @@ function runChecks(err, stdio, streamName, expected) {
} }
{ {
const cmd = 'echo "hello world"'; const cmd = 'echo hello world';
cp.exec( cp.exec(
cmd, cmd,
{ maxBuffer: 5 }, { maxBuffer: 5 },
common.mustCall((err, stdout, stderr) => { common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stdout', ''); runChecks(err, { stdout, stderr }, 'stdout', 'hello');
}) })
); );
} }
@ -42,7 +42,7 @@ const unicode = '中文测试'; // length = 4, byte length = 12
cmd, cmd,
{ maxBuffer: 10 }, { maxBuffer: 10 },
common.mustCall((err, stdout, stderr) => { common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stdout', ''); runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
}) })
); );
} }
@ -54,7 +54,7 @@ const unicode = '中文测试'; // length = 4, byte length = 12
cmd, cmd,
{ maxBuffer: 3 }, { maxBuffer: 3 },
common.mustCall((err, stdout, stderr) => { common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stderr', ''); runChecks(err, { stdout, stderr }, 'stderr', '中文测');
}) })
); );
} }
@ -66,7 +66,7 @@ const unicode = '中文测试'; // length = 4, byte length = 12
cmd, cmd,
{ encoding: null, maxBuffer: 10 }, { encoding: null, maxBuffer: 10 },
common.mustCall((err, stdout, stderr) => { common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stdout', ''); runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n');
}) })
); );
@ -80,9 +80,22 @@ const unicode = '中文测试'; // length = 4, byte length = 12
cmd, cmd,
{ encoding: null, maxBuffer: 3 }, { encoding: null, maxBuffer: 3 },
common.mustCall((err, stdout, stderr) => { common.mustCall((err, stdout, stderr) => {
runChecks(err, { stdout, stderr }, 'stderr', ''); runChecks(err, { stdout, stderr }, 'stderr', '中文测');
}) })
); );
child.stderr.setEncoding('utf-8'); child.stderr.setEncoding('utf-8');
} }
{
const cmd = `"${process.execPath}" -e "console.error('${unicode}');"`;
cp.exec(
cmd,
{ encoding: null, maxBuffer: 5 },
common.mustCall((err, stdout, stderr) => {
const buf = Buffer.from(unicode).slice(0, 5);
runChecks(err, { stdout, stderr }, 'stderr', buf);
})
);
}