From e47f972d6851b3196b3b2ba611929f25a5fcadb6 Mon Sep 17 00:00:00 2001 From: Jeremiah Senkpiel Date: Mon, 10 Dec 2018 16:34:32 -0800 Subject: [PATCH] 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 Reviewed-By: Anna Henningsen --- doc/api/child_process.md | 20 +++++++++------ lib/child_process.js | 16 ++++++++++-- .../test-child-process-exec-maxBuffer.js | 25 ++++++++++++++----- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/doc/api/child_process.md b/doc/api/child_process.md index e32568cd78e677..cdfdab1a98d635 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -147,8 +147,9 @@ changes: `'/bin/sh'` on UNIX, `process.env.ComSpec` on Windows. * `timeout` {number} **Default:** `0` * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or - stderr. If exceeded, the child process is terminated. See caveat at - [`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. + stderr. If exceeded, the child process is terminated and any output is + truncated. See caveat at [`maxBuffer` and Unicode][]. + **Default:** `200 * 1024`. * `killSignal` {string|integer} **Default:** `'SIGTERM'` * `uid` {number} Sets the user identity of the process (see setuid(2)). * `gid` {number} Sets the group identity of the process (see setgid(2)). @@ -245,8 +246,9 @@ changes: * `encoding` {string} **Default:** `'utf8'` * `timeout` {number} **Default:** `0` * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or - stderr. If exceeded, the child process is terminated. See caveat at - [`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. + stderr. If exceeded, the child process is terminated and any output is + truncated. See caveat at [`maxBuffer` and Unicode][]. + **Default:** `200 * 1024`. * `killSignal` {string|integer} **Default:** `'SIGTERM'` * `uid` {number} Sets the user identity of the process (see setuid(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 process will be killed. **Default:** `'SIGTERM'`. * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or - stderr. If exceeded, the child process is terminated. See caveat at - [`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. + stderr. If exceeded, the child process is terminated and any output is + truncated. See caveat at [`maxBuffer` and Unicode][]. + **Default:** `200 * 1024`. * `encoding` {string} The encoding used for all stdio inputs and outputs. **Default:** `'buffer'`. * `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 process will be killed. **Default:** `'SIGTERM'`. * `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or - stderr. If exceeded, the child process is terminated. See caveat at - [`maxBuffer` and Unicode][]. **Default:** `200 * 1024`. + stderr. If exceeded, the child process is terminated and any output is + truncated. See caveat at [`maxBuffer` and Unicode][]. + **Default:** `200 * 1024`. * `encoding` {string} The encoding used for all stdio inputs and outputs. **Default:** `'buffer'`. * `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses diff --git a/lib/child_process.js b/lib/child_process.js index d70803866c1f58..8060412b3f076f 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -343,9 +343,15 @@ exports.execFile = function execFile(file /* , args, options, callback */) { child.stdout.on('data', function onChildStdout(chunk) { 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) { + const truncatedLen = options.maxBuffer - (stdoutLen - length); + _stdout.push(chunk.slice(0, truncatedLen)); + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout'); kill(); } else { @@ -360,9 +366,15 @@ exports.execFile = function execFile(file /* , args, options, callback */) { child.stderr.on('data', function onChildStderr(chunk) { 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) { + const truncatedLen = options.maxBuffer - (stderrLen - length); + _stderr.push(chunk.slice(0, truncatedLen)); + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr'); kill(); } else { diff --git a/test/parallel/test-child-process-exec-maxBuffer.js b/test/parallel/test-child-process-exec-maxBuffer.js index 569efb5deba699..dfa46b0b000aab 100644 --- a/test/parallel/test-child-process-exec-maxBuffer.js +++ b/test/parallel/test-child-process-exec-maxBuffer.js @@ -22,13 +22,13 @@ function runChecks(err, stdio, streamName, expected) { } { - const cmd = 'echo "hello world"'; + const cmd = 'echo hello world'; cp.exec( cmd, { maxBuffer: 5 }, 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, { maxBuffer: 10 }, 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, { maxBuffer: 3 }, 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, { encoding: null, maxBuffer: 10 }, 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, { encoding: null, maxBuffer: 3 }, common.mustCall((err, stdout, stderr) => { - runChecks(err, { stdout, stderr }, 'stderr', ''); + runChecks(err, { stdout, stderr }, 'stderr', '中文测'); }) ); 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); + }) + ); +}