From 057731285286f6ba6f3db765f3986d93809912ea Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sun, 20 May 2018 11:41:39 -0400 Subject: [PATCH 01/14] fs: don't limit ftruncate() length to 32 bits The length used by ftruncate() is 64 bits in the binding layer. This commit removes the 32 bit restriction in the JS layer. Backport-PR-URL: https://github.com/nodejs/node/pull/21171 PR-URL: https://github.com/nodejs/node/pull/20851 Fixes: https://github.com/nodejs/node/issues/20844 Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum Backport-PR-URL: https://github.com/nodejs/node/pull/21171 Co-authored-by: Shelley Vohr --- lib/fs.js | 9 ++------- lib/internal/fs/promises.js | 4 ++-- test/parallel/test-fs-truncate.js | 5 +---- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index cc559a898dc4ea..7168b7875498c5 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -86,7 +86,6 @@ const { const { isUint32, validateInteger, - validateInt32, validateUint32 } = require('internal/validators'); @@ -788,11 +787,7 @@ fs.ftruncate = function(fd, len = 0, callback) { len = 0; } validateUint32(fd, 'fd'); - // TODO(BridgeAR): This does not seem right. - // There does not seem to be any validation before and if there is any, it - // should work similar to validateUint32 or not have a upper cap at all. - // This applies to all usage of `validateInt32(len, 'len')`. - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); @@ -801,7 +796,7 @@ fs.ftruncate = function(fd, len = 0, callback) { fs.ftruncateSync = function(fd, len = 0) { validateUint32(fd, 'fd'); - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); const ctx = {}; binding.ftruncate(fd, len, undefined, ctx); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 4c0a256f5ad66c..d4dbffd850694e 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -34,7 +34,7 @@ const { } = require('internal/fs/utils'); const { isUint32, - validateInt32, + validateInteger, validateUint32 } = require('internal/validators'); const pathModule = require('path'); @@ -265,7 +265,7 @@ async function truncate(path, len = 0) { async function ftruncate(handle, len = 0) { validateFileHandle(handle); - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); return binding.ftruncate(handle.fd, len, kUsePromises); } diff --git a/test/parallel/test-fs-truncate.js b/test/parallel/test-fs-truncate.js index 347cc0e10492d6..735385f61c5249 100644 --- a/test/parallel/test-fs-truncate.js +++ b/test/parallel/test-fs-truncate.js @@ -220,17 +220,14 @@ function testFtruncate(cb) { `an integer. Received ${input}` } ); - }); - // 2 ** 31 = 2147483648 - [2147483648, -2147483649].forEach((input) => { assert.throws( () => fs.ftruncate(fd, input), { code: 'ERR_OUT_OF_RANGE', name: 'RangeError [ERR_OUT_OF_RANGE]', message: 'The value of "len" is out of range. It must be ' + - `> -2147483649 && < 2147483648. Received ${input}` + `an integer. Received ${input}` } ); }); From 39df351c44baaaba7e1c8c4b2793e49f4eee1a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 6 Jun 2018 15:05:20 +0200 Subject: [PATCH 02/14] process: backport process/methods file To ease future backports, create the process/methods file introduced in https://github.com/nodejs/node/pull/19973. This commit just adds the JS functions that forward calls to C++ and does not change type checking. --- lib/internal/bootstrap/node.js | 1 + lib/internal/process/methods.js | 65 +++++++++++++++++++ node.gyp | 1 + .../{test-umask.js => test-process-umask.js} | 0 4 files changed, 67 insertions(+) create mode 100644 lib/internal/process/methods.js rename test/parallel/{test-umask.js => test-process-umask.js} (100%) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 4d95529c4b58e6..d6fd67fe5d2c85 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -40,6 +40,7 @@ NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/next_tick').setup(); NativeModule.require('internal/process/stdio').setup(); + NativeModule.require('internal/process/methods').setup(); const perf = process.binding('performance'); const { diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js new file mode 100644 index 00000000000000..1a720c5cb0e5e4 --- /dev/null +++ b/lib/internal/process/methods.js @@ -0,0 +1,65 @@ +'use strict'; + +function setupProcessMethods() { + // Non-POSIX platforms like Windows don't have certain methods. + if (process.setgid !== undefined) { + setupPosixMethods(); + } + + const { chdir: _chdir, umask: _umask } = process; + + process.chdir = chdir; + process.umask = umask; + + function chdir(...args) { + return _chdir(...args); + } + + function umask(...args) { + return _umask(...args); + } +} + +function setupPosixMethods() { + const { + initgroups: _initgroups, + setegid: _setegid, + seteuid: _seteuid, + setgid: _setgid, + setuid: _setuid, + setgroups: _setgroups + } = process; + + process.initgroups = initgroups; + process.setegid = setegid; + process.seteuid = seteuid; + process.setgid = setgid; + process.setuid = setuid; + process.setgroups = setgroups; + + function initgroups(...args) { + return _initgroups(...args); + } + + function setegid(...args) { + return _setegid(...args); + } + + function seteuid(...args) { + return _seteuid(...args); + } + + function setgid(...args) { + return _setgid(...args); + } + + function setuid(...args) { + return _setuid(...args); + } + + function setgroups(...args) { + return _setgroups(...args); + } +} + +exports.setup = setupProcessMethods; diff --git a/node.gyp b/node.gyp index 19367d9bd9517d..7bcc17ce843798 100644 --- a/node.gyp +++ b/node.gyp @@ -120,6 +120,7 @@ 'lib/internal/net.js', 'lib/internal/os.js', 'lib/internal/process/esm_loader.js', + 'lib/internal/process/methods.js', 'lib/internal/process/next_tick.js', 'lib/internal/process/promises.js', 'lib/internal/process/stdio.js', diff --git a/test/parallel/test-umask.js b/test/parallel/test-process-umask.js similarity index 100% rename from test/parallel/test-umask.js rename to test/parallel/test-process-umask.js From 748ca53a03c544eb71c35eb82c2f901599dae8a1 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 9 May 2018 22:44:44 +0800 Subject: [PATCH 03/14] lib: mask mode_t type of arguments with 0o777 - Introduce the `validateAndMaskMode` validator that validates `mode_t` arguments and mask them with 0o777 if they are 32-bit unsigned integer or octal string to be more consistent with POSIX APIs. - Use the validator in fs APIs and process.umask for consistency. - Add tests for 32-bit unsigned modes larger than 0o777. PR-URL: https://github.com/nodejs/node/pull/20636 Fixes: https://github.com/nodejs/node/issues/20498 Reviewed-By: James M Snell Reviewed-By: Anatoli Papirovski --- lib/fs.js | 63 ++++++++-------- lib/internal/fs/promises.js | 19 ++--- lib/internal/fs/utils.js | 16 ---- lib/internal/validators.js | 36 +++++++++ test/parallel/test-fs-chmod-mask.js | 95 ++++++++++++++++++++++++ test/parallel/test-fs-chmod.js | 8 +- test/parallel/test-fs-fchmod.js | 38 +++------- test/parallel/test-fs-mkdir-mode-mask.js | 45 +++++++++++ test/parallel/test-fs-open-mode-mask.js | 48 ++++++++++++ test/parallel/test-fs-promises.js | 26 +------ test/parallel/test-process-umask-mask.js | 29 ++++++++ test/parallel/test-process-umask.js | 6 +- 12 files changed, 313 insertions(+), 116 deletions(-) create mode 100644 test/parallel/test-fs-chmod-mask.js create mode 100644 test/parallel/test-fs-mkdir-mode-mask.js create mode 100644 test/parallel/test-fs-open-mode-mask.js create mode 100644 test/parallel/test-process-umask-mask.js diff --git a/lib/fs.js b/lib/fs.js index 7168b7875498c5..2e4d8499ef62e3 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -65,7 +65,6 @@ const internalUtil = require('internal/util'); const { copyObject, getOptions, - modeNum, nullCheck, preprocessSymlinkDestination, Stats, @@ -85,6 +84,7 @@ const { } = require('internal/constants'); const { isUint32, + validateAndMaskMode, validateInteger, validateUint32 } = require('internal/validators'); @@ -549,32 +549,36 @@ fs.closeSync = function(fd) { handleErrorFromBinding(ctx); }; -fs.open = function(path, flags, mode, callback_) { - var callback = makeCallback(arguments[arguments.length - 1]); - mode = modeNum(mode, 0o666); - +fs.open = function(path, flags, mode, callback) { path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + const flagsNumber = stringToFlags(flags); + if (arguments.length < 4) { + callback = makeCallback(mode); + mode = 0o666; + } else { + mode = validateAndMaskMode(mode, 'mode', 0o666); + callback = makeCallback(callback); + } const req = new FSReqWrap(); req.oncomplete = callback; binding.open(pathModule.toNamespacedPath(path), - stringToFlags(flags), + flagsNumber, mode, req); }; fs.openSync = function(path, flags, mode) { - mode = modeNum(mode, 0o666); path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + const flagsNumber = stringToFlags(flags); + mode = validateAndMaskMode(mode, 'mode', 0o666); const ctx = { path }; const result = binding.open(pathModule.toNamespacedPath(path), - stringToFlags(flags), mode, + flagsNumber, mode, undefined, ctx); handleErrorFromBinding(ctx); return result; @@ -849,12 +853,16 @@ fs.fsyncSync = function(fd) { }; fs.mkdir = function(path, mode, callback) { - if (typeof mode === 'function') callback = mode; - callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode, 0o777); - validateUint32(mode, 'mode'); + + if (arguments.length < 3) { + callback = makeCallback(mode); + mode = 0o777; + } else { + callback = makeCallback(callback); + mode = validateAndMaskMode(mode, 'mode', 0o777); + } const req = new FSReqWrap(); req.oncomplete = callback; @@ -864,8 +872,7 @@ fs.mkdir = function(path, mode, callback) { fs.mkdirSync = function(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode, 0o777); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode', 0o777); const ctx = { path }; binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); @@ -1047,25 +1054,18 @@ fs.unlinkSync = function(path) { }; fs.fchmod = function(fd, mode, callback) { - mode = modeNum(mode); validateUint32(fd, 'fd'); - validateUint32(mode, 'mode'); - // Values for mode < 0 are already checked via the validateUint32 function - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); + mode = validateAndMaskMode(mode, 'mode'); + callback = makeCallback(callback); const req = new FSReqWrap(); - req.oncomplete = makeCallback(callback); + req.oncomplete = callback; binding.fchmod(fd, mode, req); }; fs.fchmodSync = function(fd, mode) { - mode = modeNum(mode); validateUint32(fd, 'fd'); - validateUint32(mode, 'mode'); - // Values for mode < 0 are already checked via the validateUint32 function - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); + mode = validateAndMaskMode(mode, 'mode'); const ctx = {}; binding.fchmod(fd, mode, undefined, ctx); handleErrorFromBinding(ctx); @@ -1106,11 +1106,10 @@ if (O_SYMLINK !== undefined) { fs.chmod = function(path, mode, callback) { - callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode'); + callback = makeCallback(callback); const req = new FSReqWrap(); req.oncomplete = callback; @@ -1120,8 +1119,8 @@ fs.chmod = function(path, mode, callback) { fs.chmodSync = function(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode'); + const ctx = { path }; binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index d4dbffd850694e..9ff319ecb4ee61 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -12,8 +12,7 @@ const { Buffer, kMaxLength } = require('buffer'); const { ERR_FS_FILE_TOO_LARGE, ERR_INVALID_ARG_TYPE, - ERR_METHOD_NOT_IMPLEMENTED, - ERR_OUT_OF_RANGE + ERR_METHOD_NOT_IMPLEMENTED } = require('internal/errors').codes; const { getPathFromURL } = require('internal/url'); const { isUint8Array } = require('internal/util/types'); @@ -21,7 +20,6 @@ const { copyObject, getOptions, getStatsFromBinding, - modeNum, nullCheck, preprocessSymlinkDestination, stringToFlags, @@ -34,6 +32,7 @@ const { } = require('internal/fs/utils'); const { isUint32, + validateAndMaskMode, validateInteger, validateUint32 } = require('internal/validators'); @@ -191,10 +190,9 @@ async function copyFile(src, dest, flags) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - mode = modeNum(mode, 0o666); path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode', 0o666); return new FileHandle( await binding.openFileHandle(pathModule.toNamespacedPath(path), stringToFlags(flags), @@ -287,10 +285,9 @@ async function fsync(handle) { } async function mkdir(path, mode) { - mode = modeNum(mode, 0o777); path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode', 0o777); return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises); } @@ -361,19 +358,15 @@ async function unlink(path) { } async function fchmod(handle, mode) { - mode = modeNum(mode); validateFileHandle(handle); - validateUint32(mode, 'mode'); - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); + mode = validateAndMaskMode(mode, 'mode'); return binding.fchmod(handle.fd, mode, kUsePromises); } async function chmod(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateAndMaskMode(mode, 'mode'); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); } diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 2f7a8d8ced176e..46b3a97f741572 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -70,21 +70,6 @@ function getOptions(options, defaultOptions) { return options; } -function modeNum(m, def) { - if (typeof m === 'number') - return m; - if (typeof m === 'string') { - const parsed = parseInt(m, 8); - if (Number.isNaN(parsed)) - return m; - return parsed; - } - // TODO(BridgeAR): Only return `def` in case `m == null` - if (def !== undefined) - return def; - return m; -} - // Check if the path contains null types if it is a string nor Uint8Array, // otherwise return silently. function nullCheck(path, propName, throwError = true) { @@ -391,7 +376,6 @@ module.exports = { assertEncoding, copyObject, getOptions, - modeNum, nullCheck, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), diff --git a/lib/internal/validators.js b/lib/internal/validators.js index aabe71ef33979a..991af52fee5b95 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -2,6 +2,7 @@ const { ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE } = require('internal/errors').codes; @@ -13,6 +14,40 @@ function isUint32(value) { return value === (value >>> 0); } +const octalReg = /^[0-7]+$/; +const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; +// Validator for mode_t (the S_* constants). Valid numbers or octal strings +// will be masked with 0o777 to be consistent with the behavior in POSIX APIs. +function validateAndMaskMode(value, name, def) { + if (isUint32(value)) { + return value & 0o777; + } + + if (typeof value === 'number') { + if (!Number.isInteger(value)) { + throw new ERR_OUT_OF_RANGE(name, 'an integer', value); + } else { + // 2 ** 32 === 4294967296 + throw new ERR_OUT_OF_RANGE(name, '>= 0 && < 4294967296', value); + } + } + + if (typeof value === 'string') { + if (!octalReg.test(value)) { + throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); + } + const parsed = parseInt(value, 8); + return parsed & 0o777; + } + + // TODO(BridgeAR): Only return `def` in case `value == null` + if (def !== undefined) { + return def; + } + + throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); +} + function validateInteger(value, name) { let err; @@ -67,6 +102,7 @@ function validateUint32(value, name, positive) { module.exports = { isInt32, isUint32, + validateAndMaskMode, validateInteger, validateInt32, validateUint32 diff --git a/test/parallel/test-fs-chmod-mask.js b/test/parallel/test-fs-chmod-mask.js new file mode 100644 index 00000000000000..5f3a8d5ab82864 --- /dev/null +++ b/test/parallel/test-fs-chmod-mask.js @@ -0,0 +1,95 @@ +'use strict'; + +// This tests that mode > 0o777 will be masked off with 0o777 in fs APIs. + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; +// On Windows chmod is only able to manipulate write permission +if (common.isWindows) { + mode = 0o444; // read-only +} else { + mode = 0o777; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `chmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmod(file, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } + + { + const file = path.join(tmpdir.path, `chmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmodSync(file, input); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = path.join(tmpdir.path, `fchmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.open(file, 'w', common.mustCall((err, fd) => { + assert.ifError(err); + + fs.fchmod(fd, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.close(fd, assert.ifError); + })); + })); + } + + { + const file = path.join(tmpdir.path, `fchmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + const fd = fs.openSync(file, 'w'); + + fs.fchmodSync(fd, input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + + fs.close(fd, assert.ifError); + } + + if (fs.lchmod) { + const link = path.join(tmpdir.path, `lchmod-src-${suffix}`); + const file = path.join(tmpdir.path, `lchmod-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmod(link, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + })); + } + + if (fs.lchmodSync) { + const link = path.join(tmpdir.path, `lchmodSync-src-${suffix}`); + const file = path.join(tmpdir.path, `lchmodSync-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmodSync(link, input); + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-chmod.js b/test/parallel/test-fs-chmod.js index 6727ec2144f2ce..ed5919ce887bc8 100644 --- a/test/parallel/test-fs-chmod.js +++ b/test/parallel/test-fs-chmod.js @@ -62,7 +62,7 @@ function closeSync() { } -// On Windows chmod is only able to manipulate read-only bit +// On Windows chmod is only able to manipulate write permission if (common.isWindows) { mode_async = 0o400; // read-only mode_sync = 0o600; // read-write @@ -112,10 +112,10 @@ fs.open(file2, 'w', common.mustCall((err, fd) => { common.expectsError( () => fs.fchmod(fd, {}), { - code: 'ERR_INVALID_ARG_TYPE', + code: 'ERR_INVALID_ARG_VALUE', type: TypeError, - message: 'The "mode" argument must be of type number. ' + - 'Received type object' + message: 'The argument \'mode\' must be a 32-bit unsigned integer ' + + 'or an octal string. Received {}' } ); diff --git a/test/parallel/test-fs-fchmod.js b/test/parallel/test-fs-fchmod.js index 63d780a57dfc66..df7748538a5cc4 100644 --- a/test/parallel/test-fs-fchmod.js +++ b/test/parallel/test-fs-fchmod.js @@ -1,6 +1,7 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); +const util = require('util'); const fs = require('fs'); // This test ensures that input for fchmod is valid, testing for valid @@ -16,7 +17,16 @@ const fs = require('fs'); }; assert.throws(() => fs.fchmod(input), errObj); assert.throws(() => fs.fchmodSync(input), errObj); - errObj.message = errObj.message.replace('fd', 'mode'); +}); + + +[false, null, undefined, {}, [], '', '123x'].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError [ERR_INVALID_ARG_VALUE]', + message: 'The argument \'mode\' must be a 32-bit unsigned integer or an ' + + `octal string. Received ${util.inspect(input)}` + }; assert.throws(() => fs.fchmod(1, input), errObj); assert.throws(() => fs.fchmodSync(1, input), errObj); }); @@ -62,27 +72,3 @@ const fs = require('fs'); assert.throws(() => fs.fchmod(1, input), errObj); assert.throws(() => fs.fchmodSync(1, input), errObj); }); - -// Check for mode values range -const modeUpperBoundaryValue = 0o777; -fs.fchmod(1, modeUpperBoundaryValue, common.mustCall()); -fs.fchmodSync(1, modeUpperBoundaryValue); - -// umask of 0o777 is equal to 775 -const modeOutsideUpperBoundValue = 776; -assert.throws( - () => fs.fchmod(1, modeOutsideUpperBoundValue), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]', - message: 'The value of "mode" is out of range. Received 776' - } -); -assert.throws( - () => fs.fchmodSync(1, modeOutsideUpperBoundValue), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]', - message: 'The value of "mode" is out of range. Received 776' - } -); diff --git a/test/parallel/test-fs-mkdir-mode-mask.js b/test/parallel/test-fs-mkdir-mode-mask.js new file mode 100644 index 00000000000000..e4e8a423483376 --- /dev/null +++ b/test/parallel/test-fs-mkdir-mode-mask.js @@ -0,0 +1,45 @@ +'use strict'; + +// This tests that mode > 0o777 will be masked off with 0o777 in fs.mkdir(). + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; + +if (common.isWindows) { + common.skip('mode is not supported in mkdir on Windows'); + return; +} else { + mode = 0o644; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const dir = path.join(tmpdir.path, `mkdirSync-${suffix}`); + fs.mkdirSync(dir, input); + assert.strictEqual(fs.statSync(dir).mode & 0o777, mode); + } + + { + const dir = path.join(tmpdir.path, `mkdir-${suffix}`); + fs.mkdir(dir, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.statSync(dir).mode & 0o777, mode); + })); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-open-mode-mask.js b/test/parallel/test-fs-open-mode-mask.js new file mode 100644 index 00000000000000..4db41864af099c --- /dev/null +++ b/test/parallel/test-fs-open-mode-mask.js @@ -0,0 +1,48 @@ +'use strict'; + +// This tests that mode > 0o777 will be masked off with 0o777 in fs.open(). + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; + +if (common.isWindows) { + mode = 0o444; +} else { + mode = 0o644; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `openSync-${suffix}.txt`); + const fd = fs.openSync(file, 'w+', input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = path.join(tmpdir.path, `open-${suffix}.txt`); + fs.open(file, 'w+', input, common.mustCall((err, fd) => { + assert.ifError(err); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-promises.js b/test/parallel/test-fs-promises.js index b7e01544162e81..53219342d93af8 100644 --- a/test/parallel/test-fs-promises.js +++ b/test/parallel/test-fs-promises.js @@ -114,28 +114,10 @@ function verifyStatObject(stat) { await fchmod(handle, 0o666); await handle.chmod(0o666); - // `mode` can't be > 0o777 - assert.rejects( - async () => chmod(handle, (0o777 + 1)), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError [ERR_INVALID_ARG_TYPE]' - } - ); - assert.rejects( - async () => fchmod(handle, (0o777 + 1)), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]' - } - ); - assert.rejects( - async () => handle.chmod(handle, (0o777 + 1)), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError [ERR_INVALID_ARG_TYPE]' - } - ); + // Mode larger than 0o777 should be masked off. + await chmod(dest, (0o777 + 1)); + await fchmod(handle, 0o777 + 1); + await handle.chmod(0o777 + 1); await utimes(dest, new Date(), new Date()); diff --git a/test/parallel/test-process-umask-mask.js b/test/parallel/test-process-umask-mask.js new file mode 100644 index 00000000000000..c312c061d2b76a --- /dev/null +++ b/test/parallel/test-process-umask-mask.js @@ -0,0 +1,29 @@ +'use strict'; + +// This tests that mask > 0o777 will be masked off with 0o777 in +// process.umask() + +const common = require('../common'); +const assert = require('assert'); + +let mask; + +if (common.isWindows) { + mask = 0o600; +} else { + mask = 0o664; +} + +const maskToIgnore = 0o10000; + +const old = process.umask(); + +function test(input, output) { + process.umask(input); + assert.strictEqual(process.umask(), output); + + process.umask(old); +} + +test(mask | maskToIgnore, mask); +test((mask | maskToIgnore).toString(8), mask); diff --git a/test/parallel/test-process-umask.js b/test/parallel/test-process-umask.js index 1ac32cb7018906..c0c00f3ba397f8 100644 --- a/test/parallel/test-process-umask.js +++ b/test/parallel/test-process-umask.js @@ -33,13 +33,13 @@ if (common.isWindows) { const old = process.umask(mask); -assert.strictEqual(parseInt(mask, 8), process.umask(old)); +assert.strictEqual(process.umask(old), parseInt(mask, 8)); // confirm reading the umask does not modify it. // 1. If the test fails, this call will succeed, but the mask will be set to 0 -assert.strictEqual(old, process.umask()); +assert.strictEqual(process.umask(), old); // 2. If the test fails, process.umask() will return 0 -assert.strictEqual(old, process.umask()); +assert.strictEqual(process.umask(), old); assert.throws(() => { process.umask({}); From 57a11af5e5ed909a50cb7232617b0016c922b40d Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 10 May 2018 01:26:42 +0800 Subject: [PATCH 04/14] doc: document file mode caveats on Windows - On Windows only the write permission (read-only bit) can be manipulated, and there is no distinction among owner, group or others. - mkdir on Windows does not support the mode argument. PR-URL: https://github.com/nodejs/node/pull/20636 Fixes: https://github.com/nodejs/node/issues/20498 Reviewed-By: James M Snell Reviewed-By: Anatoli Papirovski --- doc/api/fs.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index c2daa8d71a5055..af06bf91e5b5f5 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1089,6 +1089,10 @@ For example, the octal value `0o765` means: * The group may read and write the file. * Others may read and execute the file. +Caveats: on Windows only the write permission can be changed, and the +distinction among the permissions of group, owner or others is not +implemented. + ## fs.chmodSync(path, mode) * `path` {string|Buffer|URL} -* `mode` {integer} **Default:** `0o777` +* `mode` {integer} Not supported on Windows. **Default:** `0o777`. * `callback` {Function} * `err` {Error} @@ -2007,7 +2011,7 @@ changes: --> * `path` {string|Buffer|URL} -* `mode` {integer} **Default:** `0o777` +* `mode` {integer} Not supported on Windows. **Default:** `0o777`. Synchronously creates a directory. Returns `undefined`. This is the synchronous version of [`fs.mkdir()`][]. @@ -2127,7 +2131,8 @@ changes: Asynchronous file open. See open(2). `mode` sets the file mode (permission and sticky bits), but only if the file was -created. +created. Note that on Windows only the write permission can be manipulated, +see [`fs.chmod()`][]. The callback gets two arguments `(err, fd)`. From f6ab98937df5a8a3155b7d9e9073a76c6812f5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 6 May 2018 12:13:56 +0300 Subject: [PATCH 05/14] fs: drop duplicate API in promises mode This drops exporting duplicate methods that accept FileHandle as the first argument (to mirror callback-based methods accepting 'fd'). Those methods were not adding actual value to the API because all of those are already present as FileHandle methods, and they would probably be confusing to the new users and making docs harder to read. Also, the API was a bit inconsistent and lacked .close(handle). Fixes: https://github.com/nodejs/node/issues/20548 PR-URL: https://github.com/nodejs/node/pull/20559 Reviewed-By: Matteo Collina Reviewed-By: Franziska Hinkelmann Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Colin Ihrig Reviewed-By: Joyee Cheung Reviewed-By: Ruben Bridgewater --- benchmark/fs/bench-stat-promise.js | 6 +- doc/api/fs.md | 184 ----------------------------- lib/internal/fs/promises.js | 9 -- test/parallel/test-fs-promises.js | 23 +--- 4 files changed, 8 insertions(+), 214 deletions(-) diff --git a/benchmark/fs/bench-stat-promise.js b/benchmark/fs/bench-stat-promise.js index 96c7058fa6218a..99a5da5799b787 100644 --- a/benchmark/fs/bench-stat-promise.js +++ b/benchmark/fs/bench-stat-promise.js @@ -9,12 +9,12 @@ const bench = common.createBenchmark(main, { }); async function run(n, statType) { - const arg = statType === 'fstat' ? - await fsPromises.open(__filename, 'r') : __filename; + const handleMode = statType === 'fstat'; + const arg = handleMode ? await fsPromises.open(__filename, 'r') : __filename; let remaining = n; bench.start(); while (remaining-- > 0) - await fsPromises[statType](arg); + await (handleMode ? arg.stat() : fsPromises[statType](arg)); bench.end(n); if (typeof arg.close === 'function') diff --git a/doc/api/fs.md b/doc/api/fs.md index af06bf91e5b5f5..bdd5c9758cc3e3 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -3800,128 +3800,6 @@ fsPromises.copyFile('source.txt', 'destination.txt', COPYFILE_EXCL) .catch(() => console.log('The file could not be copied')); ``` -### fsPromises.fchmod(filehandle, mode) - - -* `filehandle` {FileHandle} -* `mode` {integer} -* Returns: {Promise} - -Asynchronous fchmod(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.fchown(filehandle, uid, gid) - - -* `filehandle` {FileHandle} -* `uid` {integer} -* `gid` {integer} -* Returns: {Promise} - -Changes the ownership of the file represented by `filehandle` then resolves -the `Promise` with no arguments upon success. - -### fsPromises.fdatasync(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Asynchronous fdatasync(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.fstat(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Retrieves the [`fs.Stats`][] for the given `filehandle`. - -### fsPromises.fsync(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Asynchronous fsync(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.ftruncate(filehandle[, len]) - - -* `filehandle` {FileHandle} -* `len` {integer} **Default:** `0` -* Returns: {Promise} - -Truncates the file represented by `filehandle` then resolves the `Promise` -with no arguments upon success. - -If the file referred to by the `FileHandle` was larger than `len` bytes, only -the first `len` bytes will be retained in the file. - -For example, the following program retains only the first four bytes of the -file: - -```js -console.log(fs.readFileSync('temp.txt', 'utf8')); -// Prints: Node.js - -async function doTruncate() { - const fd = await fsPromises.open('temp.txt', 'r+'); - await fsPromises.ftruncate(fd, 4); - console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints: Node -} - -doTruncate().catch(console.error); -``` - -If the file previously was shorter than `len` bytes, it is extended, and the -extended part is filled with null bytes (`'\0'`). For example, - -```js -console.log(fs.readFileSync('temp.txt', 'utf8')); -// Prints: Node.js - -async function doTruncate() { - const fd = await fsPromises.open('temp.txt', 'r+'); - await fsPromises.ftruncate(fd, 10); - console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints Node.js\0\0\0 -} - -doTruncate().catch(console.error); -``` - -The last three bytes are null bytes (`'\0'`), to compensate the over-truncation. - -### fsPromises.futimes(filehandle, atime, mtime) - - -* `filehandle` {FileHandle} -* `atime` {number|string|Date} -* `mtime` {number|string|Date} -* Returns: {Promise} - -Change the file system timestamps of the object referenced by the supplied -`FileHandle` then resolves the `Promise` with no arguments upon success. - -This function does not work on AIX versions before 7.1, it will resolve the -`Promise` with an error using code `UV_ENOSYS`. - ### fsPromises.lchmod(path, mode) - -* `filehandle` {FileHandle} -* `buffer` {Buffer|Uint8Array} -* `offset` {integer} -* `length` {integer} -* `position` {integer} -* Returns: {Promise} - -Read data from the file specified by `filehandle`. - -`buffer` is the buffer that the data will be written to. - -`offset` is the offset in the buffer to start writing at. - -`length` is an integer specifying the number of bytes to read. - -`position` is an argument specifying where to begin reading from in the file. -If `position` is `null`, data will be read from the current file position, -and the file position will be updated. -If `position` is an integer, the file position will remain unchanged. - -Following successful read, the `Promise` is resolved with an object with a -`bytesRead` property specifying the number of bytes read, and a `buffer` -property that is a reference to the passed in `buffer` argument. - ### fsPromises.readdir(path[, options]) - -* `filehandle` {FileHandle} -* `buffer` {Buffer|Uint8Array} -* `offset` {integer} -* `length` {integer} -* `position` {integer} -* Returns: {Promise} - -Write `buffer` to the file specified by `filehandle`. - -The `Promise` is resolved with an object containing a `bytesWritten` property -identifying the number of bytes written, and a `buffer` property containing -a reference to the `buffer` written. - -`offset` determines the part of the buffer to be written, and `length` is -an integer specifying the number of bytes to write. - -`position` refers to the offset from the beginning of the file where this data -should be written. If `typeof position !== 'number'`, the data will be written -at the current position. See pwrite(2). - -It is unsafe to use `fsPromises.write()` multiple times on the same file -without waiting for the `Promise` to be resolved (or rejected). For this -scenario, `fs.createWriteStream` is strongly recommended. - -On Linux, positional writes do not work when the file is opened in append mode. -The kernel ignores the position argument and always appends the data to -the end of the file. - ### fsPromises.writeFile(file, data[, options]) -* `host` {string} The hostname to verify the certificate against +* `hostname` {string} The hostname to verify the certificate against * `cert` {Object} An object representing the peer's certificate. The returned object has some properties corresponding to the fields of the certificate. * Returns: {Error|undefined} -Verifies the certificate `cert` is issued to host `host`. +Verifies the certificate `cert` is issued to `hostname`. Returns {Error} object, populating it with the reason, host, and cert on failure. On success, returns {undefined}. diff --git a/lib/tls.js b/lib/tls.js index 1e444d5d8898c2..f4b72851907862 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -169,14 +169,14 @@ function check(hostParts, pattern, wildcards) { return true; } -exports.checkServerIdentity = function checkServerIdentity(host, cert) { +exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { const subject = cert.subject; const altNames = cert.subjectaltname; const dnsNames = []; const uriNames = []; const ips = []; - host = '' + host; + hostname = '' + hostname; if (altNames) { for (const name of altNames.split(', ')) { @@ -194,14 +194,14 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { let valid = false; let reason = 'Unknown reason'; - if (net.isIP(host)) { - valid = ips.includes(canonicalizeIP(host)); + if (net.isIP(hostname)) { + valid = ips.includes(canonicalizeIP(hostname)); if (!valid) - reason = `IP: ${host} is not in the cert's list: ${ips.join(', ')}`; + reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`; // TODO(bnoordhuis) Also check URI SANs that are IP addresses. } else if (subject) { - host = unfqdn(host); // Remove trailing dot for error messages. - const hostParts = splitHost(host); + hostname = unfqdn(hostname); // Remove trailing dot for error messages. + const hostParts = splitHost(hostname); const wildcard = (pattern) => check(hostParts, pattern, true); const noWildcard = (pattern) => check(hostParts, pattern, false); @@ -215,11 +215,12 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { valid = wildcard(cn); if (!valid) - reason = `Host: ${host}. is not cert's CN: ${cn}`; + reason = `Host: ${hostname}. is not cert's CN: ${cn}`; } else { valid = dnsNames.some(wildcard) || uriNames.some(noWildcard); if (!valid) - reason = `Host: ${host}. is not in the cert's altnames: ${altNames}`; + reason = + `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; } } else { reason = 'Cert is empty'; @@ -228,7 +229,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { if (!valid) { const err = new ERR_TLS_CERT_ALTNAME_INVALID(reason); err.reason = reason; - err.host = host; + err.host = hostname; err.cert = cert; return err; } From 633b24d39f1ea380bd1881bcba2867c5c0e2c2a2 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 26 May 2018 18:51:19 +0800 Subject: [PATCH 12/14] lib: unmask mode_t values with 0o777 This commit allows permission bits higher than 0o777 to go through the API (e.g. `S_ISVTX`=`0o1000`, `S_ISGID`=`0o2000`, `S_ISUID`=`0o4000`). Also documents that these bits are not exposed through `fs.constants` and their behaviors are platform-specific, so the users need to use them on their own risk. PR-URL: https://github.com/nodejs/node/pull/20975 Reviewed-By: Luigi Pinca Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- doc/api/fs.md | 6 ++++++ lib/fs.js | 18 +++++++++--------- lib/internal/fs/promises.js | 10 +++++----- lib/internal/validators.js | 23 +++++++++++++++++------ test/parallel/test-fs-chmod-mask.js | 2 +- test/parallel/test-fs-mkdir-mode-mask.js | 2 +- test/parallel/test-fs-open-mode-mask.js | 2 +- test/parallel/test-fs-promises.js | 5 ++--- test/parallel/test-process-umask-mask.js | 2 +- 9 files changed, 43 insertions(+), 27 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index bdd5c9758cc3e3..e492660bae2f2a 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1089,6 +1089,12 @@ For example, the octal value `0o765` means: * The group may read and write the file. * Others may read and execute the file. +Note: When using raw numbers where file modes are expected, +any value larger than `0o777` may result in platform-specific +behaviors that are not supported to work consistently. +Therefore constants like `S_ISVTX`, `S_ISGID` or `S_ISUID` are +not exposed in `fs.constants`. + Caveats: on Windows only the write permission can be changed, and the distinction among the permissions of group, owner or others is not implemented. diff --git a/lib/fs.js b/lib/fs.js index 49050af6677cd6..983625bc9b0af3 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -77,7 +77,7 @@ const { } = require('internal/constants'); const { isUint32, - validateAndMaskMode, + validateMode, validateInteger, validateInt32, validateUint32 @@ -416,7 +416,7 @@ function open(path, flags, mode, callback) { callback = makeCallback(mode); mode = 0o666; } else { - mode = validateAndMaskMode(mode, 'mode', 0o666); + mode = validateMode(mode, 'mode', 0o666); callback = makeCallback(callback); } @@ -434,7 +434,7 @@ function openSync(path, flags, mode) { path = getPathFromURL(path); validatePath(path); const flagsNumber = stringToFlags(flags); - mode = validateAndMaskMode(mode, 'mode', 0o666); + mode = validateMode(mode, 'mode', 0o666); const ctx = { path }; const result = binding.open(pathModule.toNamespacedPath(path), @@ -721,7 +721,7 @@ function mkdir(path, mode, callback) { mode = 0o777; } else { callback = makeCallback(callback); - mode = validateAndMaskMode(mode, 'mode', 0o777); + mode = validateMode(mode, 'mode', 0o777); } const req = new FSReqWrap(); @@ -732,7 +732,7 @@ function mkdir(path, mode, callback) { function mkdirSync(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode', 0o777); + mode = validateMode(mode, 'mode', 0o777); const ctx = { path }; binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); @@ -915,7 +915,7 @@ function unlinkSync(path) { function fchmod(fd, mode, callback) { validateInt32(fd, 'fd', 0); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); callback = makeCallback(callback); const req = new FSReqWrap(); @@ -925,7 +925,7 @@ function fchmod(fd, mode, callback) { function fchmodSync(fd, mode) { validateInt32(fd, 'fd', 0); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); const ctx = {}; binding.fchmod(fd, mode, undefined, ctx); handleErrorFromBinding(ctx); @@ -966,7 +966,7 @@ function lchmodSync(path, mode) { function chmod(path, mode, callback) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); callback = makeCallback(callback); const req = new FSReqWrap(); @@ -977,7 +977,7 @@ function chmod(path, mode, callback) { function chmodSync(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); const ctx = { path }; binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 8774f48e99ed22..43956dae3f4d18 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -32,7 +32,7 @@ const { } = require('internal/fs/utils'); const { isUint32, - validateAndMaskMode, + validateMode, validateInteger, validateUint32 } = require('internal/validators'); @@ -192,7 +192,7 @@ async function copyFile(src, dest, flags) { async function open(path, flags, mode) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode', 0o666); + mode = validateMode(mode, 'mode', 0o666); return new FileHandle( await binding.openFileHandle(pathModule.toNamespacedPath(path), stringToFlags(flags), @@ -287,7 +287,7 @@ async function fsync(handle) { async function mkdir(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode', 0o777); + mode = validateMode(mode, 'mode', 0o777); return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises); } @@ -359,14 +359,14 @@ async function unlink(path) { async function fchmod(handle, mode) { validateFileHandle(handle); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); return binding.fchmod(handle.fd, mode, kUsePromises); } async function chmod(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = validateAndMaskMode(mode, 'mode'); + mode = validateMode(mode, 'mode'); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); } diff --git a/lib/internal/validators.js b/lib/internal/validators.js index 17b10dab189133..b7b21d29bfa097 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -16,11 +16,22 @@ function isUint32(value) { const octalReg = /^[0-7]+$/; const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; -// Validator for mode_t (the S_* constants). Valid numbers or octal strings -// will be masked with 0o777 to be consistent with the behavior in POSIX APIs. -function validateAndMaskMode(value, name, def) { + +/** + * Validate values that will be converted into mode_t (the S_* constants). + * Only valid numbers and octal strings are allowed. They could be converted + * to 32-bit unsigned integers or non-negative signed integers in the C++ + * land, but any value higher than 0o777 will result in platform-specific + * behaviors. + * + * @param {*} value Values to be validated + * @param {string} name Name of the argument + * @param {number} def If specified, will be returned for invalid values + * @returns {number} + */ +function validateMode(value, name, def) { if (isUint32(value)) { - return value & 0o777; + return value; } if (typeof value === 'number') { @@ -37,7 +48,7 @@ function validateAndMaskMode(value, name, def) { throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); } const parsed = parseInt(value, 8); - return parsed & 0o777; + return parsed; } // TODO(BridgeAR): Only return `def` in case `value == null` @@ -106,7 +117,7 @@ function validateUint32(value, name, positive) { module.exports = { isInt32, isUint32, - validateAndMaskMode, + validateMode, validateInteger, validateInt32, validateUint32 diff --git a/test/parallel/test-fs-chmod-mask.js b/test/parallel/test-fs-chmod-mask.js index 5f3a8d5ab82864..9564ca142bd643 100644 --- a/test/parallel/test-fs-chmod-mask.js +++ b/test/parallel/test-fs-chmod-mask.js @@ -1,6 +1,6 @@ 'use strict'; -// This tests that mode > 0o777 will be masked off with 0o777 in fs APIs. +// This tests that the lower bits of mode > 0o777 still works in fs APIs. const common = require('../common'); const assert = require('assert'); diff --git a/test/parallel/test-fs-mkdir-mode-mask.js b/test/parallel/test-fs-mkdir-mode-mask.js index e4e8a423483376..515b982054b82b 100644 --- a/test/parallel/test-fs-mkdir-mode-mask.js +++ b/test/parallel/test-fs-mkdir-mode-mask.js @@ -1,6 +1,6 @@ 'use strict'; -// This tests that mode > 0o777 will be masked off with 0o777 in fs.mkdir(). +// This tests that the lower bits of mode > 0o777 still works in fs.mkdir(). const common = require('../common'); const assert = require('assert'); diff --git a/test/parallel/test-fs-open-mode-mask.js b/test/parallel/test-fs-open-mode-mask.js index 4db41864af099c..0cd9a2c5923a71 100644 --- a/test/parallel/test-fs-open-mode-mask.js +++ b/test/parallel/test-fs-open-mode-mask.js @@ -1,6 +1,6 @@ 'use strict'; -// This tests that mode > 0o777 will be masked off with 0o777 in fs.open(). +// This tests that the lower bits of mode > 0o777 still works in fs.open(). const common = require('../common'); const assert = require('assert'); diff --git a/test/parallel/test-fs-promises.js b/test/parallel/test-fs-promises.js index 32439ce0c48cac..6871a6762b68e2 100644 --- a/test/parallel/test-fs-promises.js +++ b/test/parallel/test-fs-promises.js @@ -107,9 +107,8 @@ function verifyStatObject(stat) { await chmod(dest, 0o666); await handle.chmod(0o666); - // Mode larger than 0o777 should be masked off. - await chmod(dest, (0o777 + 1)); - await handle.chmod(0o777 + 1); + await chmod(dest, (0o10777)); + await handle.chmod(0o10777); await utimes(dest, new Date(), new Date()); diff --git a/test/parallel/test-process-umask-mask.js b/test/parallel/test-process-umask-mask.js index c312c061d2b76a..8ec8fc0074ac1b 100644 --- a/test/parallel/test-process-umask-mask.js +++ b/test/parallel/test-process-umask-mask.js @@ -1,6 +1,6 @@ 'use strict'; -// This tests that mask > 0o777 will be masked off with 0o777 in +// This tests that the lower bits of mode > 0o777 still works in // process.umask() const common = require('../common'); From d2ad241fb730aaffc503164f69a4bbfe3e9d9053 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sun, 27 May 2018 06:07:29 +0800 Subject: [PATCH 13/14] fs: do not crash when using a closed fs event watcher Before this commit, when the user calls methods on a closed or errored fs event watcher, they could hit a crash since the FSEventWrap in C++ land may have already been destroyed with the internal pointer set to nullptr. This commit makes sure that the user cannot hit crashes like that, instead the methods calling on a closed watcher will be noops. Also explicitly documents that the watchers should not be used in `close` and `error` event handlers. PR-URL: https://github.com/nodejs/node/pull/20985 Fixes: https://github.com/nodejs/node/issues/20738 Fixes: https://github.com/nodejs/node/issues/20297 Reviewed-By: Anna Henningsen Reviewed-By: Benjamin Gruenbaum Reviewed-By: Ruben Bridgewater Reviewed-By: Ron Korving Reviewed-By: Sakthipriyan Vairamani Reviewed-By: James M Snell Reviewed-By: Tiancheng "Timothy" Gu --- doc/api/fs.md | 6 ++- lib/internal/fs/watchers.js | 21 ++++++++-- .../test-fs-watch-close-when-destroyed.js | 38 +++++++++++++++++++ test/parallel/test-fs-watch.js | 19 ++++++++-- 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 test/parallel/test-fs-watch-close-when-destroyed.js diff --git a/doc/api/fs.md b/doc/api/fs.md index e492660bae2f2a..02bf4abdf8b13b 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -325,7 +325,8 @@ fs.watch('./tmp', { encoding: 'buffer' }, (eventType, filename) => { added: v10.0.0 --> -Emitted when the watcher stops watching for changes. +Emitted when the watcher stops watching for changes. The closed +`fs.FSWatcher` object is no longer usable in the event handler. ### Event: 'error'