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 94545e719ba2d7..dfa46b0b000aab 100644 --- a/test/parallel/test-child-process-exec-maxBuffer.js +++ b/test/parallel/test-child-process-exec-maxBuffer.js @@ -3,12 +3,11 @@ const common = require('../common'); const assert = require('assert'); const cp = require('child_process'); -function checkFactory(streamName) { - return common.mustCall((err) => { - assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`); - assert(err instanceof RangeError); - assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'); - }); +function runChecks(err, stdio, streamName, expected) { + assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`); + assert(err instanceof RangeError); + assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'); + assert.deepStrictEqual(stdio[streamName], expected); } { @@ -23,9 +22,15 @@ function checkFactory(streamName) { } { - const cmd = 'echo "hello world"'; + const cmd = 'echo hello world'; - cp.exec(cmd, { maxBuffer: 5 }, checkFactory('stdout')); + cp.exec( + cmd, + { maxBuffer: 5 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', 'hello'); + }) + ); } const unicode = '中文测试'; // length = 4, byte length = 12 @@ -33,13 +38,25 @@ const unicode = '中文测试'; // length = 4, byte length = 12 { const cmd = `"${process.execPath}" -e "console.log('${unicode}');"`; - cp.exec(cmd, { maxBuffer: 10 }, checkFactory('stdout')); + cp.exec( + cmd, + { maxBuffer: 10 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n'); + }) + ); } { const cmd = `"${process.execPath}" -e "console.error('${unicode}');"`; - cp.exec(cmd, { maxBuffer: 10 }, checkFactory('stderr')); + cp.exec( + cmd, + { maxBuffer: 3 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stderr', '中文测'); + }) + ); } { @@ -48,7 +65,10 @@ const unicode = '中文测试'; // length = 4, byte length = 12 const child = cp.exec( cmd, { encoding: null, maxBuffer: 10 }, - checkFactory('stdout')); + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n'); + }) + ); child.stdout.setEncoding('utf-8'); } @@ -58,8 +78,24 @@ const unicode = '中文测试'; // length = 4, byte length = 12 const child = cp.exec( cmd, - { encoding: null, maxBuffer: 10 }, - checkFactory('stderr')); + { encoding: null, maxBuffer: 3 }, + common.mustCall((err, stdout, 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); + }) + ); +}