diff --git a/src/shell.js b/src/shell.js index e27f72ccb..d705e35ee 100644 --- a/src/shell.js +++ b/src/shell.js @@ -22,6 +22,19 @@ function shell(tag) { const SUDO = '/usr/bin/sudo'; +function lineCount(buffer) { + assert(Buffer.isBuffer(buffer)); + + const NEW_LINE = Buffer.from('\n'); + let index = buffer.indexOf(NEW_LINE); + let count = 0; + while (index >= 0) { + index = buffer.indexOf(NEW_LINE, index+1); + ++count; + } + return count; +} + function spawn(tag, file, args, options) { assert.strictEqual(typeof tag, 'string'); assert.strictEqual(typeof file, 'string'); @@ -30,12 +43,23 @@ function spawn(tag, file, args, options) { debug(`${tag}: ${file} ${args.join(' ').replace(/\n/g, '\\n')}`); + const maxLines = options.maxLines || Number.MAX_SAFE_INTEGER; + return new Promise((resolve, reject) => { const cp = child_process.spawn(file, args, options); const stdoutBuffers = [], stderrBuffers = []; + let stdoutLineCount = 0, stderrLineCount = 0; - cp.stdout.on('data', (data) => stdoutBuffers.push(data)); - cp.stderr.on('data', (data) => stderrBuffers.push(data)); + cp.stdout.on('data', (data) => { + stdoutBuffers.push(data); + stdoutLineCount += lineCount(data); + if (stdoutLineCount >= maxLines) return cp.kill('SIGKILL'); + }); + cp.stderr.on('data', (data) => { + stderrBuffers.push(data); + stderrLineCount += lineCount(data); + if (stderrLineCount >= maxLines) return cp.kill('SIGKILL'); + }); cp.on('close', function (code, signal) { // always called. after 'exit' or 'error' const stdoutBuffer = Buffer.concat(stdoutBuffers); @@ -47,7 +71,9 @@ function spawn(tag, file, args, options) { const e = new BoxError(BoxError.SHELL_ERROR, `${file} exited with code ${code} signal ${signal}`); e.stdout = stdout; // when promisified, this is the way to get stdout + e.stdoutLineCount = stdoutLineCount; e.stderr = stderr; // when promisified, this is the way to get stderr + e.stderrLineCount = stderrLineCount; e.code = code; e.signal = signal; diff --git a/src/test/shell-test.js b/src/test/shell-test.js index d93fe923e..708659536 100644 --- a/src/test/shell-test.js +++ b/src/test/shell-test.js @@ -27,6 +27,24 @@ describe('shell', function () { }); }); + describe('maxLines', function () { + it('maxLines=0 means unlimited', async function () { + await shell.bash('for i in {1..10}; do echo $i; sleep 1; done', { encoding: 'utf8', maxLines: 0}); + }); + + it('maxLines=2 kills the process (stdout)', async function () { + const [error] = await safe(shell.bash('for i in {1..10}; do echo $i; sleep 1; done', { encoding: 'utf8', maxLines: 2})); + expect(error).to.be.ok(); + expect(error.stdoutLineCount).to.be(2); + }); + + it('maxLines=2 kills the process (stderr)', async function () { + const [error] = await safe(shell.bash('for i in {1..10}; do echo $i >&2; sleep 1; done', { encoding: 'utf8', maxLines: 2})); + expect(error).to.be.ok(); + expect(error.stderrLineCount).to.be(2); + }); + }); + describe('sudo', function () { it('cannot sudo invalid program', function (done) { shell.sudo([ 'randomprogram' ], {}, function (error) {