From c0b9086f38ae3411a1d8d702801788efa9e7d94d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 15 Sep 2017 17:18:28 -0700 Subject: [PATCH 1/2] fs: initial experimental promisified API Introduces an experimental promises API for the fs module. The API is accessible via `fs.async`, e.g. ```js const { async:fs } = require('fs'); fs.access('.') .then(console.log) .catch(console.error); ``` Most fs functions are supported. There are some variations from the base fs module (for instance, `fs.async.realpath()` uses the faster libuv realpath implementation rather than the slower js implementation. This does not yet include documentation updates. --- benchmark/fs/promises-access.js | 56 ++ benchmark/fs/promises-chmod.js | 70 ++ benchmark/fs/promises-chown.js | 77 ++ benchmark/fs/promises-copyfile.js | 76 ++ benchmark/fs/promises-open-close.js | 77 ++ benchmark/fs/promises-readfile.js | 58 ++ lib/fs.js | 883 +++++++++------- lib/internal/async/fs.js | 947 ++++++++++++++++++ lib/internal/errors.js | 3 + lib/internal/fs.js | 235 ++++- lib/internal/repl.js | 6 +- node.gyp | 1 + src/node_constants.cc | 8 + src/node_file.cc | 367 ++----- test/parallel/test-fs-access.js | 11 +- test/parallel/test-fs-buffer.js | 11 +- test/parallel/test-fs-chmod.js | 32 + test/parallel/test-fs-close-errors.js | 24 + test/parallel/test-fs-copyfile.js | 22 +- test/parallel/test-fs-exists.js | 10 +- test/parallel/test-fs-link.js | 42 +- test/parallel/test-fs-null-bytes.js | 37 +- test/parallel/test-fs-open-flags.js | 8 +- test/parallel/test-fs-open.js | 37 + test/parallel/test-fs-promise-access.js | 103 ++ test/parallel/test-fs-promise-chmod.js | 116 +++ test/parallel/test-fs-promise-chown.js | 136 +++ test/parallel/test-fs-promise-copyfile.js | 136 +++ test/parallel/test-fs-promise-fdatasync.js | 46 + test/parallel/test-fs-promise-ftruncate.js | 81 ++ test/parallel/test-fs-promise-link.js | 122 +++ test/parallel/test-fs-promise-mkdir.js | 64 ++ test/parallel/test-fs-promise-mkdtemp.js | 35 + test/parallel/test-fs-promise-open-close.js | 93 ++ .../test-fs-promise-read-write-file.js | 47 + test/parallel/test-fs-promise-read.js | 33 + test/parallel/test-fs-promise-readdir.js | 82 ++ test/parallel/test-fs-promise-rename.js | 81 ++ test/parallel/test-fs-promise-stat.js | 87 ++ test/parallel/test-fs-promise-symlinks.js | 88 ++ test/parallel/test-fs-promise-utimes.js | 106 ++ test/parallel/test-fs-promise-write.js | 52 + test/parallel/test-fs-read-type.js | 27 +- test/parallel/test-fs-utimes.js | 64 +- test/parallel/test-fs-whatwg-url.js | 69 +- 45 files changed, 4009 insertions(+), 757 deletions(-) create mode 100644 benchmark/fs/promises-access.js create mode 100644 benchmark/fs/promises-chmod.js create mode 100644 benchmark/fs/promises-chown.js create mode 100644 benchmark/fs/promises-copyfile.js create mode 100644 benchmark/fs/promises-open-close.js create mode 100644 benchmark/fs/promises-readfile.js create mode 100644 lib/internal/async/fs.js create mode 100644 test/parallel/test-fs-close-errors.js create mode 100644 test/parallel/test-fs-promise-access.js create mode 100644 test/parallel/test-fs-promise-chmod.js create mode 100644 test/parallel/test-fs-promise-chown.js create mode 100644 test/parallel/test-fs-promise-copyfile.js create mode 100644 test/parallel/test-fs-promise-fdatasync.js create mode 100644 test/parallel/test-fs-promise-ftruncate.js create mode 100644 test/parallel/test-fs-promise-link.js create mode 100644 test/parallel/test-fs-promise-mkdir.js create mode 100644 test/parallel/test-fs-promise-mkdtemp.js create mode 100644 test/parallel/test-fs-promise-open-close.js create mode 100644 test/parallel/test-fs-promise-read-write-file.js create mode 100644 test/parallel/test-fs-promise-read.js create mode 100644 test/parallel/test-fs-promise-readdir.js create mode 100644 test/parallel/test-fs-promise-rename.js create mode 100644 test/parallel/test-fs-promise-stat.js create mode 100644 test/parallel/test-fs-promise-symlinks.js create mode 100644 test/parallel/test-fs-promise-utimes.js create mode 100644 test/parallel/test-fs-promise-write.js diff --git a/benchmark/fs/promises-access.js b/benchmark/fs/promises-access.js new file mode 100644 index 00000000000000..07ea5b6ab8afca --- /dev/null +++ b/benchmark/fs/promises-access.js @@ -0,0 +1,56 @@ +'use strict'; + +const common = require('../common'); +const { access, async } = require('fs'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); + +const bench = common.createBenchmark(main, { + n: [20e4], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(n) { + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + access(__filename, () => countdown.dec()); + } +} + +function usePromisify(n) { + const _access = promisify(access); + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _access(__filename).then(() => countdown.dec()); + } +} + +function usePromise(n) { + const _access = async.access; + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _access(__filename).then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const method = conf.method; + + switch (method) { + case 'legacy': return useLegacy(n); + case 'promisify': return usePromisify(n); + case 'promise': return usePromise(n); + default: + throw new Error('invalid method'); + } +} diff --git a/benchmark/fs/promises-chmod.js b/benchmark/fs/promises-chmod.js new file mode 100644 index 00000000000000..5405fb86193086 --- /dev/null +++ b/benchmark/fs/promises-chmod.js @@ -0,0 +1,70 @@ +'use strict'; + +const common = require('../common'); +const { + chmod, + async, + unlinkSync, + openSync, + closeSync +} = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); +const filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); +const mode = process.platform === 'win32' ? 0o600 : 0o644; + +const bench = common.createBenchmark(main, { + n: [20e4], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(n) { + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + chmod(filename, mode, () => countdown.dec()); + } +} + +function usePromisify(n) { + const _chmod = promisify(chmod); + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _chmod(filename, mode).then(() => countdown.dec()); + } +} + +function usePromise(n) { + const _chmod = async.chmod; + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _chmod(filename, mode).then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const method = conf.method; + + closeSync(openSync(filename, 'w')); + + switch (method) { + case 'legacy': return useLegacy(n); + case 'promisify': return usePromisify(n); + case 'promise': return usePromise(n); + default: + throw new Error('invalid method'); + } +} diff --git a/benchmark/fs/promises-chown.js b/benchmark/fs/promises-chown.js new file mode 100644 index 00000000000000..4505c815996385 --- /dev/null +++ b/benchmark/fs/promises-chown.js @@ -0,0 +1,77 @@ +'use strict'; + +const common = require('../common'); + +if (process.platform === 'win32') { + console.error('Benchmark unsupported on Windows'); + process.exit(1); +} + +const { + chown, + async, + unlinkSync, + openSync, + closeSync +} = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); +const filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); +const uid = process.getuid(); +const gid = process.getgid(); + +const bench = common.createBenchmark(main, { + n: [20e4], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(n) { + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + chown(filename, uid, gid, () => countdown.dec()); + } +} + +function usePromisify(n) { + const _chown = promisify(chown); + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _chown(filename, uid, gid).then(() => countdown.dec()); + } +} + +function usePromise(n) { + const _chown = async.chown; + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _chown(filename, uid, gid).then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const method = conf.method; + + closeSync(openSync(filename, 'w')); + + switch (method) { + case 'legacy': return useLegacy(n); + case 'promisify': return usePromisify(n); + case 'promise': return usePromise(n); + default: + throw new Error('invalid method'); + } +} diff --git a/benchmark/fs/promises-copyfile.js b/benchmark/fs/promises-copyfile.js new file mode 100644 index 00000000000000..3f9848ba7fbf9a --- /dev/null +++ b/benchmark/fs/promises-copyfile.js @@ -0,0 +1,76 @@ +'use strict'; + +const common = require('../common'); + +const { + async, + unlinkSync, + openSync, + closeSync, + copyFile +} = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); +const src = path.resolve(__dirname, '.removeme-benchmark-garbage'); +const dest = path.resolve(__dirname, '.removeme-benchmark-garbage2'); + +const bench = common.createBenchmark(main, { + n: [20000], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(n) { + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(src); + unlinkSync(dest); + }); + bench.start(); + for (let i = 0; i < n; i++) { + copyFile(src, dest, (err) => countdown.dec()); + } +} + +function usePromisify(n) { + const _copyFile = promisify(copyFile); + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(src); + unlinkSync(dest); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _copyFile(src, dest) + .then(() => countdown.dec()); + } +} + +function usePromise(n) { + const _copyFile = async.copyFile; + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(src); + unlinkSync(dest); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _copyFile(src, dest) + .then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const method = conf.method; + + closeSync(openSync(src, 'w')); + + switch (method) { + case 'legacy': return useLegacy(n); + case 'promisify': return usePromisify(n); + case 'promise': return usePromise(n); + default: + throw new Error('invalid method'); + } +} diff --git a/benchmark/fs/promises-open-close.js b/benchmark/fs/promises-open-close.js new file mode 100644 index 00000000000000..34e9cbb4259bff --- /dev/null +++ b/benchmark/fs/promises-open-close.js @@ -0,0 +1,77 @@ +'use strict'; + +const common = require('../common'); + +const { + async, + unlinkSync, + open, + close +} = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); +const filename = path.resolve(__dirname, '.removeme-benchmark-garbage'); + +const bench = common.createBenchmark(main, { + n: [20000], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(n) { + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + open(filename, 'w', (err, fd) => { + close(fd, () => { + countdown.dec(); + }); + }); + } +} + +function usePromisify(n) { + const _open = promisify(open); + const _close = promisify(close); + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _open(filename, 'w') + .then(_close) + .then(() => countdown.dec()); + } +} + +function usePromise(n) { + const _open = async.open; + const _close = async.close; + const countdown = new Countdown(n, () => { + bench.end(n); + unlinkSync(filename); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _open(filename, 'w') + .then(_close) + .then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const method = conf.method; + + switch (method) { + case 'legacy': return useLegacy(n); + case 'promisify': return usePromisify(n); + case 'promise': return usePromise(n); + default: + throw new Error('invalid method'); + } +} diff --git a/benchmark/fs/promises-readfile.js b/benchmark/fs/promises-readfile.js new file mode 100644 index 00000000000000..65f5dedcb1d09b --- /dev/null +++ b/benchmark/fs/promises-readfile.js @@ -0,0 +1,58 @@ +'use strict'; + +const common = require('../common'); +const { readFile, async } = require('fs'); +const { promisify } = require('util'); +const Countdown = require('../../test/common/countdown'); + +const bench = common.createBenchmark(main, { + n: [2000], + file: [__filename, 'benchmark/fixtures/alice.html'], + method: ['legacy', 'promisify', 'promise'] +}); + +function useLegacy(file, n) { + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + readFile(file, () => countdown.dec()); + } +} + +function usePromisify(file, n) { + const _readFile = promisify(readFile); + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _readFile(file).then(() => countdown.dec()); + } +} + +function usePromise(file, n) { + const _readFile = async.readFile; + const countdown = new Countdown(n, () => { + bench.end(n); + }); + bench.start(); + for (let i = 0; i < n; i++) { + _readFile(file).then(() => countdown.dec()); + } +} + +function main(conf) { + const n = conf.n >>> 0; + const file = conf.file; + const method = conf.method; + + switch (method) { + case 'legacy': return useLegacy(file, n); + case 'promisify': return usePromisify(file, n); + case 'promise': return usePromise(file, n); + default: + throw new Error('invalid method'); + } +} diff --git a/lib/fs.js b/lib/fs.js index fb1001c8c09d6f..ea430b601d2160 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -38,13 +38,26 @@ const Stream = require('stream').Stream; const EventEmitter = require('events'); const FSReqWrap = binding.FSReqWrap; const FSEvent = process.binding('fs_event_wrap').FSEvent; -const internalFS = require('internal/fs'); const internalURL = require('internal/url'); const internalUtil = require('internal/util'); -const assertEncoding = internalFS.assertEncoding; -const stringToFlags = internalFS.stringToFlags; const getPathFromURL = internalURL.getPathFromURL; +const { + assertEncoding, + getOptions, + modeNum, + preprocessSymlinkDestination, + realpathCacheKey, + stringToFlags, + stringToSymlinkType, + toUnixTimestamp, + statsFromValues, + statsFromPrevValues, + statValues, + Stats, + SyncWriteStream: _SyncWriteStream +} = require('internal/fs'); + Object.defineProperty(exports, 'constants', { configurable: false, enumerable: true, @@ -62,28 +75,6 @@ const isWindows = process.platform === 'win32'; const DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); const errnoException = util._errnoException; -function getOptions(options, defaultOptions) { - if (options === null || options === undefined || - typeof options === 'function') { - return defaultOptions; - } - - if (typeof options === 'string') { - defaultOptions = util._extend({}, defaultOptions); - defaultOptions.encoding = options; - options = defaultOptions; - } else if (typeof options !== 'object') { - throw new errors.TypeError('ERR_INVALID_ARG_TYPE', - 'options', - ['string', 'object'], - options); - } - - if (options.encoding !== 'buffer') - assertEncoding(options.encoding); - return options; -} - function copyObject(source) { var target = {}; for (var key in source) @@ -176,87 +167,8 @@ function isFd(path) { return (path >>> 0) === path; } -// Constructor for file stats. -function Stats( - dev, - mode, - nlink, - uid, - gid, - rdev, - blksize, - ino, - size, - blocks, - atim_msec, - mtim_msec, - ctim_msec, - birthtim_msec -) { - this.dev = dev; - this.mode = mode; - this.nlink = nlink; - this.uid = uid; - this.gid = gid; - this.rdev = rdev; - this.blksize = blksize; - this.ino = ino; - this.size = size; - this.blocks = blocks; - this.atimeMs = atim_msec; - this.mtimeMs = mtim_msec; - this.ctimeMs = ctim_msec; - this.birthtimeMs = birthtim_msec; - this.atime = new Date(atim_msec + 0.5); - this.mtime = new Date(mtim_msec + 0.5); - this.ctime = new Date(ctim_msec + 0.5); - this.birthtime = new Date(birthtim_msec + 0.5); -} fs.Stats = Stats; -Stats.prototype._checkModeProperty = function(property) { - return ((this.mode & S_IFMT) === property); -}; - -Stats.prototype.isDirectory = function() { - return this._checkModeProperty(constants.S_IFDIR); -}; - -Stats.prototype.isFile = function() { - return this._checkModeProperty(S_IFREG); -}; - -Stats.prototype.isBlockDevice = function() { - return this._checkModeProperty(constants.S_IFBLK); -}; - -Stats.prototype.isCharacterDevice = function() { - return this._checkModeProperty(constants.S_IFCHR); -}; - -Stats.prototype.isSymbolicLink = function() { - return this._checkModeProperty(S_IFLNK); -}; - -Stats.prototype.isFIFO = function() { - return this._checkModeProperty(S_IFIFO); -}; - -Stats.prototype.isSocket = function() { - return this._checkModeProperty(S_IFSOCK); -}; - -const statValues = binding.getStatValues(); - -function statsFromValues() { - return new Stats(statValues[0], statValues[1], statValues[2], statValues[3], - statValues[4], statValues[5], - statValues[6] < 0 ? undefined : statValues[6], statValues[7], - statValues[8], statValues[9] < 0 ? undefined : statValues[9], - statValues[10], statValues[11], statValues[12], - statValues[13]); -} - // Don't allow mode to accidentally be overwritten. Object.defineProperties(fs, { F_OK: { enumerable: true, value: constants.F_OK || 0 }, @@ -265,14 +177,9 @@ Object.defineProperties(fs, { X_OK: { enumerable: true, value: constants.X_OK || 0 }, }); -function handleError(val, callback) { - if (val instanceof Error) { - if (typeof callback === 'function') { - process.nextTick(callback, val); - return true; - } else throw val; - } - return false; +function handleError(val) { + if (val instanceof Error) + throw val; } fs.access = function(path, mode, callback) { @@ -283,35 +190,41 @@ fs.access = function(path, mode, callback) { throw new errors.TypeError('ERR_INVALID_CALLBACK'); } - if (handleError((path = getPathFromURL(path)), callback)) - return; + handleError(path = getPathFromURL(path)); + nullCheck(path); - if (!nullCheck(path, callback)) - return; + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + if (!Number.isInteger(mode)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'integer'); + } - mode = mode | 0; - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.access(pathModule._makeLong(path), mode, req); }; -fs.accessSync = function(path, mode) { - handleError((path = getPathFromURL(path))); +fs.accessSync = function(path, mode = fs.F_OK) { + handleError(path = getPathFromURL(path)); nullCheck(path); - if (mode === undefined) - mode = fs.F_OK; - else - mode = mode | 0; + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + if (!Number.isInteger(mode)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'integer'); + } binding.access(pathModule._makeLong(path), mode); }; fs.exists = function(path, callback) { - if (handleError((path = getPathFromURL(path)), cb)) - return; - if (!nullCheck(path, cb)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + const req = new FSReqWrap(); req.oncomplete = cb; binding.stat(pathModule._makeLong(path), req); function cb(err) { @@ -329,9 +242,9 @@ Object.defineProperty(fs.exists, internalUtil.promisify.custom, { fs.existsSync = function(path) { + handleError(path = getPathFromURL(path)); + nullCheck(path); try { - handleError((path = getPathFromURL(path))); - nullCheck(path); binding.stat(pathModule._makeLong(path)); return true; } catch (e) { @@ -343,14 +256,12 @@ fs.readFile = function(path, options, callback) { callback = maybeCallback(callback || options); options = getOptions(options, { flag: 'r' }); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) - return; + handleError(path = getPathFromURL(path)); + nullCheck(path); - var context = new ReadFileContext(callback, options.encoding); + const context = new ReadFileContext(callback, options.encoding); context.isUserFd = isFd(path); // file descriptor ownership - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.context = context; req.oncomplete = readFileAfterOpen; @@ -361,8 +272,13 @@ fs.readFile = function(path, options, callback) { return; } + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + binding.open(pathModule._makeLong(path), - stringToFlags(options.flag || 'r'), + stringToFlags(options.flag || 'r', true), 0o666, req); }; @@ -615,50 +531,65 @@ fs.readFileSync = function(path, options) { // list to make the arguments clear. fs.close = function(fd, callback) { - var req = new FSReqWrap(); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.close(fd, req); }; fs.closeSync = function(fd) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } return binding.close(fd); }; -function modeNum(m, def) { - if (typeof m === 'number') - return m; - if (typeof m === 'string') - return parseInt(m, 8); - if (def) - return modeNum(def); - return undefined; -} - fs.open = function(path, flags, mode, callback_) { - var callback = makeCallback(arguments[arguments.length - 1]); + const callback = makeCallback(arguments[arguments.length - 1]); mode = modeNum(mode, 0o666); + flags = stringToFlags(flags); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; + handleError(path = getPathFromURL(path)); + nullCheck(path); - var req = new FSReqWrap(); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); req.oncomplete = callback; - binding.open(pathModule._makeLong(path), - stringToFlags(flags), - mode, - req); + binding.open(pathModule._makeLong(path), flags, mode, req); }; fs.openSync = function(path, flags, mode) { mode = modeNum(mode, 0o666); - handleError((path = getPathFromURL(path))); + flags = stringToFlags(flags); + handleError(path = getPathFromURL(path)); nullCheck(path); - return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + return binding.open(pathModule._makeLong(path), flags, mode); }; fs.read = function(fd, buffer, offset, length, position, callback) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + if (!isUint8Array(buffer)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer', + ['Buffer', 'Uint8Array']); + } if (length === 0) { return process.nextTick(function() { callback && callback(null, 0, buffer); @@ -670,7 +601,7 @@ fs.read = function(fd, buffer, offset, length, position, callback) { callback && callback(err, bytesRead || 0, buffer); } - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = wrapper; binding.read(fd, buffer, offset, length, position, req); @@ -680,6 +611,14 @@ Object.defineProperty(fs.read, internalUtil.customPromisifyArgs, { value: ['bytesRead', 'buffer'], enumerable: false }); fs.readSync = function(fd, buffer, offset, length, position) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + if (!isUint8Array(buffer)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer', + ['Buffer', 'Uint8Array']); + } if (length === 0) { return 0; } @@ -692,25 +631,34 @@ fs.readSync = function(fd, buffer, offset, length, position) { // OR // fs.write(fd, string[, position[, encoding]], callback); fs.write = function(fd, buffer, offset, length, position, callback) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } function wrapper(err, written) { // Retain a reference to buffer so that it can't be GC'ed too soon. callback(err, written || 0, buffer); } - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = wrapper; if (isUint8Array(buffer)) { callback = maybeCallback(callback || position || length || offset); - if (typeof offset !== 'number') { + if (typeof offset !== 'number') offset = 0; - } - if (typeof length !== 'number') { + if (typeof length !== 'number') length = buffer.length - offset; - } - if (typeof position !== 'number') { + if (typeof position !== 'number') position = null; - } + + if (offset > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'offset'); + if (length > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'length'); + if (offset + length < offset) + throw new errors.RangeError('ERR_OVERFLOW'); + return binding.writeBuffer(fd, buffer, offset, length, position, req); } @@ -726,6 +674,7 @@ fs.write = function(fd, buffer, offset, length, position, callback) { length = 'utf8'; } callback = maybeCallback(position); + assertEncoding(length); return binding.writeString(fd, buffer, offset, length, req); }; @@ -737,6 +686,10 @@ Object.defineProperty(fs.write, internalUtil.customPromisifyArgs, // OR // fs.writeSync(fd, string[, position[, encoding]]); fs.writeSync = function(fd, buffer, offset, length, position) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } if (isUint8Array(buffer)) { if (position === undefined) position = null; @@ -744,26 +697,43 @@ fs.writeSync = function(fd, buffer, offset, length, position) { offset = 0; if (typeof length !== 'number') length = buffer.length - offset; + + if (offset > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'offset'); + if (length > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'length'); + if (offset + length < offset) + throw new errors.RangeError('ERR_OVERFLOW'); + return binding.writeBuffer(fd, buffer, offset, length, position); } if (typeof buffer !== 'string') buffer += ''; if (offset === undefined) offset = null; - return binding.writeString(fd, buffer, offset, length, position); + assertEncoding(length); + return binding.writeString(fd, buffer, offset, length); }; fs.rename = function(oldPath, newPath, callback) { callback = makeCallback(callback); - if (handleError((oldPath = getPathFromURL(oldPath)), callback)) - return; + handleError(oldPath = getPathFromURL(oldPath)); + handleError(newPath = getPathFromURL(newPath)); - if (handleError((newPath = getPathFromURL(newPath)), callback)) - return; + nullCheck(oldPath); + nullCheck(newPath); - if (!nullCheck(oldPath, callback)) return; - if (!nullCheck(newPath, callback)) return; - var req = new FSReqWrap(); + if (typeof oldPath !== 'string' && !Buffer.isBuffer(oldPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'oldPath', + ['string', 'Buffer', 'URL']); + } + + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'newPath', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); req.oncomplete = callback; binding.rename(pathModule._makeLong(oldPath), pathModule._makeLong(newPath), @@ -771,10 +741,21 @@ fs.rename = function(oldPath, newPath, callback) { }; fs.renameSync = function(oldPath, newPath) { - handleError((oldPath = getPathFromURL(oldPath))); - handleError((newPath = getPathFromURL(newPath))); + handleError(oldPath = getPathFromURL(oldPath)); + handleError(newPath = getPathFromURL(newPath)); nullCheck(oldPath); nullCheck(newPath); + + if (typeof oldPath !== 'string' && !Buffer.isBuffer(oldPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'oldPath', + ['string', 'Buffer', 'URL']); + } + + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'newPath', + ['string', 'Buffer', 'URL']); + } + return binding.rename(pathModule._makeLong(oldPath), pathModule._makeLong(newPath)); }; @@ -786,14 +767,14 @@ fs.truncate = function(path, len, callback) { if (typeof len === 'function') { callback = len; len = 0; - } else if (len === undefined) { + } else if (len == null) { len = 0; } callback = maybeCallback(callback); fs.open(path, 'r+', function(er, fd) { if (er) return callback(er); - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = function oncomplete(er) { fs.close(fd, function(er2) { callback(er || er2); @@ -808,11 +789,11 @@ fs.truncateSync = function(path, len) { // legacy return fs.ftruncateSync(path, len); } - if (len === undefined) { + if (len == null) { len = 0; } // allow error to be thrown, but still close fd. - var fd = fs.openSync(path, 'r+'); + const fd = fs.openSync(path, 'r+'); var ret; try { @@ -827,117 +808,192 @@ fs.ftruncate = function(fd, len, callback) { if (typeof len === 'function') { callback = len; len = 0; - } else if (len === undefined) { + } else if (len == null) { len = 0; } - var req = new FSReqWrap(); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(len) || len < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'len', + 'unsigned integer'); + } + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.ftruncate(fd, len, req); }; fs.ftruncateSync = function(fd, len) { - if (len === undefined) { + if (len == null) len = 0; + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(len) || len < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'len', + 'unsigned integer'); } return binding.ftruncate(fd, len); }; fs.rmdir = function(path, callback) { callback = maybeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); req.oncomplete = callback; binding.rmdir(pathModule._makeLong(path), req); }; fs.rmdirSync = function(path) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + return binding.rmdir(pathModule._makeLong(path)); }; fs.fdatasync = function(fd, callback) { - var req = new FSReqWrap(); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fdatasync(fd, req); }; fs.fdatasyncSync = function(fd) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + return binding.fdatasync(fd); }; fs.fsync = function(fd, callback) { - var req = new FSReqWrap(); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fsync(fd, req); }; fs.fsyncSync = function(fd) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } return binding.fsync(fd); }; fs.mkdir = function(path, mode, callback) { if (typeof mode === 'function') callback = mode; callback = makeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + mode = modeNum(0o777); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); req.oncomplete = callback; - binding.mkdir(pathModule._makeLong(path), - modeNum(mode, 0o777), - req); + binding.mkdir(pathModule._makeLong(path), mode, req); }; fs.mkdirSync = function(path, mode) { - handleError((path = getPathFromURL(path))); + mode = modeNum(0o777); + handleError(path = getPathFromURL(path)); nullCheck(path); - return binding.mkdir(pathModule._makeLong(path), - modeNum(mode, 0o777)); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + return binding.mkdir(pathModule._makeLong(path), mode); }; fs.readdir = function(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); req.oncomplete = callback; binding.readdir(pathModule._makeLong(path), options.encoding, req); }; fs.readdirSync = function(path, options) { options = getOptions(options, {}); - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + return binding.readdir(pathModule._makeLong(path), options.encoding); }; fs.fstat = function(fd, callback) { - var req = new FSReqWrap(); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); req.oncomplete = makeStatsCallback(callback); binding.fstat(fd, req); }; fs.lstat = function(path, callback) { callback = makeStatsCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.lstat(pathModule._makeLong(path), req); }; fs.stat = function(path, callback) { callback = makeStatsCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.stat(pathModule._makeLong(path), req); }; @@ -948,14 +1004,14 @@ fs.fstatSync = function(fd) { }; fs.lstatSync = function(path) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); binding.lstat(pathModule._makeLong(path)); return statsFromValues(); }; fs.statSync = function(path) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); binding.stat(pathModule._makeLong(path)); return statsFromValues(); @@ -964,83 +1020,101 @@ fs.statSync = function(path) { fs.readlink = function(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.readlink(pathModule._makeLong(path), options.encoding, req); }; fs.readlinkSync = function(path, options) { options = getOptions(options, {}); - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } return binding.readlink(pathModule._makeLong(path), options.encoding); }; -function preprocessSymlinkDestination(path, type, linkPath) { - if (!isWindows) { - // No preprocessing is needed on Unix. - return path; - } else if (type === 'junction') { - // Junctions paths need to be absolute and \\?\-prefixed. - // A relative target is relative to the link's parent directory. - path = pathModule.resolve(linkPath, '..', path); - return pathModule._makeLong(path); - } else { - // Windows symlinks don't tolerate forward slashes. - return ('' + path).replace(/\//g, '\\'); +fs.symlink = function(target, path, type, callback) { + if (typeof type === 'function') { + callback = type; + type = 'file'; } -} + const _type = stringToSymlinkType(type); -fs.symlink = function(target, path, type_, callback_) { - var type = (typeof type_ === 'string' ? type_ : null); - var callback = makeCallback(arguments[arguments.length - 1]); + handleError(target = getPathFromURL(target)); + handleError(path = getPathFromURL(path)); - if (handleError((target = getPathFromURL(target)), callback)) - return; + nullCheck(target); + nullCheck(path); - if (handleError((path = getPathFromURL(path)), callback)) - return; + if (typeof target !== 'string' && !Buffer.isBuffer(target)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'target', + ['string', 'Buffer', 'URL']); + } - if (!nullCheck(target, callback)) return; - if (!nullCheck(path, callback)) return; + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } - var req = new FSReqWrap(); - req.oncomplete = callback; + const req = new FSReqWrap(); + req.oncomplete = makeCallback(callback); binding.symlink(preprocessSymlinkDestination(target, type, path), pathModule._makeLong(path), - type, + _type, req); }; -fs.symlinkSync = function(target, path, type) { - type = (typeof type === 'string' ? type : null); - handleError((target = getPathFromURL(target))); - handleError((path = getPathFromURL(path))); +fs.symlinkSync = function(target, path, type = 'file') { + const _type = stringToSymlinkType(type); + handleError(target = getPathFromURL(target)); + handleError(path = getPathFromURL(path)); nullCheck(target); nullCheck(path); + if (typeof target !== 'string' && !Buffer.isBuffer(target)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'target', + ['string', 'Buffer', 'URL']); + } + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + return binding.symlink(preprocessSymlinkDestination(target, type, path), pathModule._makeLong(path), - type); + _type); }; fs.link = function(existingPath, newPath, callback) { callback = makeCallback(callback); - if (handleError((existingPath = getPathFromURL(existingPath)), callback)) - return; + handleError(existingPath = getPathFromURL(existingPath)); + handleError(newPath = getPathFromURL(newPath)); - if (handleError((newPath = getPathFromURL(newPath)), callback)) - return; + nullCheck(existingPath); + nullCheck(newPath); - if (!nullCheck(existingPath, callback)) return; - if (!nullCheck(newPath, callback)) return; + if (typeof existingPath !== 'string' && !Buffer.isBuffer(existingPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } - var req = new FSReqWrap(); + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.link(pathModule._makeLong(existingPath), @@ -1049,38 +1123,67 @@ fs.link = function(existingPath, newPath, callback) { }; fs.linkSync = function(existingPath, newPath) { - handleError((existingPath = getPathFromURL(existingPath))); - handleError((newPath = getPathFromURL(newPath))); + handleError(existingPath = getPathFromURL(existingPath)); + handleError(newPath = getPathFromURL(newPath)); nullCheck(existingPath); nullCheck(newPath); + if (typeof existingPath !== 'string' && !Buffer.isBuffer(existingPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } + + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } return binding.link(pathModule._makeLong(existingPath), pathModule._makeLong(newPath)); }; fs.unlink = function(path, callback) { callback = makeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.unlink(pathModule._makeLong(path), req); }; fs.unlinkSync = function(path) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } return binding.unlink(pathModule._makeLong(path)); }; fs.fchmod = function(fd, mode, callback) { - var req = new FSReqWrap(); + mode = modeNum(mode); + + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); - binding.fchmod(fd, modeNum(mode), req); + binding.fchmod(fd, mode, req); }; fs.fchmodSync = function(fd, mode) { - return binding.fchmod(fd, modeNum(mode)); + mode = modeNum(mode); + + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + return binding.fchmod(fd, mode); }; if (constants.O_SYMLINK !== undefined) { @@ -1102,7 +1205,7 @@ if (constants.O_SYMLINK !== undefined) { }; fs.lchmodSync = function(path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); + const fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); // Prefer to return the chmod error, if one occurs, // but still try to close, and report closing errors if they occur. @@ -1123,20 +1226,39 @@ if (constants.O_SYMLINK !== undefined) { fs.chmod = function(path, mode, callback) { callback = makeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + mode = modeNum(mode); + if (!Number.isInteger(mode) || mode < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', + 'unsigned integer'); + } + + const req = new FSReqWrap(); req.oncomplete = callback; - binding.chmod(pathModule._makeLong(path), - modeNum(mode), - req); + binding.chmod(pathModule._makeLong(path), mode, req); }; fs.chmodSync = function(path, mode) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); - return binding.chmod(pathModule._makeLong(path), modeNum(mode)); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + mode = modeNum(mode); + if (!Number.isInteger(mode) || mode < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', + 'unsigned integer'); + } + + return binding.chmod(pathModule._makeLong(path), mode); }; if (constants.O_SYMLINK !== undefined) { @@ -1158,62 +1280,99 @@ if (constants.O_SYMLINK !== undefined) { } fs.fchown = function(fd, uid, gid, callback) { - var req = new FSReqWrap(); + + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(uid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', 'integer'); + } + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); + } + + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchown(fd, uid, gid, req); }; fs.fchownSync = function(fd, uid, gid) { + + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(uid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', 'integer'); + } + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); + } + return binding.fchown(fd, uid, gid); }; fs.chown = function(path, uid, gid, callback) { callback = makeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + if (!Number.isInteger(uid) || uid < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', + 'unsigned integer'); + } + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); + } + + const req = new FSReqWrap(); req.oncomplete = callback; binding.chown(pathModule._makeLong(path), uid, gid, req); }; fs.chownSync = function(path, uid, gid) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); - return binding.chown(pathModule._makeLong(path), uid, gid); -}; -// converts Date or number to a fractional UNIX timestamp -function toUnixTimestamp(time) { - // eslint-disable-next-line eqeqeq - if (typeof time === 'string' && +time == time) { - return +time; + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); } - if (Number.isFinite(time)) { - if (time < 0) { - return Date.now() / 1000; - } - return time; + + if (!Number.isInteger(uid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', 'integer'); } - if (util.isDate(time)) { - // convert to 123.456 UNIX timestamp - return time.getTime() / 1000; + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); } - throw new errors.Error('ERR_INVALID_ARG_TYPE', - 'time', - ['Date', 'time in seconds'], - time); -} + + return binding.chown(pathModule._makeLong(path), uid, gid); +}; // exported for unit tests, not for public consumption fs._toUnixTimestamp = toUnixTimestamp; fs.utimes = function(path, atime, mtime, callback) { callback = makeCallback(callback); - if (handleError((path = getPathFromURL(path)), callback)) - return; - if (!nullCheck(path, callback)) return; - var req = new FSReqWrap(); + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); req.oncomplete = callback; binding.utimes(pathModule._makeLong(path), toUnixTimestamp(atime), @@ -1222,22 +1381,36 @@ fs.utimes = function(path, atime, mtime, callback) { }; fs.utimesSync = function(path, atime, mtime) { - handleError((path = getPathFromURL(path))); + handleError(path = getPathFromURL(path)); nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); binding.utimes(pathModule._makeLong(path), atime, mtime); }; fs.futimes = function(fd, atime, mtime, callback) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.futimes(fd, atime, mtime, req); }; fs.futimesSync = function(fd, atime, mtime) { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); binding.futimes(fd, atime, mtime); @@ -1357,20 +1530,19 @@ fs.appendFileSync = function(path, data, options) { function FSWatcher() { EventEmitter.call(this); - var self = this; this._handle = new FSEvent(); this._handle.owner = this; - this._handle.onchange = function(status, eventType, filename) { + this._handle.onchange = (status, eventType, filename) => { if (status < 0) { - self._handle.close(); + this._handle.close(); const error = !filename ? errnoException(status, 'Error watching file for changes:') : errnoException(status, `Error watching file ${filename} for changes:`); error.filename = filename; - self.emit('error', error); + this.emit('error', error); } else { - self.emit('change', eventType, filename); + this.emit('change', eventType, filename); } }; } @@ -1380,7 +1552,7 @@ FSWatcher.prototype.start = function(filename, persistent, recursive, encoding) { - handleError((filename = getPathFromURL(filename))); + handleError(filename = getPathFromURL(filename)); nullCheck(filename); var err = this._handle.start(pathModule._makeLong(filename), persistent, @@ -1399,7 +1571,7 @@ FSWatcher.prototype.close = function() { }; fs.watch = function(filename, options, listener) { - handleError((filename = getPathFromURL(filename))); + handleError(filename = getPathFromURL(filename)); nullCheck(filename); if (typeof options === 'function') { @@ -1433,15 +1605,6 @@ function emitStop(self) { self.emit('stop'); } -function statsFromPrevValues() { - return new Stats(statValues[14], statValues[15], statValues[16], - statValues[17], statValues[18], statValues[19], - statValues[20] < 0 ? undefined : statValues[20], - statValues[21], statValues[22], - statValues[23] < 0 ? undefined : statValues[23], - statValues[24], statValues[25], statValues[26], - statValues[27]); -} function StatWatcher() { EventEmitter.call(this); @@ -1469,7 +1632,7 @@ util.inherits(StatWatcher, EventEmitter); StatWatcher.prototype.start = function(filename, persistent, interval) { - handleError((filename = getPathFromURL(filename))); + handleError(filename = getPathFromURL(filename)); nullCheck(filename); this._handle.start(pathModule._makeLong(filename), persistent, interval); }; @@ -1483,7 +1646,7 @@ StatWatcher.prototype.stop = function() { const statWatchers = new Map(); fs.watchFile = function(filename, options, listener) { - handleError((filename = getPathFromURL(filename))); + handleError(filename = getPathFromURL(filename)); nullCheck(filename); filename = pathModule.resolve(filename); var stat; @@ -1523,10 +1686,10 @@ fs.watchFile = function(filename, options, listener) { }; fs.unwatchFile = function(filename, listener) { - handleError((filename = getPathFromURL(filename))); + handleError(filename = getPathFromURL(filename)); nullCheck(filename); filename = pathModule.resolve(filename); - var stat = statWatchers.get(filename); + const stat = statWatchers.get(filename); if (stat === undefined) return; @@ -1601,7 +1764,7 @@ fs.realpathSync = function realpathSync(p, options) { nullCheck(p); p = pathModule.resolve(p); - const cache = options[internalFS.realpathCacheKey]; + const cache = options[realpathCacheKey]; const maybeCachedResult = cache && cache.get(p); if (maybeCachedResult) { return maybeCachedResult; @@ -1722,13 +1885,11 @@ fs.realpath = function realpath(p, options, callback) { else options = getOptions(options, emptyObj); if (typeof p !== 'string') { - if (handleError((p = getPathFromURL(p)), callback)) - return; + handleError(p = getPathFromURL(p)); if (typeof p !== 'string') p += ''; } - if (!nullCheck(p, callback)) - return; + nullCheck(p); p = pathModule.resolve(p); const seenLinks = Object.create(null); @@ -1860,11 +2021,9 @@ fs.mkdtemp = function(prefix, options, callback) { 'string', prefix); } - if (!nullCheck(prefix, callback)) { - return; - } + nullCheck(prefix); - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = callback; binding.mkdtemp(prefix + 'XXXXXX', options.encoding, req); @@ -1900,19 +2059,23 @@ fs.copyFile = function(src, dest, flags, callback) { src = getPathFromURL(src); - if (handleError(src, callback)) - return; + handleError(src); + nullCheck(src); - if (!nullCheck(src, callback)) - return; + if (typeof src !== 'string' && !Buffer.isBuffer(src)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } dest = getPathFromURL(dest); - if (handleError(dest, callback)) - return; + handleError(dest); + nullCheck(dest); - if (!nullCheck(dest, callback)) - return; + if (typeof dest !== 'string' && !Buffer.isBuffer(dest)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } src = pathModule._makeLong(src); dest = pathModule._makeLong(dest); @@ -1928,10 +2091,20 @@ fs.copyFileSync = function(src, dest, flags) { handleError(src); nullCheck(src); + if (typeof src !== 'string' && !Buffer.isBuffer(src)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } + dest = getPathFromURL(dest); handleError(dest); nullCheck(dest); + if (typeof dest !== 'string' && !Buffer.isBuffer(dest)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } + src = pathModule._makeLong(src); dest = pathModule._makeLong(dest); flags = flags | 0; @@ -1965,7 +2138,7 @@ function ReadStream(path, options) { Readable.call(this, options); - handleError((this.path = getPathFromURL(path))); + handleError(this.path = getPathFromURL(path)); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'r' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; @@ -2142,7 +2315,7 @@ function WriteStream(path, options) { Writable.call(this, options); - handleError((this.path = getPathFromURL(path))); + handleError(this.path = getPathFromURL(path)); this.fd = options.fd === undefined ? null : options.fd; this.flags = options.flags === undefined ? 'w' : options.flags; this.mode = options.mode === undefined ? 0o666 : options.mode; @@ -2283,7 +2456,7 @@ WriteStream.prototype.destroySoon = WriteStream.prototype.end; // SyncWriteStream is internal. DO NOT USE. // This undocumented API was never intended to be made public. -var SyncWriteStream = internalFS.SyncWriteStream; +var SyncWriteStream = _SyncWriteStream; Object.defineProperty(fs, 'SyncWriteStream', { configurable: true, get: internalUtil.deprecate(() => SyncWriteStream, @@ -2291,3 +2464,11 @@ Object.defineProperty(fs, 'SyncWriteStream', { set: internalUtil.deprecate((val) => { SyncWriteStream = val; }, 'fs.SyncWriteStream is deprecated.', 'DEP0061') }); + +Object.defineProperty(fs, 'async', { + enumerable: true, + configurable: true, + get() { + return require('internal/async/fs'); + } +}); diff --git a/lib/internal/async/fs.js b/lib/internal/async/fs.js new file mode 100644 index 00000000000000..0f110d2b2a5c58 --- /dev/null +++ b/lib/internal/async/fs.js @@ -0,0 +1,947 @@ +'use strict'; + +process.emitWarning( + 'The promisified fs module is an experimental API.', + 'ExperimentalWarning' +); + +const { + F_OK, + O_SYMLINK, + O_WRONLY +} = process.binding('constants').fs; + +const errors = require('internal/errors'); + +const { + access: _access, + chmod: _chmod, + chown: _chown, + close: _close, + copyFile: _copyFile, + fchmod: _fchmod, + fchown: _fchown, + fdatasync: _fdatasync, + fstat: _fstat, + fsync: _fsync, + ftruncate: _ftruncate, + futimes: _futimes, + link: _link, + lstat: _lstat, + mkdir: _mkdir, + mkdtemp: _mkdtemp, + open: _open, + read: _read, + readdir: _readdir, + readlink: _readlink, + realpath: _realpath, + rename: _rename, + rmdir: _rmdir, + stat: _stat, + symlink: _symlink, + unlink: _unlink, + utimes: _utimes, + writeBuffer: _writeBuffer, + writeString: _writeString, + FSReqWrap +} = process.binding('fs'); + +const { + assertEncoding, + getOptions, + modeNum, + nullCheck, + preprocessSymlinkDestination, + statsFromValues, + stringToFlags, + stringToSymlinkType, + toUnixTimestamp +} = require('internal/fs'); + +const { + createPromise, + isUint8Array, + promiseResolve, + promiseReject +} = process.binding('util'); + +const { + _makeLong +} = require('path'); + +const { + getPathFromURL +} = require('internal/url'); + +const { + Buffer, + kMaxLength +} = require('buffer'); + +const kReadFileBufferLength = 8 * 1024; + +function handleError(val) { + if (val instanceof Error) + throw val; +} + +function isFd(path) { + return (path >>> 0) === path; +} + +function access(path, mode = F_OK) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + if (!Number.isInteger(mode)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'integer'); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + _access(_makeLong(path), mode, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function chmod(path, mode) { + const promise = createPromise(); + try { + mode = modeNum(mode); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + if (!Number.isInteger(mode) || mode < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', + 'unsigned integer'); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _chmod(_makeLong(path), mode, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function chown(path, uid, gid) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + if (!Number.isInteger(uid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', 'integer'); + } + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _chown(_makeLong(path), uid, gid, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function close(fd) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + _close(fd, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function copyFile(src, dest, flags = 0) { + const promise = createPromise(); + try { + src = getPathFromURL(src); + handleError(src); + nullCheck(src); + + if (typeof src !== 'string' && !Buffer.isBuffer(src)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } + + dest = getPathFromURL(dest); + handleError(dest); + nullCheck(dest); + + if (typeof dest !== 'string' && !Buffer.isBuffer(dest)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } + + src = _makeLong(src); + dest = _makeLong(dest); + flags = flags | 0; + const req = new FSReqWrap(); + req.oncomplete = promise; + _copyFile(src, dest, flags, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function fchmod(fd, mode) { + const promise = createPromise(); + try { + mode = modeNum(mode); + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + _fchmod(fd, mode, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function fchown(fd, uid, gid) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(uid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'uid', 'integer'); + } + + if (!Number.isInteger(gid)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'gid', 'integer'); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _fchown(fd, uid, gid, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function fdatasync(fd) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _fdatasync(fd, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function fsync(fd) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + _fsync(fd, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function ftruncate(fd, len) { + const promise = createPromise(); + try { + if (len == null) + len = 0; + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + if (!Number.isInteger(len) || len < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'len', + 'unsigned integer'); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _ftruncate(fd, len, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function futimes(fd, atime, mtime) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + + atime = toUnixTimestamp(atime); + mtime = toUnixTimestamp(mtime); + const req = new FSReqWrap(); + req.oncomplete = promise; + _futimes(fd, atime, mtime, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function open(path, flags, mode) { + const promise = createPromise(); + try { + mode = modeNum(mode, 0o666); + flags = stringToFlags(flags); + + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + + _open(_makeLong(path), flags, mode, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function link(existingPath, newPath) { + const promise = createPromise(); + try { + handleError(existingPath = getPathFromURL(existingPath)); + handleError(newPath = getPathFromURL(newPath)); + + nullCheck(existingPath); + nullCheck(newPath); + + if (typeof existingPath !== 'string' && !Buffer.isBuffer(existingPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'src', + ['string', 'Buffer', 'URL']); + } + + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'dest', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + + _link(_makeLong(existingPath), + _makeLong(newPath), + req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function mkdir(path, mode) { + const promise = createPromise(); + try { + mode = modeNum(mode, 0o777); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _mkdir(_makeLong(path), mode, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function mkdtemp(prefix, options) { + const promise = createPromise(); + try { + options = getOptions(options, {}); + if (!prefix || typeof prefix !== 'string') { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'prefix', + 'string'); + } + nullCheck(prefix); + + const req = new FSReqWrap(); + req.oncomplete = promise; + _mkdtemp(`${prefix}XXXXXX`, options.encoding, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function readdir(path, options) { + const promise = createPromise(); + try { + options = getOptions(options, {}); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _readdir(_makeLong(path), options.encoding, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function readlink(path, options) { + const promise = createPromise(); + try { + options = getOptions(options, {}); + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _readlink(_makeLong(path), options.encoding, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function rename(oldPath, newPath) { + const promise = createPromise(); + try { + handleError(oldPath = getPathFromURL(oldPath)); + handleError(newPath = getPathFromURL(newPath)); + + nullCheck(oldPath); + nullCheck(newPath); + + if (typeof oldPath !== 'string' && !Buffer.isBuffer(oldPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'oldPath', + ['string', 'Buffer', 'URL']); + } + + if (typeof newPath !== 'string' && !Buffer.isBuffer(newPath)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'newPath', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + + _rename(_makeLong(oldPath), + _makeLong(newPath), + req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function rmdir(path) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _rmdir(_makeLong(path), req); + } catch (err) { + promiseReject(err); + } + return promise; +} + +function symlink(target, path, type = 'file') { + const promise = createPromise(); + try { + const _type = stringToSymlinkType(type); + + handleError(target = getPathFromURL(target)); + handleError(path = getPathFromURL(path)); + + nullCheck(target); + nullCheck(path); + + if (typeof target !== 'string' && !Buffer.isBuffer(target)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'target', + ['string', 'Buffer', 'URL']); + } + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + + _symlink(preprocessSymlinkDestination(target, type, path), + _makeLong(path), + _type, + req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function unlink(path) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const req = new FSReqWrap(); + req.oncomplete = promise; + _unlink(_makeLong(path), req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function utimes(path, atime, mtime) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + _utimes(_makeLong(path), + toUnixTimestamp(atime), + toUnixTimestamp(mtime), + req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function writeBuffer(fd, buffer, offset, length, position, req) { + if (typeof offset !== 'number') + offset = 0; + if (typeof length !== 'number') + length = buffer.length - offset; + if (typeof position !== 'number') + position = null; + + if (offset > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'offset'); + if (length > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'length'); + if (offset + length < offset) + throw new errors.RangeError('ERR_OVERFLOW'); + + _writeBuffer(fd, buffer, offset, length, position, req); +} + +function writeString(fd, str, position, encoding, req) { + assertEncoding(encoding); + if (typeof position !== 'number') + position = null; + _writeString(fd, `${str}`, position, encoding || 'utf8', req); +} + +function write(fd, buffer, offsetOrPosition, lengthOrEncoding, position) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); + req.oncomplete = promise; + req.buffer = buffer; // Retain a reference to buffer + + if (isUint8Array(buffer)) { + writeBuffer(fd, buffer, offsetOrPosition, + lengthOrEncoding, position, req); + } else { + writeString(fd, buffer, offsetOrPosition, lengthOrEncoding, req); + } + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +async function writeAll(fd, isUserFd, buffer, offset, length, position) { + try { + const written = await write(fd, buffer, offset, length, position); + if (written === length) { + if (!isUserFd) + await close(fd); + } else { + offset += written; + length -= written; + if (position !== null) + position += written; + await writeAll(fd, isUserFd, buffer, offset, length, position); + } + return written; + } catch (err) { + if (!isUserFd) + await close(fd); + throw err; + } +} + +async function writeFd(fd, data, isUserFd, options, flag) { + const buffer = isUint8Array(data) ? + data : Buffer.from(`${data}`, options.encoding || 'utf8'); + const position = /a/.test(flag) ? null : 0; + return await writeAll(fd, isUserFd, buffer, 0, buffer.length, position); +} + +async function writeFile(path, data, options) { + options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); + const flag = options.flag || 'w'; + + if (isFd(path)) { + return await writeFd(path, data, true, options, flag); + } else { + const fd = await open(path, flag, options, options.mode); + return await writeFd(fd, data, false, options, flag); + } +} + +async function appendFile(path, data, options) { + options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); + options = Object.assign({}, options); + if (!options.flag || isFd(path)) + options.flag = 'a'; + return await writeFile(path, data, options); +} + +async function truncate(path, len = 0) { + if (typeof path === 'number') + return await ftruncate(path, len); + + const fd = await open(path, 'r+'); + try { + await ftruncate(fd, len); + } finally { + await close(fd); + } +} + +function read(fd, buffer, offset, length, position) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + if (!isUint8Array(buffer)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer', + ['Buffer', 'Uint8Array']); + } + if (length === 0) { + promiseResolve(promise, { length: 0, buffer }); + return promise; + } + + function wrapper(err, len) { + if (err) { + promiseReject(promise, err); + return; + } + promiseResolve(promise, { length: len, buffer }); + } + + if (typeof offset !== 'number') + offset = 0; + if (typeof length !== 'number') + length = buffer.length; + if (typeof position !== 'number') + position = 0; + + if (offset > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'offset'); + if (length > buffer.length) + throw new errors.RangeError('ERR_OUTOFBOUNDS', 'length'); + + const req = new FSReqWrap(); + req.oncomplete = wrapper; + + _read(fd, buffer, offset, length, position, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function resolveWithStats(promise) { + return (err) => { + if (err) { + promiseReject(promise, err); + return; + } + promiseResolve(promise, statsFromValues()); + }; +} + +function fstat(fd) { + const promise = createPromise(); + try { + if (!Number.isInteger(fd) || fd < 0) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'fd', + 'unsigned integer'); + } + const req = new FSReqWrap(); + req.oncomplete = resolveWithStats(promise); + _fstat(fd, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function lstat(path) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); + req.oncomplete = resolveWithStats(promise); + _lstat(_makeLong(path), req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +function stat(path) { + const promise = createPromise(); + try { + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + const req = new FSReqWrap(); + req.oncomplete = resolveWithStats(promise); + _stat(_makeLong(path), req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +async function lchmod(path, mode) { + const fd = await open(path, O_WRONLY | O_SYMLINK); + try { + await fchmod(fd, mode); + } finally { + await close(fd); + } +} + +async function lchown(path, uid, gid) { + const fd = await open(path, O_WRONLY | O_SYMLINK); + try { + await fchown(fd, uid, gid); + } finally { + await close(fd); + } +} + +// This uses the fast libuv implementation, not the legacy one that +// is in fs that we had to revert back to. +function realpath(path, options) { + options = getOptions(options, { encoding: 'utf8' }); + + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const promise = createPromise(); + const req = new FSReqWrap(); + req.oncomplete = promise; + try { + _realpath(_makeLong(path), options.encoding, req); + } catch (err) { + promiseReject(promise, err); + } + return promise; +} + +async function readFile(path, options) { + options = getOptions(options, { flag: 'r' }); + + handleError(path = getPathFromURL(path)); + nullCheck(path); + if (typeof path !== 'string' && !Buffer.isBuffer(path)) { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'path', + ['string', 'Buffer', 'URL']); + } + + const fd = isFd(path) ? + path : + await open(_makeLong(path), + stringToFlags(options.flag || 'r'), + 0o666); + + try { + const stat = await fstat(fd); + + const size = stat.isFile() ? stat.size : 0; + + if (size > kMaxLength) { + throw new RangeError('File size is greater than possible Buffer: ' + + `0x${kMaxLength.toString(16)} bytes`); + } + + // Ok, so if size it known, allocate a buffer that big and read once. + // Otherwise, iterate until there is no more data to read, concat + // the buffers, and done! + let buffer; + if (size > 0) { + buffer = Buffer.allocUnsafeSlow(size); + await read(fd, buffer, 0, size); + } else { + // The size is not known, just read until there are no more bytes + const buffers = []; + let bytesRead = 0; + do { + const buf = Buffer.allocUnsafeSlow(kReadFileBufferLength); + bytesRead = + (await read(fd, buf, 0, kReadFileBufferLength, null)).length; + buffers.push(buf.slice(0, bytesRead)); + } while (bytesRead > 0); + buffer = Buffer.concat(buffers); + } + + if (options.encoding) + return buffer.toString(options.encoding); + + return buffer; + } finally { + await close(fd); + } +} + +module.exports = { + access, + appendFile, + chmod, + chown, + close, + copyFile, + fchmod, + fchown, + fdatasync, + fstat, + fsync, + ftruncate, + futimes, + open, + link, + lstat, + mkdir, + mkdtemp, + read, + readdir, + readFile, + readlink, + realpath, + rename, + rmdir, + stat, + symlink, + truncate, + unlink, + utimes, + write, + writeFile +}; + +if (O_SYMLINK !== undefined) { + module.exports.lchmod = lchmod; + module.exports.lchown = lchown; +} diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 261378e2b1fba1..ac0ce95ee22b8b 100755 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -236,6 +236,7 @@ E('ERR_INVALID_PROTOCOL', (protocol, expectedProtocol) => `Protocol "${protocol}" not supported. Expected "${expectedProtocol}"`); E('ERR_INVALID_REPL_EVAL_CONFIG', 'Cannot specify both "breakEvalOnSigint" and "eval" for REPL'); +E('ERR_INVALID_SYMLINK_TYPE', 'Invalid symlink type: %s'); E('ERR_INVALID_SYNC_FORK_INPUT', 'Asynchronous forks do not support Buffer, Uint8Array or string input: %s'); E('ERR_INVALID_THIS', 'Value of "this" must be of type %s'); @@ -259,7 +260,9 @@ E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support'); E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); E('ERR_OUT_OF_RANGE', 'The "%s" argument is out of range'); +E('ERR_OUTOFBOUNDS', 'The "%s" argument is out of bounds'); E('ERR_OUTOFMEMORY', 'Out of memory'); +E('ERR_OVERFLOW', 'Overflow error'); E('ERR_PARSE_HISTORY_DATA', 'Could not parse history data in %s'); E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s'); E('ERR_SERVER_ALREADY_LISTEN', diff --git a/lib/internal/fs.js b/lib/internal/fs.js index b8f710f04b32cd..08fdbb2184adf6 100644 --- a/lib/internal/fs.js +++ b/lib/internal/fs.js @@ -5,6 +5,7 @@ const Writable = require('stream').Writable; const errors = require('internal/errors'); const fs = require('fs'); const util = require('util'); +const pathModule = require('path'); const { O_APPEND, @@ -14,17 +15,56 @@ const { O_RDWR, O_SYNC, O_TRUNC, - O_WRONLY + O_WRONLY, + S_IFDIR, + S_IFBLK, + S_IFREG, + S_IFCHR, + S_IFMT, + S_IFLNK, + S_IFIFO, + S_IFSOCK, + UV_FS_SYMLINK_DIR, + UV_FS_SYMLINK_JUNCTION } = process.binding('constants').fs; +const { + getStatValues +} = process.binding('fs'); + +const statValues = getStatValues(); + +const isWindows = process.platform === 'win32'; + function assertEncoding(encoding) { if (encoding && !Buffer.isEncoding(encoding)) { throw new errors.TypeError('ERR_INVALID_OPT_VALUE_ENCODING', encoding); } } -function stringToFlags(flags) { - if (typeof flags === 'number') { +function stringToSymlinkType(type = 'file') { + if (type != null && typeof type !== 'string') { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'type', 'string'); + } + type = type || 'file'; + var flags = 0; + switch (type) { + case 'dir': + flags |= UV_FS_SYMLINK_DIR; + break; + case 'junction': + flags |= UV_FS_SYMLINK_JUNCTION; + break; + case 'file': + break; + default: + throw new errors.TypeError('ERR_INVALID_SYMLINK_TYPE', type); + } + return flags; +} + +function stringToFlags(flags, opt = false) { + if (Number.isSafeInteger(flags) && flags >= 0) { return flags; } @@ -53,7 +93,12 @@ function stringToFlags(flags) { case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL; } - throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); + if (opt) { + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); + } else { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'flags', + ['string', 'unsigned integer']); + } } // Temporary hack for process.stdout and process.stderr when piped to files. @@ -95,9 +140,191 @@ SyncWriteStream.prototype.destroy = function() { return true; }; +function nullCheck(path, callback) { + if (('' + path).indexOf('\u0000') !== -1) { + const er = new errors.Error('ERR_INVALID_ARG_TYPE', + 'path', + 'string without null bytes', + path); + + if (typeof callback !== 'function') + throw er; + process.nextTick(callback, er); + return false; + } + return true; +} + +function modeNum(m, def) { + if (typeof m === 'number') + return m; + if (typeof m === 'string') + return parseInt(m, 8); + if (def) + return modeNum(def); + return undefined; +} + +// converts Date or number to a fractional UNIX timestamp +function toUnixTimestamp(time) { + // eslint-disable-next-line eqeqeq + if (typeof time === 'string' && +time == time) { + return +time; + } + if (Number.isFinite(time)) { + if (time < 0) { + return Date.now() / 1000; + } + return time; + } + if (util.isDate(time)) { + // convert to 123.456 UNIX timestamp + return time.getTime() / 1000; + } + throw new errors.Error('ERR_INVALID_ARG_TYPE', + 'time', + ['Date', 'time in seconds'], + time); +} + +function getOptions(options, defaultOptions) { + if (options === null || options === undefined || + typeof options === 'function') { + return defaultOptions; + } + + if (typeof options === 'string') { + defaultOptions = util._extend({}, defaultOptions); + defaultOptions.encoding = options; + options = defaultOptions; + } else if (typeof options !== 'object') { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'options', + ['string', 'object'], + options); + } + + if (options.encoding !== 'buffer') + assertEncoding(options.encoding); + return options; +} + +function preprocessSymlinkDestination(path, type, linkPath) { + if (!isWindows) { + // No preprocessing is needed on Unix. + return path; + } else if (type === 'junction') { + // Junctions paths need to be absolute and \\?\-prefixed. + // A relative target is relative to the link's parent directory. + path = pathModule.resolve(linkPath, '..', path); + return pathModule._makeLong(path); + } else { + // Windows symlinks don't tolerate forward slashes. + return ('' + path).replace(/\//g, '\\'); + } +} + +// Constructor for file stats. +function Stats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atim_msec, + mtim_msec, + ctim_msec, + birthtim_msec +) { + this.dev = dev; + this.mode = mode; + this.nlink = nlink; + this.uid = uid; + this.gid = gid; + this.rdev = rdev; + this.blksize = blksize; + this.ino = ino; + this.size = size; + this.blocks = blocks; + this.atimeMs = atim_msec; + this.mtimeMs = mtim_msec; + this.ctimeMs = ctim_msec; + this.birthtimeMs = birthtim_msec; + this.atime = new Date(atim_msec + 0.5); + this.mtime = new Date(mtim_msec + 0.5); + this.ctime = new Date(ctim_msec + 0.5); + this.birthtime = new Date(birthtim_msec + 0.5); +} + +Stats.prototype._checkModeProperty = function(property) { + return ((this.mode & S_IFMT) === property); +}; + +Stats.prototype.isDirectory = function() { + return this._checkModeProperty(S_IFDIR); +}; + +Stats.prototype.isFile = function() { + return this._checkModeProperty(S_IFREG); +}; + +Stats.prototype.isBlockDevice = function() { + return this._checkModeProperty(S_IFBLK); +}; + +Stats.prototype.isCharacterDevice = function() { + return this._checkModeProperty(S_IFCHR); +}; + +Stats.prototype.isSymbolicLink = function() { + return this._checkModeProperty(S_IFLNK); +}; + +Stats.prototype.isFIFO = function() { + return this._checkModeProperty(S_IFIFO); +}; + +Stats.prototype.isSocket = function() { + return this._checkModeProperty(S_IFSOCK); +}; + +function statsFromValues() { + return new Stats(statValues[0], statValues[1], statValues[2], statValues[3], + statValues[4], statValues[5], + statValues[6] < 0 ? undefined : statValues[6], statValues[7], + statValues[8], statValues[9] < 0 ? undefined : statValues[9], + statValues[10], statValues[11], statValues[12], + statValues[13]); +} + +function statsFromPrevValues() { + return new Stats(statValues[14], statValues[15], statValues[16], + statValues[17], statValues[18], statValues[19], + statValues[20] < 0 ? undefined : statValues[20], + statValues[21], statValues[22], + statValues[23] < 0 ? undefined : statValues[23], + statValues[24], statValues[25], statValues[26], + statValues[27]); +} + module.exports = { assertEncoding, + getOptions, + modeNum, + nullCheck, + preprocessSymlinkDestination, + statsFromValues, + statsFromPrevValues, + statValues, stringToFlags, + stringToSymlinkType, + Stats, SyncWriteStream, + toUnixTimestamp, realpathCacheKey: Symbol('realpathCacheKey') }; diff --git a/lib/internal/repl.js b/lib/internal/repl.js index 1564dfd3700e00..8ebca3d7a61380 100644 --- a/lib/internal/repl.js +++ b/lib/internal/repl.js @@ -100,7 +100,11 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) { // History files are conventionally not readable by others: // https://github.com/nodejs/node/issues/3392 // https://github.com/nodejs/node/pull/3394 - fs.open(historyPath, 'a+', 0o0600, oninit); + try { + fs.open(historyPath, 'a+', 0o0600, oninit); + } catch (err) { + oninit(err); + } function oninit(err, hnd) { if (err) { diff --git a/node.gyp b/node.gyp index 39106ce346becf..3815fa0a2aa83f 100644 --- a/node.gyp +++ b/node.gyp @@ -76,6 +76,7 @@ 'lib/v8.js', 'lib/vm.js', 'lib/zlib.js', + 'lib/internal/async/fs.js', 'lib/internal/buffer.js', 'lib/internal/child_process.js', 'lib/internal/cluster/child.js', diff --git a/src/node_constants.cc b/src/node_constants.cc index 7fd303dd32fed0..27095ee85c825e 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -1164,6 +1164,14 @@ void DefineSystemConstants(Local target) { #ifdef X_OK NODE_DEFINE_CONSTANT(target, X_OK); #endif + +#ifdef UV_FS_SYMLINK_DIR + NODE_DEFINE_CONSTANT(target, UV_FS_SYMLINK_DIR); +#endif + +#ifdef UV_FS_SYMLINK_JUNCTION + NODE_DEFINE_CONSTANT(target, UV_FS_SYMLINK_JUNCTION); +#endif } void DefineCryptoConstants(Local target) { diff --git a/src/node_file.cc b/src/node_file.cc index b9b3d34f346e56..f4de897cfb8e28 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -56,6 +56,7 @@ using v8::Local; using v8::MaybeLocal; using v8::Number; using v8::Object; +using v8::Promise; using v8::String; using v8::Value; @@ -63,8 +64,6 @@ using v8::Value; # define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif -#define TYPE_ERROR(msg) env->ThrowTypeError(msg) - #define GET_OFFSET(a) ((a)->IsNumber() ? (a)->IntegerValue() : -1) class FSReqWrap: public ReqWrap { @@ -93,6 +92,40 @@ class FSReqWrap: public ReqWrap { size_t self_size() const override { return sizeof(*this); } + void Resolve(Environment* env, Local arg) { + Local context = env->context(); + Local promise = + object()->Get(context, env->oncomplete_string()).ToLocalChecked(); + if (promise->IsPromise()) { + if (promise.As()->State() != Promise::kPending) return; + Local resolver = promise.As(); + resolver->Resolve(context, arg).FromJust(); + return; + } + if (arg.IsEmpty()) { + Local arg = Null(env->isolate()); + MakeCallback(env->oncomplete_string(), 1, &arg); + } else { + Local argv[2]; + argv[0] = Null(env->isolate()); + argv[1] = arg; + MakeCallback(env->oncomplete_string(), 2, argv); + } + } + + void Reject(Environment* env, Local arg) { + Local context = env->context(); + Local promise = + object()->Get(context, env->oncomplete_string()).ToLocalChecked(); + if (promise->IsPromise()) { + if (promise.As()->State() != Promise::kPending) return; + Local resolver = promise.As(); + resolver->Reject(context, arg).FromJust(); + return; + } + MakeCallback(env->oncomplete_string(), 1, &arg); + } + private: FSReqWrap(Environment* env, Local req, @@ -121,9 +154,6 @@ class FSReqWrap: public ReqWrap { DISALLOW_COPY_AND_ASSIGN(FSReqWrap); }; -#define ASSERT_PATH(path) \ - if (*path == nullptr) \ - return TYPE_ERROR( #path " must be a string or Buffer"); FSReqWrap* FSReqWrap::New(Environment* env, Local req, @@ -167,30 +197,20 @@ void After(uv_fs_t *req) { HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); - // there is always at least one argument. "error" - int argc = 1; - - // Allocate space for two args. We may only use one depending on the case. - // (Feel free to increase this if you need more) - Local argv[2]; MaybeLocal link; + Local arg = Undefined(env->isolate()); Local error; if (req->result < 0) { // An error happened. - argv[0] = UVException(env->isolate(), - req->result, - req_wrap->syscall(), - nullptr, - req->path, - req_wrap->data()); + error = UVException(env->isolate(), + req->result, + req_wrap->syscall(), + nullptr, + req->path, + req_wrap->data()); + req_wrap->Reject(env, error); } else { - // error value is empty or null for non-error. - argv[0] = Null(env->isolate()); - - // All have at least two args now. - argc = 2; - switch (req->fs_type) { // These all have no data to pass. case UV_FS_ACCESS: @@ -210,28 +230,25 @@ void After(uv_fs_t *req) { case UV_FS_FCHOWN: case UV_FS_COPYFILE: // These, however, don't. - argc = 1; break; case UV_FS_STAT: case UV_FS_LSTAT: case UV_FS_FSTAT: - argc = 1; FillStatsArray(env->fs_stats_field_array(), static_cast(req->ptr)); break; case UV_FS_UTIME: case UV_FS_FUTIME: - argc = 0; break; case UV_FS_OPEN: - argv[1] = Integer::New(env->isolate(), req->result); + arg = Integer::New(env->isolate(), req->result); break; case UV_FS_WRITE: - argv[1] = Integer::New(env->isolate(), req->result); + arg = Integer::New(env->isolate(), req->result); break; case UV_FS_MKDTEMP: @@ -241,9 +258,10 @@ void After(uv_fs_t *req) { req_wrap->encoding_, &error); if (link.IsEmpty()) { - argv[0] = error; + req_wrap->Reject(env, error); + goto cleanup; } else { - argv[1] = link.ToLocalChecked(); + arg = link.ToLocalChecked(); } break; } @@ -254,9 +272,10 @@ void After(uv_fs_t *req) { req_wrap->encoding_, &error); if (link.IsEmpty()) { - argv[0] = error; + req_wrap->Reject(env, error); + goto cleanup; } else { - argv[1] = link.ToLocalChecked(); + arg = link.ToLocalChecked(); } break; @@ -266,15 +285,16 @@ void After(uv_fs_t *req) { req_wrap->encoding_, &error); if (link.IsEmpty()) { - argv[0] = error; + req_wrap->Reject(env, error); + goto cleanup; } else { - argv[1] = link.ToLocalChecked(); + arg = link.ToLocalChecked(); } break; case UV_FS_READ: // Buffer interface - argv[1] = Integer::New(env->isolate(), req->result); + arg = Integer::New(env->isolate(), req->result); break; case UV_FS_SCANDIR: @@ -292,11 +312,12 @@ void After(uv_fs_t *req) { if (r == UV_EOF) break; if (r != 0) { - argv[0] = UVException(r, - nullptr, - req_wrap->syscall(), - static_cast(req->path)); - break; + error = UVException(r, + nullptr, + req_wrap->syscall(), + static_cast(req->path)); + req_wrap->Reject(env, error); + goto cleanup; } MaybeLocal filename = @@ -305,8 +326,8 @@ void After(uv_fs_t *req) { req_wrap->encoding_, &error); if (filename.IsEmpty()) { - argv[0] = error; - break; + req_wrap->Reject(env, error); + goto cleanup; } name_argv[name_idx++] = filename.ToLocalChecked(); @@ -322,17 +343,17 @@ void After(uv_fs_t *req) { .ToLocalChecked(); } - argv[1] = names; + arg = names; } break; default: CHECK(0 && "Unhandled eio response"); } + req_wrap->Resolve(env, arg); } - req_wrap->MakeCallback(env->oncomplete_string(), argc, argv); - + cleanup: uv_fs_req_cleanup(req_wrap->req()); req_wrap->Dispose(); } @@ -395,13 +416,8 @@ void Access(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args.GetIsolate()); HandleScope scope(env->isolate()); - if (args.Length() < 2) - return TYPE_ERROR("path and mode are required"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("mode must be an integer"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); int mode = static_cast(args[1]->Int32Value()); @@ -416,11 +432,6 @@ void Access(const FunctionCallbackInfo& args) { void Close(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("fd is required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - int fd = args[0]->Int32Value(); if (args[1]->IsObject()) { @@ -547,11 +558,8 @@ static void InternalModuleStat(const FunctionCallbackInfo& args) { static void Stat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); if (args[1]->IsObject()) { ASYNC_CALL(stat, args[1], UTF8, *path) @@ -565,11 +573,8 @@ static void Stat(const FunctionCallbackInfo& args) { static void LStat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); if (args[1]->IsObject()) { ASYNC_CALL(lstat, args[1], UTF8, *path) @@ -583,11 +588,6 @@ static void LStat(const FunctionCallbackInfo& args) { static void FStat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("fd is required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - int fd = args[0]->Int32Value(); if (args[1]->IsObject()) { @@ -602,29 +602,13 @@ static void FStat(const FunctionCallbackInfo& args) { static void Symlink(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("target path required"); - if (len < 2) - return TYPE_ERROR("src path required"); - BufferValue target(env->isolate(), args[0]); - ASSERT_PATH(target) + CHECK_NE(*target, nullptr); + BufferValue path(env->isolate(), args[1]); - ASSERT_PATH(path) - - int flags = 0; - - if (args[2]->IsString()) { - node::Utf8Value mode(env->isolate(), args[2]); - if (strcmp(*mode, "dir") == 0) { - flags |= UV_FS_SYMLINK_DIR; - } else if (strcmp(*mode, "junction") == 0) { - flags |= UV_FS_SYMLINK_JUNCTION; - } else if (strcmp(*mode, "file") != 0) { - return env->ThrowError("Unknown symlink type"); - } - } + CHECK_NE(*path, nullptr); + + int flags = args[2]->Int32Value(env->context()).ToChecked(); if (args[3]->IsObject()) { ASYNC_DEST_CALL(symlink, args[3], *path, UTF8, *target, *path, flags) @@ -636,17 +620,11 @@ static void Symlink(const FunctionCallbackInfo& args) { static void Link(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("src path required"); - if (len < 2) - return TYPE_ERROR("dest path required"); - BufferValue src(env->isolate(), args[0]); - ASSERT_PATH(src) + CHECK_NE(*src, nullptr); BufferValue dest(env->isolate(), args[1]); - ASSERT_PATH(dest) + CHECK_NE(*dest, nullptr); if (args[2]->IsObject()) { ASYNC_DEST_CALL(link, args[2], *dest, UTF8, *src, *dest) @@ -660,11 +638,8 @@ static void ReadLink(const FunctionCallbackInfo& args) { const int argc = args.Length(); - if (argc < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8); @@ -694,16 +669,11 @@ static void ReadLink(const FunctionCallbackInfo& args) { static void Rename(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("old path required"); - if (len < 2) - return TYPE_ERROR("new path required"); - BufferValue old_path(env->isolate(), args[0]); - ASSERT_PATH(old_path) + CHECK_NE(*old_path, nullptr); + BufferValue new_path(env->isolate(), args[1]); - ASSERT_PATH(new_path) + CHECK_NE(*new_path, nullptr); if (args[2]->IsObject()) { ASYNC_DEST_CALL(rename, args[2], *new_path, UTF8, *old_path, *new_path) @@ -715,24 +685,8 @@ static void Rename(const FunctionCallbackInfo& args) { static void FTruncate(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 2) - return TYPE_ERROR("fd and length are required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - int fd = args[0]->Int32Value(); - - // FIXME(bnoordhuis) It's questionable to reject non-ints here but still - // allow implicit coercion from null or undefined to zero. Probably best - // handled in lib/fs.js. - Local len_v(args[1]); - if (!len_v->IsUndefined() && - !len_v->IsNull() && - !IsInt64(len_v->NumberValue())) { - return env->ThrowTypeError("Not an integer"); - } - - const int64_t len = len_v->IntegerValue(); + const int64_t len = args[1]->IntegerValue(); if (args[2]->IsObject()) { ASYNC_CALL(ftruncate, args[2], UTF8, fd, len) @@ -744,11 +698,6 @@ static void FTruncate(const FunctionCallbackInfo& args) { static void Fdatasync(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("fd is required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - int fd = args[0]->Int32Value(); if (args[1]->IsObject()) { @@ -761,11 +710,6 @@ static void Fdatasync(const FunctionCallbackInfo& args) { static void Fsync(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("fd is required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - int fd = args[0]->Int32Value(); if (args[1]->IsObject()) { @@ -778,11 +722,8 @@ static void Fsync(const FunctionCallbackInfo& args) { static void Unlink(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); if (args[1]->IsObject()) { ASYNC_CALL(unlink, args[1], UTF8, *path) @@ -794,11 +735,8 @@ static void Unlink(const FunctionCallbackInfo& args) { static void RMDir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); if (args[1]->IsObject()) { ASYNC_CALL(rmdir, args[1], UTF8, *path) @@ -810,13 +748,8 @@ static void RMDir(const FunctionCallbackInfo& args) { static void MKDir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 2) - return TYPE_ERROR("path and mode are required"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("mode must be an integer"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); int mode = static_cast(args[1]->Int32Value()); @@ -832,11 +765,8 @@ static void RealPath(const FunctionCallbackInfo& args) { const int argc = args.Length(); - if (argc < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8); @@ -868,11 +798,8 @@ static void ReadDir(const FunctionCallbackInfo& args) { const int argc = args.Length(); - if (argc < 1) - return TYPE_ERROR("path required"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8); @@ -931,20 +858,8 @@ static void ReadDir(const FunctionCallbackInfo& args) { static void Open(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("path required"); - if (len < 2) - return TYPE_ERROR("flags required"); - if (len < 3) - return TYPE_ERROR("mode required"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("flags must be an int"); - if (!args[2]->IsInt32()) - return TYPE_ERROR("mode must be an int"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); int flags = args[1]->Int32Value(); int mode = static_cast(args[2]->Int32Value()); @@ -961,17 +876,10 @@ static void Open(const FunctionCallbackInfo& args) { static void CopyFile(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsString()) - return TYPE_ERROR("src must be a string"); - if (!args[1]->IsString()) - return TYPE_ERROR("dest must be a string"); - if (!args[2]->IsInt32()) - return TYPE_ERROR("flags must be an int"); - BufferValue src(env->isolate(), args[0]); - ASSERT_PATH(src) + CHECK_NE(*src, nullptr); BufferValue dest(env->isolate(), args[1]); - ASSERT_PATH(dest) + CHECK_NE(*dest, nullptr); int flags = args[2]->Int32Value(); if (args[3]->IsObject()) { @@ -994,12 +902,9 @@ static void CopyFile(const FunctionCallbackInfo& args) { static void WriteBuffer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsInt32()) - return env->ThrowTypeError("First argument must be file descriptor"); + int fd = args[0]->Int32Value(); CHECK(Buffer::HasInstance(args[1])); - - int fd = args[0]->Int32Value(); Local obj = args[1].As(); const char* buf = Buffer::Data(obj); size_t buffer_length = Buffer::Length(obj); @@ -1008,14 +913,7 @@ static void WriteBuffer(const FunctionCallbackInfo& args) { int64_t pos = GET_OFFSET(args[4]); Local req = args[5]; - if (off > buffer_length) - return env->ThrowRangeError("offset out of bounds"); - if (len > buffer_length) - return env->ThrowRangeError("length out of bounds"); - if (off + len < off) - return env->ThrowRangeError("off + len overflow"); - if (!Buffer::IsWithinBounds(off, len, buffer_length)) - return env->ThrowRangeError("off + len > buffer.length"); + CHECK(Buffer::IsWithinBounds(off, len, buffer_length)); buf += off; @@ -1081,9 +979,6 @@ static void WriteBuffers(const FunctionCallbackInfo& args) { static void WriteString(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (!args[0]->IsInt32()) - return env->ThrowTypeError("First argument must be file descriptor"); - Local req; Local string = args[1]; int fd = args[0]->Int32Value(); @@ -1158,13 +1053,6 @@ static void WriteString(const FunctionCallbackInfo& args) { static void Read(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 2) - return TYPE_ERROR("fd and buffer are required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - if (!Buffer::HasInstance(args[1])) - return TYPE_ERROR("Second argument needs to be a buffer"); - int fd = args[0]->Int32Value(); Local req; @@ -1210,13 +1098,8 @@ static void Read(const FunctionCallbackInfo& args) { static void Chmod(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 2) - return TYPE_ERROR("path and mode are required"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("mode must be an integer"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); int mode = static_cast(args[1]->Int32Value()); @@ -1234,13 +1117,6 @@ static void Chmod(const FunctionCallbackInfo& args) { static void FChmod(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - if (args.Length() < 2) - return TYPE_ERROR("fd and mode are required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be a file descriptor"); - if (!args[1]->IsInt32()) - return TYPE_ERROR("mode must be an integer"); - int fd = args[0]->Int32Value(); int mode = static_cast(args[1]->Int32Value()); @@ -1258,20 +1134,8 @@ static void FChmod(const FunctionCallbackInfo& args) { static void Chown(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("path required"); - if (len < 2) - return TYPE_ERROR("uid required"); - if (len < 3) - return TYPE_ERROR("gid required"); - if (!args[1]->IsUint32()) - return TYPE_ERROR("uid must be an unsigned int"); - if (!args[2]->IsUint32()) - return TYPE_ERROR("gid must be an unsigned int"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); uv_uid_t uid = static_cast(args[1]->Uint32Value()); uv_gid_t gid = static_cast(args[2]->Uint32Value()); @@ -1290,20 +1154,6 @@ static void Chown(const FunctionCallbackInfo& args) { static void FChown(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("fd required"); - if (len < 2) - return TYPE_ERROR("uid required"); - if (len < 3) - return TYPE_ERROR("gid required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be an int"); - if (!args[1]->IsUint32()) - return TYPE_ERROR("uid must be an unsigned int"); - if (!args[2]->IsUint32()) - return TYPE_ERROR("gid must be an unsigned int"); - int fd = args[0]->Int32Value(); uv_uid_t uid = static_cast(args[1]->Uint32Value()); uv_gid_t gid = static_cast(args[2]->Uint32Value()); @@ -1319,20 +1169,8 @@ static void FChown(const FunctionCallbackInfo& args) { static void UTimes(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("path required"); - if (len < 2) - return TYPE_ERROR("atime required"); - if (len < 3) - return TYPE_ERROR("mtime required"); - if (!args[1]->IsNumber()) - return TYPE_ERROR("atime must be a number"); - if (!args[2]->IsNumber()) - return TYPE_ERROR("mtime must be a number"); - BufferValue path(env->isolate(), args[0]); - ASSERT_PATH(path) + CHECK_NE(*path, nullptr); const double atime = static_cast(args[1]->NumberValue()); const double mtime = static_cast(args[2]->NumberValue()); @@ -1347,20 +1185,6 @@ static void UTimes(const FunctionCallbackInfo& args) { static void FUTimes(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - int len = args.Length(); - if (len < 1) - return TYPE_ERROR("fd required"); - if (len < 2) - return TYPE_ERROR("atime required"); - if (len < 3) - return TYPE_ERROR("mtime required"); - if (!args[0]->IsInt32()) - return TYPE_ERROR("fd must be an int"); - if (!args[1]->IsNumber()) - return TYPE_ERROR("atime must be a number"); - if (!args[2]->IsNumber()) - return TYPE_ERROR("mtime must be a number"); - const int fd = args[0]->Int32Value(); const double atime = static_cast(args[1]->NumberValue()); const double mtime = static_cast(args[2]->NumberValue()); @@ -1378,8 +1202,7 @@ static void Mkdtemp(const FunctionCallbackInfo& args) { CHECK_GE(args.Length(), 2); BufferValue tmpl(env->isolate(), args[0]); - if (*tmpl == nullptr) - return TYPE_ERROR("template must be a string or Buffer"); + CHECK_NE(*tmpl, nullptr); const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8); diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js index d3140941bd0e72..df95fea9249785 100644 --- a/test/parallel/test-fs-access.js +++ b/test/parallel/test-fs-access.js @@ -81,9 +81,14 @@ fs.access(readOnlyFile, fs.W_OK, common.mustCall((err) => { } })); -assert.throws(() => { - fs.access(100, fs.F_OK, common.mustNotCall()); -}, /^TypeError: path must be a string or Buffer$/); +common.expectsError( + () => fs.access(100, fs.F_OK, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } +); common.expectsError( () => { diff --git a/test/parallel/test-fs-buffer.js b/test/parallel/test-fs-buffer.js index e7f575a437555e..d6a182e4ff5c63 100644 --- a/test/parallel/test-fs-buffer.js +++ b/test/parallel/test-fs-buffer.js @@ -24,9 +24,14 @@ assert.doesNotThrow(() => { })); }); -assert.throws(() => { - fs.accessSync(true); -}, /path must be a string or Buffer/); +common.expectsError( + () => fs.accessSync(true), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } +); const dir = Buffer.from(common.fixturesDir); fs.readdir(dir, 'hex', common.mustCall((err, hexList) => { diff --git a/test/parallel/test-fs-chmod.js b/test/parallel/test-fs-chmod.js index 7d4b7a10dbd9b4..2d91e617eedd4a 100644 --- a/test/parallel/test-fs-chmod.js +++ b/test/parallel/test-fs-chmod.js @@ -136,6 +136,38 @@ if (fs.lchmod) { })); } +[1, {}, [], true, undefined, null].forEach((i) => { + common.expectsError( + () => fs.chmodSync(i, 0o664), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } + ); + + common.expectsError( + () => fs.chmod(i, 0o664, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } + ); +}); + +[-1, {}, [], true, undefined, null, Infinity, 'foo'].forEach((i) => { + common.expectsError( + () => fs.chmod(__filename, i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "mode" argument must be of type unsigned integer' + } + ); + fs.chmodSync(__filename, 0o664); +}); + process.on('exit', function() { assert.strictEqual(0, openCount); diff --git a/test/parallel/test-fs-close-errors.js b/test/parallel/test-fs-close-errors.js new file mode 100644 index 00000000000000..f0340493b4af33 --- /dev/null +++ b/test/parallel/test-fs-close-errors.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../common'); +const fs = require('fs'); + +['', {}, -1, true, Infinity, undefined, null].forEach((i) => { + common.expectsError( + () => fs.closeSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + } + ); + + common.expectsError( + () => fs.close(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + } + ); +}); diff --git a/test/parallel/test-fs-copyfile.js b/test/parallel/test-fs-copyfile.js index 28dbc605fcbe1a..c8edfcf94b3b48 100644 --- a/test/parallel/test-fs-copyfile.js +++ b/test/parallel/test-fs-copyfile.js @@ -69,9 +69,14 @@ common.expectsError(() => { }); // Throws if the source path is not a string. -assert.throws(() => { - fs.copyFileSync(null, dest); -}, /^TypeError: src must be a string$/); +common.expectsError( + () => fs.copyFileSync(null, dest), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "src" argument must be one of type string, Buffer, or URL' + } +); // Throws if the source path is an invalid path. common.expectsError(() => { @@ -84,9 +89,14 @@ common.expectsError(() => { }); // Throws if the destination path is not a string. -assert.throws(() => { - fs.copyFileSync(src, null); -}, /^TypeError: dest must be a string$/); +common.expectsError( + () => fs.copyFileSync(src, null), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "dest" argument must be one of type string, Buffer, or URL' + } +); // Throws if the destination path is an invalid path. common.expectsError(() => { diff --git a/test/parallel/test-fs-exists.js b/test/parallel/test-fs-exists.js index b19aa387741a70..fefc0d1f1be98d 100644 --- a/test/parallel/test-fs-exists.js +++ b/test/parallel/test-fs-exists.js @@ -34,7 +34,15 @@ fs.exists(`${f}-NO`, common.mustCall(function(y) { assert.strictEqual(y, false); })); -fs.exists(new URL('https://foo'), common.mustCall(function(y) { +common.expectsError( + () => fs.exists(new URL('https://foo'), common.mustNotCall()), + { + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + } +); +fs.exists(new URL(`file://${f}-NO`), common.mustCall(function(y) { assert.strictEqual(y, false); })); diff --git a/test/parallel/test-fs-link.js b/test/parallel/test-fs-link.js index 525392aa2be01c..db2debde5952cf 100644 --- a/test/parallel/test-fs-link.js +++ b/test/parallel/test-fs-link.js @@ -21,16 +21,38 @@ fs.link(srcPath, dstPath, common.mustCall(callback)); // test error outputs -assert.throws( - function() { - fs.link(); - }, - /src must be a string or Buffer/ +common.expectsError( + () => fs.linkSync(), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "src" argument must be one of type string, Buffer, or URL' + } ); -assert.throws( - function() { - fs.link('abc'); - }, - /dest must be a string or Buffer/ +common.expectsError( + () => fs.linkSync('abc'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "dest" argument must be one of type string, Buffer, or URL' + } +); + +common.expectsError( + () => fs.link(undefined, undefined, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "src" argument must be one of type string, Buffer, or URL' + } +); + +common.expectsError( + () => fs.link('abc', undefined, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "dest" argument must be one of type string, Buffer, or URL' + } ); diff --git a/test/parallel/test-fs-null-bytes.js b/test/parallel/test-fs-null-bytes.js index 44defc782ee139..98662e04aae6b2 100644 --- a/test/parallel/test-fs-null-bytes.js +++ b/test/parallel/test-fs-null-bytes.js @@ -25,32 +25,25 @@ const assert = require('assert'); const fs = require('fs'); const URL = require('url').URL; -function check(async, sync) { - const argsSync = Array.prototype.slice.call(arguments, 2); - const argsAsync = argsSync.concat((er) => { - common.expectsError( - () => { - throw er; - }, - { - code: 'ERR_INVALID_ARG_TYPE', - type: Error - }); - }); +function check(async, sync, ...args) { if (sync) { common.expectsError( - () => { - sync.apply(null, argsSync); - }, + () => sync(...args), { code: 'ERR_INVALID_ARG_TYPE', type: Error, - }); + } + ); } if (async) { - async.apply(null, argsAsync); + common.expectsError( + () => async(...args, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE' + } + ); } } @@ -81,6 +74,7 @@ check(fs.utimes, fs.utimesSync, 'foo\u0000bar', 0, 0); check(null, fs.watch, 'foo\u0000bar', common.mustNotCall()); check(null, fs.watchFile, 'foo\u0000bar', common.mustNotCall()); check(fs.writeFile, fs.writeFileSync, 'foo\u0000bar', 'abc'); +check(fs.exists, fs.existsSync, 'foo\u0000bar'); const fileUrl = new URL('file:///C:/foo\u0000bar'); const fileUrl2 = new URL('file:///C:/foo%00bar'); @@ -112,6 +106,7 @@ check(fs.utimes, fs.utimesSync, fileUrl, 0, 0); check(null, fs.watch, fileUrl, assert.fail); check(null, fs.watchFile, fileUrl, assert.fail); check(fs.writeFile, fs.writeFileSync, fileUrl, 'abc'); +check(fs.exists, fs.existsSync, fileUrl); check(fs.access, fs.accessSync, fileUrl2); check(fs.access, fs.accessSync, fileUrl2, fs.F_OK); @@ -140,10 +135,4 @@ check(fs.utimes, fs.utimesSync, fileUrl2, 0, 0); check(null, fs.watch, fileUrl2, assert.fail); check(null, fs.watchFile, fileUrl2, assert.fail); check(fs.writeFile, fs.writeFileSync, fileUrl2, 'abc'); - -// an 'error' for exists means that it doesn't exist. -// one of many reasons why this file is the absolute worst. -fs.exists('foo\u0000bar', common.mustCall((exists) => { - assert(!exists); -})); -assert(!fs.existsSync('foo\u0000bar')); +check(fs.exists, fs.existsSync, fileUrl2); diff --git a/test/parallel/test-fs-open-flags.js b/test/parallel/test-fs-open-flags.js index 873bd7112828eb..315d4a31b444b7 100644 --- a/test/parallel/test-fs-open-flags.js +++ b/test/parallel/test-fs-open-flags.js @@ -60,21 +60,21 @@ assert.strictEqual(stringToFlags('xa+'), O_APPEND | O_CREAT | O_RDWR | O_EXCL); .forEach(function(flags) { common.expectsError( () => stringToFlags(flags), - { code: 'ERR_INVALID_OPT_VALUE', type: TypeError } + { code: 'ERR_INVALID_ARG_TYPE', type: TypeError } ); }); common.expectsError( () => stringToFlags({}), - { code: 'ERR_INVALID_OPT_VALUE', type: TypeError } + { code: 'ERR_INVALID_ARG_TYPE', type: TypeError } ); common.expectsError( () => stringToFlags(true), - { code: 'ERR_INVALID_OPT_VALUE', type: TypeError } + { code: 'ERR_INVALID_ARG_TYPE', type: TypeError } ); common.expectsError( () => stringToFlags(null), - { code: 'ERR_INVALID_OPT_VALUE', type: TypeError } + { code: 'ERR_INVALID_ARG_TYPE', type: TypeError } ); diff --git a/test/parallel/test-fs-open.js b/test/parallel/test-fs-open.js index 94817844bdcb4c..ff5fdb9a09bfa0 100644 --- a/test/parallel/test-fs-open.js +++ b/test/parallel/test-fs-open.js @@ -43,3 +43,40 @@ fs.open(__filename, 'r', common.mustCall((err) => { fs.open(__filename, 'rs', common.mustCall((err) => { assert.ifError(err); })); + +[-1, {}, [], null, undefined, Infinity, true].forEach((i) => { + common.expectsError( + () => fs.openSync(i, 'r'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } + ); + common.expectsError( + () => fs.open(i, 'r', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + } + ); + common.expectsError( + () => fs.openSync(__filename, i), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: + 'The "flags" argument must be one of type string or unsigned integer' + } + ); + common.expectsError( + () => fs.open(__filename, i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: + 'The "flags" argument must be one of type string or unsigned integer' + } + ); +}); diff --git a/test/parallel/test-fs-promise-access.js b/test/parallel/test-fs-promise-access.js new file mode 100644 index 00000000000000..5fa3299fdd6462 --- /dev/null +++ b/test/parallel/test-fs-promise-access.js @@ -0,0 +1,103 @@ +'use strict'; + +const common = require('../common'); +const { async: fs, F_OK } = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); + +const goodurl = new URL(`file://${__filename}`); +const badurl = new URL(`file://${__filename}-NO`); +const nullurl = new URL(`file://${__filename}-%00NO`); +const invalidurl = new URL(`http://${__filename}`); + +common.crashOnUnhandledRejection(); + +// Access this file, known to exist. Must not fail. +fs.access(__filename) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(goodurl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(__filename, F_OK) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(goodurl, F_OK) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(__dirname) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(__dirname, F_OK) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(`${__filename}-NO`) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, access '${__filename}-NO'` + })); + +fs.access(badurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, access '${__filename}-NO'` + })); + +fs.access(nullurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.access(invalidurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +fs.access(Buffer.from(__filename)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.access(Buffer.from(`${__filename}-NO`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, access '${__filename}-NO'` + })); + +[1, {}, [], Infinity, null, undefined, true].forEach((i) => { + fs.access(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); + +[{}, [], Infinity, null, true].forEach((i) => { + fs.access(__filename, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "mode" argument must be of type integer' + })); +}); diff --git a/test/parallel/test-fs-promise-chmod.js b/test/parallel/test-fs-promise-chmod.js new file mode 100644 index 00000000000000..8f285657c15bcc --- /dev/null +++ b/test/parallel/test-fs-promise-chmod.js @@ -0,0 +1,116 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +const mode = common.isWindows ? 0o600 : 0o644; + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +closeSync(openSync(file, 'w')); + +// fchmod +async function test(file, mode) { + const fd = await fs.open(file, 'w'); + await fs.fchmod(fd, mode); + await fs.close(fd); +} + +test(file, mode) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, file, {}, [], Infinity, true].forEach((i) => { + fs.fchmod(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); + +// chmod +const goodurl = new URL(`file://${file}`); +const badurl = new URL(`file://${file}-NO`); +const nullurl = new URL(`file://${file}-%00NO`); +const invalidurl = new URL(`http://${file}`); + +fs.chmod(file, mode) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(Buffer.from(file), mode) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(file, mode.toString(8)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(Buffer.from(file), mode.toString(8)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(goodurl, mode) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(goodurl, mode.toString(8)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chmod(badurl, mode) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, chmod '${file}-NO'` + })); + +fs.chmod(nullurl, mode) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.chmod(invalidurl, mode) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[1, {}, [], Infinity, null, undefined, true].forEach((i) => { + fs.chmod(i, mode) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); + +['', -1, null, {}, [], Infinity, true, '-1'].forEach((i) => { + fs.chmod(file, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "mode" argument must be of type unsigned integer' + })); +}); diff --git a/test/parallel/test-fs-promise-chown.js b/test/parallel/test-fs-promise-chown.js new file mode 100644 index 00000000000000..c9b323d0bb3dbd --- /dev/null +++ b/test/parallel/test-fs-promise-chown.js @@ -0,0 +1,136 @@ +'use strict'; + +const common = require('../common'); +if (common.isWindows) + common.skip('unsupported on windows'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +const uid = process.getuid(); +const gid = process.getgid(); + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +closeSync(openSync(file, 'w')); + +// fchown + +async function test(file) { + const fd = await fs.open(file, 'w'); + + [file, {}, [], null, undefined, true, Infinity].forEach((i) => { + fs.fchown(fd, i, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "uid" argument must be of type integer' + })); + fs.fchown(fd, uid, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "gid" argument must be of type integer' + })); + }); + + await fs.close(fd); +} + +test(file) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, file, {}, [], Infinity, null, undefined, true].forEach((i) => { + fs.fchown(i, uid, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); + +// chown + +const goodurl = new URL(`file://${file}`); +const badurl = new URL(`file://${file}-NO`); +const nullurl = new URL(`file://${file}-%00NO`); +const invalidurl = new URL(`http://${file}`); + +fs.chown(file, uid, gid) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chown(Buffer.from(file), uid, gid) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chown(goodurl, uid, gid) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.chown(badurl, uid, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, chown '${file}-NO'` + })); + +fs.chown(nullurl, uid, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.chown(invalidurl, uid, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[1, {}, [], Infinity, null, undefined, true].forEach((i) => { + fs.chown(i, uid, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); + +['', null, {}, [], Infinity, true, '-1'].forEach((i) => { + fs.chown(file, i, gid) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "uid" argument must be of type integer' + })); +}); + +['', null, {}, [], Infinity, true, '-1'].forEach((i) => { + fs.chown(file, uid, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "gid" argument must be of type integer' + })); +}); diff --git a/test/parallel/test-fs-promise-copyfile.js b/test/parallel/test-fs-promise-copyfile.js new file mode 100644 index 00000000000000..fc491f24604b3c --- /dev/null +++ b/test/parallel/test-fs-promise-copyfile.js @@ -0,0 +1,136 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync, + constants +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +const { COPYFILE_EXCL, UV_FS_COPYFILE_EXCL } = constants; + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); +const dest = path.join(common.tmpDir, 'b.js'); +closeSync(openSync(src, 'w')); + +const goodsrcurl = new URL(`file://${src}`); +const badsrcurl = new URL(`file://${src}-NO`); +const nullsrcurl = new URL(`file://${src}-%00NO`); +const invalidsrcurl = new URL(`http://${src}`); + +const gooddesturl = new URL(`file://${dest}`); +const nulldesturl = new URL(`file://${dest}-%00NO`); +const invaliddesturl = new URL(`http://${dest}`); + +fs.copyFile(src, dest) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(src, dest, COPYFILE_EXCL) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'EEXIST', + type: Error, + message: `EEXIST: file already exists, copyfile '${src}' -> '${dest}'` + })); + +fs.copyFile(src, dest, UV_FS_COPYFILE_EXCL) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'EEXIST', + type: Error, + message: `EEXIST: file already exists, copyfile '${src}' -> '${dest}'` + })); + +fs.copyFile(Buffer.from(src), dest) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(src, Buffer.from(dest)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(goodsrcurl, dest) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(goodsrcurl, gooddesturl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(src, gooddesturl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(Buffer.from(src), gooddesturl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.copyFile(badsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: + `ENOENT: no such file or directory, copyfile '${src}-NO' -> '${dest}'` + })); + +fs.copyFile(nullsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.copyFile(src, nulldesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.copyFile(invalidsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +fs.copyFile(src, invaliddesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[-1, {}, [], Infinity, true, null, undefined].forEach((i) => { + fs.copyFile(i, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "src" argument must be one of type string, Buffer, or URL' + })); + + fs.copyFile(src, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "dest" argument must be one of type string, Buffer, or URL' + })); +}); diff --git a/test/parallel/test-fs-promise-fdatasync.js b/test/parallel/test-fs-promise-fdatasync.js new file mode 100644 index 00000000000000..4f21eccb7d6a35 --- /dev/null +++ b/test/parallel/test-fs-promise-fdatasync.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +closeSync(openSync(file, 'w')); + +async function test(method, file) { + const fd = await fs.open(file, 'w'); + await method(fd); +} + +test(fs.fdatasync, file) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(fs.fsync, file) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, {}, [], '-1', null, undefined, Infinity].forEach((i) => { + fs.fdatasync(i) + .then(common.mustNotCall) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); + fs.fsync(i) + .then(common.mustNotCall) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); diff --git a/test/parallel/test-fs-promise-ftruncate.js b/test/parallel/test-fs-promise-ftruncate.js new file mode 100644 index 00000000000000..99f8b4ebf87dbd --- /dev/null +++ b/test/parallel/test-fs-promise-ftruncate.js @@ -0,0 +1,81 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const path = require('path'); +const { + Buffer +} = require('buffer'); +const { + URL +} = require('url'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +closeSync(openSync(file, 'w')); + +async function test(file, len) { + const fd = await fs.open(file, 'w'); + try { + await fs.ftruncate(fd, len); + } finally { + await fs.close(fd); + } +} + +async function test2(file, len) { + await fs.truncate(file, len); +} + +test(file, 0) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(file, 1) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, {}, [], '-1', Infinity].forEach((i) => { + fs.ftruncate(i) + .then(common.mustNotCall) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); + + test(file, i) + .then(common.mustNotCall) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "len" argument must be of type unsigned integer' + })); + + test2(file, i) + .then(common.mustNotCall) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "len" argument must be of type unsigned integer' + })); +}); + +test2(file, 0) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test2(Buffer.from(file), 0) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test2(new URL(`file://${file}`), 0) + .then(common.mustCall()) + .catch(common.mustNotCall()); diff --git a/test/parallel/test-fs-promise-link.js b/test/parallel/test-fs-promise-link.js new file mode 100644 index 00000000000000..bba81cc9ea4789 --- /dev/null +++ b/test/parallel/test-fs-promise-link.js @@ -0,0 +1,122 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); +const dest = path.join(common.tmpDir, 'b.js'); +closeSync(openSync(src, 'w')); + +const goodsrcurl = new URL(`file://${src}`); +const badsrcurl = new URL(`file://${src}-NO`); +const nullsrcurl = new URL(`file://${src}-%00NO`); +const invalidsrcurl = new URL(`http://${src}`); + +const gooddesturl = new URL(`file://${dest}-4`); +const nulldesturl = new URL(`file://${dest}-%00NO`); +const invaliddesturl = new URL(`http://${dest}`); + +async function test(src, dest) { + await fs.link(src, dest); + await fs.unlink(dest); +} + +test(src, `${dest}-1`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(src), `${dest}-2`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(goodsrcurl, `${dest}-3`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(src, gooddesturl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(goodsrcurl, Buffer.from(`${dest}-5`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.link(badsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: + `ENOENT: no such file or directory, link '${src}-NO' -> '${dest}'` + })); + +fs.link(nullsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.link(src, nulldesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.link(invalidsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +fs.link(src, invaliddesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[-1, {}, [], Infinity, true, null, undefined].forEach((i) => { + fs.unlink(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); + + fs.link(i, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "src" argument must be one of type string, Buffer, or URL' + })); + + fs.link(src, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "dest" argument must be one of type string, Buffer, or URL' + })); +}); diff --git a/test/parallel/test-fs-promise-mkdir.js b/test/parallel/test-fs-promise-mkdir.js new file mode 100644 index 00000000000000..dc2fc0279379d5 --- /dev/null +++ b/test/parallel/test-fs-promise-mkdir.js @@ -0,0 +1,64 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const dir = path.join(common.tmpDir, 'foo'); + +// mkdir +const goodurl = new URL(`file://${dir}-2`); +const nullurl = new URL(`file://${dir}-%00NO`); +const invalidurl = new URL(`http://${dir}`); + +async function test(dir) { + await fs.mkdir(dir); + await fs.rmdir(dir); +} + +test(dir) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(`${dir}-1`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(goodurl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(nullurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +test(invalidurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[1, {}, [], Infinity, null, undefined, true].forEach((i) => { + test(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); diff --git a/test/parallel/test-fs-promise-mkdtemp.js b/test/parallel/test-fs-promise-mkdtemp.js new file mode 100644 index 00000000000000..f2eba25e8a43bf --- /dev/null +++ b/test/parallel/test-fs-promise-mkdtemp.js @@ -0,0 +1,35 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +fs.mkdtemp(`${common.tmpDir}-`) + .then(fs.rmdir) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.mkdtemp(`${common.tmpDir}-`, 'buffer') + .then(fs.rmdir) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.mkdtemp(`${common.tmpDir}-`, { encoding: 'buffer' }) + .then(fs.rmdir) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, null, undefined, {}, [], Infinity, true].forEach((i) => { + fs.mkdtemp(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "prefix" argument must be of type string' + })); +}); diff --git a/test/parallel/test-fs-promise-open-close.js b/test/parallel/test-fs-promise-open-close.js new file mode 100644 index 00000000000000..302ac393fc5544 --- /dev/null +++ b/test/parallel/test-fs-promise-open-close.js @@ -0,0 +1,93 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +//closeSync(openSync(file, 'w')); + +const goodurl = new URL(`file://${file}`); +const badurl = new URL(`file://${file}-NO`); +const nullurl = new URL(`file://${file}-%00NO`); +const invalidurl = new URL(`http://${file}`); + +async function test(path, mode = 'w') { + await fs.close(await fs.open(path, mode)); +} + +test(file) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(file)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(goodurl) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(badurl, 'r') + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, open '${file}-NO'` + })); + +test(nullurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +test(invalidurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[1, {}, [], true, Infinity, null, undefined].forEach((i) => { + fs.open(i, 'w') + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); + +[-1, {}, [], true, Infinity, null, '-1'].forEach((i) => { + fs.open(file, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: + 'The "flags" argument must be one of type string or unsigned integer' + })); +}); + +['a', -1, '-1', {}, [], true, Infinity, null, undefined].forEach((i) => { + fs.close(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); diff --git a/test/parallel/test-fs-promise-read-write-file.js b/test/parallel/test-fs-promise-read-write-file.js new file mode 100644 index 00000000000000..85b715d69759f2 --- /dev/null +++ b/test/parallel/test-fs-promise-read-write-file.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); +const assert = require('assert'); +const { + Buffer +} = require('buffer'); +const { + URL +} = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); + +async function test(path, data) { + await fs.writeFile(path, data.slice(0, -2)); + await fs.appendFile(path, data.slice(-2)); + const content = await fs.readFile(path); + if (typeof data === 'string') { + assert.strictEqual(data, content.toString()); + } else { + assert.deepStrictEqual(data, content); + } +} + +test(src, 'testing') + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(`${src}-1`, Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(`${src}-2`), Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(new URL(`file://${src}-3`), Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); diff --git a/test/parallel/test-fs-promise-read.js b/test/parallel/test-fs-promise-read.js new file mode 100644 index 00000000000000..0f36724b8fda59 --- /dev/null +++ b/test/parallel/test-fs-promise-read.js @@ -0,0 +1,33 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + readFileSync +} = require('fs'); +const assert = require('assert'); +const { + Buffer +} = require('buffer'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const content = readFileSync(__filename, 'utf8'); + +async function test(input) { + const fd = await fs.open(input, 'r'); + try { + const stat = await fs.fstat(fd); + const buffer = Buffer.allocUnsafeSlow(stat.size); + await fs.read(fd, buffer); + assert.strictEqual(content, buffer.toString('utf8')); + } finally { + await fs.close(fd); + } +} + +test(__filename) + .then(common.mustCall()) + .catch(common.mustNotCall()); diff --git a/test/parallel/test-fs-promise-readdir.js b/test/parallel/test-fs-promise-readdir.js new file mode 100644 index 00000000000000..cadddce9d43c7d --- /dev/null +++ b/test/parallel/test-fs-promise-readdir.js @@ -0,0 +1,82 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); +const { + Buffer +} = require('buffer'); +const { + URL +} = require('url'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +fs.readdir(common.fixturesDir) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.readdir(Buffer.from(common.fixturesDir)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.readdir(new URL(`file://${common.fixturesDir}`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.readdir(`${common.fixturesDir}-NO`) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: + `ENOENT: no such file or directory, scandir '${common.fixturesDir}-NO'` + })); + +fs.readdir(Buffer.from(`${common.fixturesDir}-NO`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: + `ENOENT: no such file or directory, scandir '${common.fixturesDir}-NO'` + })); + +fs.readdir(new URL(`file://${common.fixturesDir}-NO`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: + `ENOENT: no such file or directory, scandir '${common.fixturesDir}-NO'` + })); + +fs.readdir(`${common.fixturesDir}-\u0000NO`) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.readdir(Buffer.from(`${common.fixturesDir}-\u0000NO`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type object' + })); + +fs.readdir(new URL(`file://${common.fixturesDir}-\u0000NO`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); diff --git a/test/parallel/test-fs-promise-rename.js b/test/parallel/test-fs-promise-rename.js new file mode 100644 index 00000000000000..6875e47cedb929 --- /dev/null +++ b/test/parallel/test-fs-promise-rename.js @@ -0,0 +1,81 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); +const dest = path.join(common.tmpDir, 'b.js'); +closeSync(openSync(src, 'w')); + +const nullurl = new URL(`file://${src}-%00NO`); +const invalidurl = new URL(`http://${src}`); + +async function test(s, d) { + closeSync(openSync(s, 'w')); + await fs.rename(s, d); +} + +test(src, dest) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(`${src}-1`), `${dest}-1`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(`${src}-2`, Buffer.from(`${dest}-2`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(new URL(`file://${src}-3`), `${dest}-3`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(`${src}-4`, new URL(`file://${dest}-1`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.rename(nullurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.rename(src, nullurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.rename(invalidurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +fs.rename(src, invalidurl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); diff --git a/test/parallel/test-fs-promise-stat.js b/test/parallel/test-fs-promise-stat.js new file mode 100644 index 00000000000000..2c57aa4c1c9eb2 --- /dev/null +++ b/test/parallel/test-fs-promise-stat.js @@ -0,0 +1,87 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + Stats +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const assert = require('assert'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +async function test(method, input, open = false) { + if (open) { + input = await fs.open(input, 'r'); + } + try { + const stats = await method(input); + assert(stats instanceof Stats); + } finally { + if (open) + await fs.close(input); + } +} + +test(fs.fstat, __filename, true); + +[-1, {}, [], Infinity, null, undefined, 'hello'].forEach((i) => { + fs.fstat(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); + +test(fs.stat, __filename) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(fs.stat, Buffer.from(__filename)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(fs.stat, new URL(`file://${__filename}`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(fs.stat, `${__filename}-NO`) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, stat '${__filename}-NO'` + })); + +test(fs.stat, `${__filename}-\u0000`) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +test(fs.stat, new URL(`file://${__filename}-%00`)) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +[-1, {}, [], Infinity, undefined, null, true].forEach((i) => { + test(fs.stat, i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); diff --git a/test/parallel/test-fs-promise-symlinks.js b/test/parallel/test-fs-promise-symlinks.js new file mode 100644 index 00000000000000..84b555031041cc --- /dev/null +++ b/test/parallel/test-fs-promise-symlinks.js @@ -0,0 +1,88 @@ +'use strict'; + +const common = require('../common'); +if (!common.canCreateSymLink()) + common.skip('insufficient privileges'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const assert = require('assert'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); +const dest = path.join(common.tmpDir, 'b.js'); +closeSync(openSync(src, 'w')); + +const nullsrcurl = new URL(`file://${src}-%00NO`); +const invalidsrcurl = new URL(`http://${src}`); + +const nulldesturl = new URL(`file://${dest}-%00NO`); +const invaliddesturl = new URL(`http://${dest}`); + +async function test(s, d) { + await fs.symlink(s, d); + assert.strictEqual(src, await fs.readlink(d)); + assert.strictEqual(src, await fs.realpath(d)); +} + +test(src, dest) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(src), `${dest}-1`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(src, Buffer.from(`${dest}-2`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(new URL(`file://${src}`), `${dest}-3`) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(src, new URL(`file://${dest}-4`)) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(nullsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +test(invalidsrcurl, dest) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +test(src, nulldesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +test(src, invaliddesturl) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); diff --git a/test/parallel/test-fs-promise-utimes.js b/test/parallel/test-fs-promise-utimes.js new file mode 100644 index 00000000000000..a3c389e483c94c --- /dev/null +++ b/test/parallel/test-fs-promise-utimes.js @@ -0,0 +1,106 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs, + closeSync, + openSync +} = require('fs'); +const { Buffer } = require('buffer'); +const { URL } = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +const date = new Date(); + +common.refreshTmpDir(); + +const file = path.join(common.tmpDir, 'a.js'); +closeSync(openSync(file, 'w')); + +// futimes +async function test(file) { + const fd = await fs.open(file, 'w'); + await fs.futimes(fd, date, date); + await fs.close(fd); +} + +test(file) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +[-1, file, {}, [], Infinity, true].forEach((i) => { + fs.fchmod(i) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + })); +}); + +// utimes +const goodurl = new URL(`file://${file}`); +const badurl = new URL(`file://${file}-NO`); +const nullurl = new URL(`file://${file}-%00NO`); +const invalidurl = new URL(`http://${file}`); + +fs.utimes(file, date, date) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(Buffer.from(file), date, date) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(goodurl, date, date) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(file, date.valueOf(), date.valueOf()) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(Buffer.from(file), date.valueOf(), date.valueOf()) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(goodurl, date.valueOf(), date.valueOf()) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +fs.utimes(badurl, date, date) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOENT', + type: Error, + message: `ENOENT: no such file or directory, utime '${file}-NO'` + })); + +fs.utimes(nullurl, date, date) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: Error, + message: 'The "path" argument must be of type string without null bytes. ' + + 'Received type string' + })); + +fs.utimes(invalidurl, date, date) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })); + +[1, {}, [], Infinity, null, undefined, true].forEach((i) => { + fs.utimes(i, date, date) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "path" argument must be one of type string, Buffer, or URL' + })); +}); diff --git a/test/parallel/test-fs-promise-write.js b/test/parallel/test-fs-promise-write.js new file mode 100644 index 00000000000000..4bddf96257f2c8 --- /dev/null +++ b/test/parallel/test-fs-promise-write.js @@ -0,0 +1,52 @@ +'use strict'; + +const common = require('../common'); +const { + async: fs +} = require('fs'); +const assert = require('assert'); +const { + Buffer +} = require('buffer'); +const { + URL +} = require('url'); +const path = require('path'); + +common.crashOnUnhandledRejection(); + +common.refreshTmpDir(); + +const src = path.join(common.tmpDir, 'a.js'); + +async function test(path, data) { + const fd = await fs.open(path, 'w+'); + try { + await fs.write(fd, data); + const buffer = Buffer.allocUnsafeSlow(data.length); + await fs.read(fd, buffer); + if (typeof data === 'string') { + assert.strictEqual(data, buffer.toString()); + } else { + assert.deepStrictEqual(data, buffer); + } + } finally { + await fs.close(fd); + } +} + +test(src, 'testing') + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(`${src}-1`, Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(Buffer.from(`${src}-2`), Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); + +test(new URL(`file://${src}-3`), Buffer.from('testing')) + .then(common.mustCall()) + .catch(common.mustNotCall()); diff --git a/test/parallel/test-fs-read-type.js b/test/parallel/test-fs-read-type.js index 0014affa1f7dde..cbbfe4824c1a0e 100644 --- a/test/parallel/test-fs-read-type.js +++ b/test/parallel/test-fs-read-type.js @@ -1,6 +1,5 @@ 'use strict'; const common = require('../common'); -const assert = require('assert'); const fs = require('fs'); const fixtures = require('../common/fixtures'); @@ -9,14 +8,20 @@ const fd = fs.openSync(filepath, 'r'); const expected = 'xyz\n'; // Error must be thrown with string -assert.throws(() => { - fs.read(fd, - expected.length, - 0, - 'utf-8', - common.mustNotCall()); -}, /^TypeError: Second argument needs to be a buffer$/); +common.expectsError( + () => fs.read(fd, expected.length, 0, 'utf-8', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "buffer" argument must be one of type Buffer or Uint8Array' + } +); -assert.throws(() => { - fs.readSync(fd, expected.length, 0, 'utf-8'); -}, /^TypeError: Second argument needs to be a buffer$/); +common.expectsError( + () => fs.readSync(fd, expected.length, 0, 'utf-8'), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "buffer" argument must be one of type Buffer or Uint8Array' + } +); diff --git a/test/parallel/test-fs-utimes.js b/test/parallel/test-fs-utimes.js index b72b294a0bd1b1..5ea21b1d82d089 100644 --- a/test/parallel/test-fs-utimes.js +++ b/test/parallel/test-fs-utimes.js @@ -25,9 +25,6 @@ const assert = require('assert'); const util = require('util'); const fs = require('fs'); -let tests_ok = 0; -let tests_run = 0; - function stat_resource(resource) { if (typeof resource === 'string') { return fs.statSync(resource); @@ -48,20 +45,15 @@ function check_mtime(resource, mtime) { } function expect_errno(syscall, resource, err, errno) { - if (err && (err.code === errno || err.code === 'ENOSYS')) { - tests_ok++; - } else { - console.log('FAILED:', 'expect_errno', util.inspect(arguments)); - } + if (err && (err.code === errno || err.code === 'ENOSYS')) + return; + assert.fail(null, null, `FAILED:\texpect_errno\t${util.inspect(arguments)}`); } function expect_ok(syscall, resource, err, atime, mtime) { - if (!err && check_mtime(resource, mtime) || - err && err.code === 'ENOSYS') { - tests_ok++; - } else { - console.log('FAILED:', 'expect_ok', util.inspect(arguments)); - } + if (!err && check_mtime(resource, mtime) || err && err.code === 'ENOSYS') + return; + assert.fail(null, null, `FAILED:\texpect_ok\t${util.inspect(arguments)}`); } // the tests assume that __filename belongs to the user running the tests @@ -76,12 +68,10 @@ function testIt(atime, mtime, callback) { function syncTests() { fs.utimesSync(__filename, atime, mtime); expect_ok('utimesSync', __filename, undefined, atime, mtime); - tests_run++; // some systems don't have futimes // if there's an error, it should be ENOSYS try { - tests_run++; fs.futimesSync(fd, atime, mtime); expect_ok('futimesSync', fd, undefined, atime, mtime); } catch (ex) { @@ -95,16 +85,18 @@ function testIt(atime, mtime, callback) { err = ex; } expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT'); - tests_run++; err = undefined; - try { - fs.futimesSync(-1, atime, mtime); - } catch (ex) { - err = ex; - } - expect_errno('futimesSync', -1, err, 'EBADF'); - tests_run++; + + common.expectsError( + () => fs.futimesSync(-1, atime, mtime), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + } + ); + } // @@ -126,18 +118,20 @@ function testIt(atime, mtime, callback) { fs.futimes(fd, atime, mtime, common.mustCall(function(err) { expect_ok('futimes', fd, err, atime, mtime); - fs.futimes(-1, atime, mtime, common.mustCall(function(err) { - expect_errno('futimes', -1, err, 'EBADF'); - syncTests(); - callback(); - })); - tests_run++; + common.expectsError( + () => fs.futimes(-1, atime, mtime, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "fd" argument must be of type unsigned integer' + } + ); + + syncTests(); + callback(); })); - tests_run++; })); - tests_run++; })); - tests_run++; } const stats = fs.statSync(__filename); @@ -163,10 +157,6 @@ runTest(new Date('1982-09-10 13:37'), new Date('1982-09-10 13:37'), function() { }); }); -process.on('exit', function() { - assert.strictEqual(tests_ok, tests_run); -}); - // Ref: https://github.com/nodejs/node/issues/13255 common.refreshTmpDir(); diff --git a/test/parallel/test-fs-whatwg-url.js b/test/parallel/test-fs-whatwg-url.js index b9aaee9c3081e6..cb3275c97b3d9c 100644 --- a/test/parallel/test-fs-whatwg-url.js +++ b/test/parallel/test-fs-whatwg-url.js @@ -28,46 +28,55 @@ fs.readFile(url, common.mustCall((err, data) => { // Check that using a non file:// URL reports an error const httpUrl = new URL('http://example.org'); -fs.readFile(httpUrl, common.expectsError({ - code: 'ERR_INVALID_URL_SCHEME', - type: TypeError, - message: 'The URL must be of scheme file' -})); +common.expectsError( + () => fs.readFile(httpUrl, common.mustNotCall()), + { + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + } +); // pct-encoded characters in the path will be decoded and checked -fs.readFile(new URL('file:///c:/tmp/%00test'), common.mustCall((err) => { - common.expectsError( - () => { - throw err; - }, - { - code: 'ERR_INVALID_ARG_TYPE', - type: Error - }); -})); +common.expectsError( + () => fs.readFile(new URL('file:///c:/tmp/%00test'), common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + type: Error + } +); if (common.isWindows) { // encoded back and forward slashes are not permitted on windows ['%2f', '%2F', '%5c', '%5C'].forEach((i) => { - fs.readFile(new URL(`file:///c:/tmp/${i}`), common.expectsError({ - code: 'ERR_INVALID_FILE_URL_PATH', - type: TypeError, - message: 'File URL path must not include encoded \\ or / characters' - })); + common.expectsError( + () => fs.readFile(new URL(`file:///c:/tmp/${i}`), common.mustNotCall()), + { + code: 'ERR_INVALID_FILE_URL_PATH', + type: TypeError, + message: 'File URL path must not include encoded \\ or / characters' + } + ); }); } else { // encoded forward slashes are not permitted on other platforms ['%2f', '%2F'].forEach((i) => { - fs.readFile(new URL(`file:///c:/tmp/${i}`), common.expectsError({ - code: 'ERR_INVALID_FILE_URL_PATH', - type: TypeError, - message: 'File URL path must not include encoded / characters' - })); + common.expectsError( + () => fs.readFile(new URL(`file:///c:/tmp/${i}`), common.mustNotCall()), + { + code: 'ERR_INVALID_FILE_URL_PATH', + type: TypeError, + message: 'File URL path must not include encoded / characters' + } + ); }); - fs.readFile(new URL('file://hostname/a/b/c'), common.expectsError({ - code: 'ERR_INVALID_FILE_URL_HOST', - type: TypeError, - message: `File URL host must be "localhost" or empty on ${os.platform()}` - })); + common.expectsError( + () => fs.readFile(new URL('file://hostname/a/b/c'), common.mustNotCall()), + { + code: 'ERR_INVALID_FILE_URL_HOST', + type: TypeError, + message: `File URL host must be "localhost" or empty on ${os.platform()}` + } + ); } From df1d5878c83673c73aa4b8b819e0e6d88db18c0f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 20 Sep 2017 07:41:21 -0700 Subject: [PATCH 2/2] [Squash] address some feedback --- src/node_file.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index f4de897cfb8e28..d9aa9aa390640e 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -97,7 +97,7 @@ class FSReqWrap: public ReqWrap { Local promise = object()->Get(context, env->oncomplete_string()).ToLocalChecked(); if (promise->IsPromise()) { - if (promise.As()->State() != Promise::kPending) return; + CHECK_EQ(promise.As()->State(), Promise::kPending); Local resolver = promise.As(); resolver->Resolve(context, arg).FromJust(); return; @@ -109,7 +109,7 @@ class FSReqWrap: public ReqWrap { Local argv[2]; argv[0] = Null(env->isolate()); argv[1] = arg; - MakeCallback(env->oncomplete_string(), 2, argv); + MakeCallback(env->oncomplete_string(), arraysize(argv), argv); } } @@ -118,7 +118,7 @@ class FSReqWrap: public ReqWrap { Local promise = object()->Get(context, env->oncomplete_string()).ToLocalChecked(); if (promise->IsPromise()) { - if (promise.As()->State() != Promise::kPending) return; + CHECK_EQ(promise.As()->State(), Promise::kPending); Local resolver = promise.As(); resolver->Reject(context, arg).FromJust(); return;