diff --git a/lib/unpack.js b/lib/unpack.js index b24ad72a..282e04f9 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -338,6 +338,10 @@ class Unpack extends Parser { stream.on('error', er => { if (stream.fd) fs.close(stream.fd, () => {}) + // flush all the data out so that we aren't left hanging + // if the error wasn't actually fatal. otherwise the parse + // is blocked, and we never proceed. + stream.write = () => true this[ONERROR](er, entry) fullyDone() }) @@ -510,7 +514,6 @@ class Unpack extends Parser { done() } else if (er || this[ISREUSABLE](entry, st)) this[MAKEFS](null, entry, done) - else if (st.isDirectory()) { if (entry.type === 'Directory') { if (!this.noChmod && (!entry.mode || (st.mode & 0o7777) === entry.mode)) diff --git a/test/make-tar.js b/test/make-tar.js index 0817a788..33de124b 100644 --- a/test/make-tar.js +++ b/test/make-tar.js @@ -4,14 +4,22 @@ if (module === require.main) return require('tap').pass('this is fine') const Header = require('../lib/header.js') -module.exports = chunks => - Buffer.concat(chunks.map(chunk => { - if (Buffer.isBuffer(chunk)) +module.exports = chunks => { + let dataLen = 0 + return Buffer.concat(chunks.map(chunk => { + if (Buffer.isBuffer(chunk)) { + dataLen += chunk.length return chunk - const buf = Buffer.alloc(512) + } + const size = Math.max(typeof chunk === 'string' + ? 512 * Math.ceil(chunk.length / 512) + : 512) + dataLen += size + const buf = Buffer.alloc(size) if (typeof chunk === 'string') buf.write(chunk) else new Header(chunk).encode(buf, 0) return buf - }), chunks.length * 512) + }), dataLen) +} diff --git a/test/unpack.js b/test/unpack.js index e89b2132..f901543e 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -2880,3 +2880,42 @@ t.test('close fd when error setting mtime', t => { }) unpack.end(data) }) + +t.test('do not hang on large files that fail to open()', t => { + const data = makeTar([ + { + type: 'Directory', + path: 'x', + }, + { + type: 'File', + size: 31745, + path: 'x/y', + }, + 'x'.repeat(31745), + '', + '', + ]) + t.teardown(mutateFS.fail('open', new Error('nope'))) + const dir = path.resolve(unpackdir, 'no-hang-for-large-file-failures') + mkdirp.sync(dir) + const WARNINGS = [] + const unpack = new Unpack({ + cwd: dir, + onwarn: (code, msg) => WARNINGS.push([code, msg]), + }) + unpack.on('end', () => { + t.strictSame(WARNINGS, [['TAR_ENTRY_ERROR', 'nope']]) + t.end() + }) + unpack.write(data.slice(0, 2048)) + setTimeout(() => { + unpack.write(data.slice(2048, 4096)) + setTimeout(() => { + unpack.write(data.slice(4096)) + setTimeout(() => { + unpack.end() + }) + }) + }) +})