From 2d83d5b8d19f51f2b2b3ba0d0df5cddde7c595ee Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 5 Jan 2021 14:38:51 -0800 Subject: [PATCH] Add the noChmod option This allows a way to suppress the call to process.umask() while still being as compliant as possible with the modes as defined in the tarball entries. Re: https://github.com/npm/cli/issues/1103 PR-URL: https://github.com/npm/node-tar/pull/270 Credit: @isaacs Close: #270 Reviewed-by: @ruyadorno --- README.md | 15 ++++++++++++++- lib/unpack.js | 10 +++++++--- test/unpack.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e0e85363..42afb1aa 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,6 @@ The following options are supported: - `mtime` Set to a `Date` object to force a specific `mtime` for everything added to the archive. Overridden by `noMtime`. - The following options are mostly internal, but can be modified in some advanced use cases, such as re-using caches between runs. @@ -396,6 +395,13 @@ The following options are supported: the `filter` option described above.) - `onentry` A function that gets called with `(entry)` for each entry that passes the filter. +- `onwarn` A function that will get called with `(code, message, data)` for + any warnings encountered. (See "Warnings and Errors") +- `noChmod` Set to true to omit calling `fs.chmod()` to ensure that the + extracted file matches the entry mode. This also suppresses the call to + `process.umask()` to determine the default umask value, since tar will + extract with whatever mode is provided, and let the process `umask` apply + normally. The following options are mostly internal, but can be modified in some advanced use cases, such as re-using caches between runs. @@ -451,6 +457,8 @@ The following options are supported: the call to `onentry`. Set `noResume: true` to suppress this behavior. Note that by opting into this, the stream will never complete until the entry data is consumed. +- `onwarn` A function that will get called with `(code, message, data)` for + any warnings encountered. (See "Warnings and Errors") ### tar.u(options, fileList, callback) [alias: tar.update] @@ -708,6 +716,11 @@ Most unpack errors will cause a `warn` event to be emitted. If the that passes the filter. - `onwarn` A function that will get called with `(code, message, data)` for any warnings encountered. (See "Warnings and Errors") +- `noChmod` Set to true to omit calling `fs.chmod()` to ensure that the + extracted file matches the entry mode. This also suppresses the call to + `process.umask()` to determine the default umask value, since tar will + extract with whatever mode is provided, and let the process `umask` apply + normally. ### class tar.Unpack.Sync diff --git a/lib/unpack.js b/lib/unpack.js index e3b8116c..7d4b79d9 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -169,11 +169,14 @@ class Unpack extends Parser { this.cwd = path.resolve(opt.cwd || process.cwd()) this.strip = +opt.strip || 0 - this.processUmask = process.umask() + // if we're not chmodding, then we don't need the process umask + this.processUmask = opt.noChmod ? 0 : process.umask() this.umask = typeof opt.umask === 'number' ? opt.umask : this.processUmask + // default mode for dirs created as parents this.dmode = opt.dmode || (0o0777 & (~this.umask)) this.fmode = opt.fmode || (0o0666 & (~this.umask)) + this.on('entry', entry => this[ONENTRY](entry)) } @@ -299,6 +302,7 @@ class Unpack extends Parser { cache: this.dirCache, cwd: this.cwd, mode: mode, + noChmod: this.noChmod, }, cb) } @@ -475,7 +479,7 @@ class Unpack extends Parser { else if (st.isDirectory()) { if (entry.type === 'Directory') { - if (!entry.mode || (st.mode & 0o7777) === entry.mode) + if (!this.noChmod && (!entry.mode || (st.mode & 0o7777) === entry.mode)) this[MAKEFS](null, entry, done) else { fs.chmod(entry.absolute, entry.mode, @@ -538,7 +542,7 @@ class UnpackSync extends Unpack { try { if (st.isDirectory()) { if (entry.type === 'Directory') { - if (entry.mode && (st.mode & 0o7777) !== entry.mode) + if (!this.noChmod && entry.mode && (st.mode & 0o7777) !== entry.mode) fs.chmodSync(entry.absolute, entry.mode) } else fs.rmdirSync(entry.absolute) diff --git a/test/unpack.js b/test/unpack.js index a690b61c..9a8c7865 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -1916,6 +1916,48 @@ t.test('use explicit chmod when required by umask', t => { }) }) +t.test('dont use explicit chmod if noChmod flag set', t => { + process.umask(0o022) + const { umask } = process + t.teardown(() => process.umask = umask) + process.umask = () => { + throw new Error('should not call process.umask()') + } + + const basedir = path.resolve(unpackdir, 'umask-no-chmod') + + const data = makeTar([ + { + path: 'x/y/z', + mode: 0o775, + type: 'Directory', + }, + '', + '', + ]) + + const check = t => { + const st = fs.statSync(basedir + '/x/y/z') + t.equal(st.mode & 0o777, 0o755) + rimraf.sync(basedir) + t.end() + } + + t.test('async', t => { + mkdirp.sync(basedir) + const unpack = new Unpack({ cwd: basedir, noChmod: true }) + unpack.on('close', _ => check(t)) + unpack.end(data) + }) + + return t.test('sync', t => { + mkdirp.sync(basedir) + const unpack = new Unpack.Sync({ cwd: basedir, noChmod: true}) + unpack.end(data) + check(t) + }) +}) + t.test('chown implicit dirs and also the entries', t => { const basedir = path.resolve(unpackdir, 'chownr')