diff --git a/benchmark/fs/bench-stat-promise.js b/benchmark/fs/bench-stat-promise.js index 96c7058fa6218a..99a5da5799b787 100644 --- a/benchmark/fs/bench-stat-promise.js +++ b/benchmark/fs/bench-stat-promise.js @@ -9,12 +9,12 @@ const bench = common.createBenchmark(main, { }); async function run(n, statType) { - const arg = statType === 'fstat' ? - await fsPromises.open(__filename, 'r') : __filename; + const handleMode = statType === 'fstat'; + const arg = handleMode ? await fsPromises.open(__filename, 'r') : __filename; let remaining = n; bench.start(); while (remaining-- > 0) - await fsPromises[statType](arg); + await (handleMode ? arg.stat() : fsPromises[statType](arg)); bench.end(n); if (typeof arg.close === 'function') diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index b97bc73304a4d7..3a6a44d0f71a0d 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -615,14 +615,6 @@ asyncResource.asyncId(); // Return the trigger ID for the AsyncResource instance. asyncResource.triggerAsyncId(); - -// Call AsyncHooks before callbacks. -// Deprecated: Use asyncResource.runInAsyncScope instead. -asyncResource.emitBefore(); - -// Call AsyncHooks after callbacks. -// Deprecated: Use asyncResource.runInAsyncScope instead. -asyncResource.emitAfter(); ``` #### new AsyncResource(type[, options]) diff --git a/doc/api/fs.md b/doc/api/fs.md index c2daa8d71a5055..02bf4abdf8b13b 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -325,7 +325,8 @@ fs.watch('./tmp', { encoding: 'buffer' }, (eventType, filename) => { added: v10.0.0 --> -Emitted when the watcher stops watching for changes. +Emitted when the watcher stops watching for changes. The closed +`fs.FSWatcher` object is no longer usable in the event handler. ### Event: 'error' * `path` {string|Buffer|URL} -* `mode` {integer} **Default:** `0o777` +* `mode` {integer} Not supported on Windows. **Default:** `0o777`. * `callback` {Function} * `err` {Error} @@ -2007,7 +2019,7 @@ changes: --> * `path` {string|Buffer|URL} -* `mode` {integer} **Default:** `0o777` +* `mode` {integer} Not supported on Windows. **Default:** `0o777`. Synchronously creates a directory. Returns `undefined`. This is the synchronous version of [`fs.mkdir()`][]. @@ -2127,7 +2139,8 @@ changes: Asynchronous file open. See open(2). `mode` sets the file mode (permission and sticky bits), but only if the file was -created. +created. Note that on Windows only the write permission can be manipulated, +see [`fs.chmod()`][]. The callback gets two arguments `(err, fd)`. @@ -3795,128 +3808,6 @@ fsPromises.copyFile('source.txt', 'destination.txt', COPYFILE_EXCL) .catch(() => console.log('The file could not be copied')); ``` -### fsPromises.fchmod(filehandle, mode) - - -* `filehandle` {FileHandle} -* `mode` {integer} -* Returns: {Promise} - -Asynchronous fchmod(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.fchown(filehandle, uid, gid) - - -* `filehandle` {FileHandle} -* `uid` {integer} -* `gid` {integer} -* Returns: {Promise} - -Changes the ownership of the file represented by `filehandle` then resolves -the `Promise` with no arguments upon success. - -### fsPromises.fdatasync(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Asynchronous fdatasync(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.fstat(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Retrieves the [`fs.Stats`][] for the given `filehandle`. - -### fsPromises.fsync(filehandle) - - -* `filehandle` {FileHandle} -* Returns: {Promise} - -Asynchronous fsync(2). The `Promise` is resolved with no arguments upon -success. - -### fsPromises.ftruncate(filehandle[, len]) - - -* `filehandle` {FileHandle} -* `len` {integer} **Default:** `0` -* Returns: {Promise} - -Truncates the file represented by `filehandle` then resolves the `Promise` -with no arguments upon success. - -If the file referred to by the `FileHandle` was larger than `len` bytes, only -the first `len` bytes will be retained in the file. - -For example, the following program retains only the first four bytes of the -file: - -```js -console.log(fs.readFileSync('temp.txt', 'utf8')); -// Prints: Node.js - -async function doTruncate() { - const fd = await fsPromises.open('temp.txt', 'r+'); - await fsPromises.ftruncate(fd, 4); - console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints: Node -} - -doTruncate().catch(console.error); -``` - -If the file previously was shorter than `len` bytes, it is extended, and the -extended part is filled with null bytes (`'\0'`). For example, - -```js -console.log(fs.readFileSync('temp.txt', 'utf8')); -// Prints: Node.js - -async function doTruncate() { - const fd = await fsPromises.open('temp.txt', 'r+'); - await fsPromises.ftruncate(fd, 10); - console.log(fs.readFileSync('temp.txt', 'utf8')); // Prints Node.js\0\0\0 -} - -doTruncate().catch(console.error); -``` - -The last three bytes are null bytes (`'\0'`), to compensate the over-truncation. - -### fsPromises.futimes(filehandle, atime, mtime) - - -* `filehandle` {FileHandle} -* `atime` {number|string|Date} -* `mtime` {number|string|Date} -* Returns: {Promise} - -Change the file system timestamps of the object referenced by the supplied -`FileHandle` then resolves the `Promise` with no arguments upon success. - -This function does not work on AIX versions before 7.1, it will resolve the -`Promise` with an error using code `UV_ENOSYS`. - ### fsPromises.lchmod(path, mode) - -* `filehandle` {FileHandle} -* `buffer` {Buffer|Uint8Array} -* `offset` {integer} -* `length` {integer} -* `position` {integer} -* Returns: {Promise} - -Read data from the file specified by `filehandle`. - -`buffer` is the buffer that the data will be written to. - -`offset` is the offset in the buffer to start writing at. - -`length` is an integer specifying the number of bytes to read. - -`position` is an argument specifying where to begin reading from in the file. -If `position` is `null`, data will be read from the current file position, -and the file position will be updated. -If `position` is an integer, the file position will remain unchanged. - -Following successful read, the `Promise` is resolved with an object with a -`bytesRead` property specifying the number of bytes read, and a `buffer` -property that is a reference to the passed in `buffer` argument. - ### fsPromises.readdir(path[, options]) - -* `filehandle` {FileHandle} -* `buffer` {Buffer|Uint8Array} -* `offset` {integer} -* `length` {integer} -* `position` {integer} -* Returns: {Promise} - -Write `buffer` to the file specified by `filehandle`. - -The `Promise` is resolved with an object containing a `bytesWritten` property -identifying the number of bytes written, and a `buffer` property containing -a reference to the `buffer` written. - -`offset` determines the part of the buffer to be written, and `length` is -an integer specifying the number of bytes to write. - -`position` refers to the offset from the beginning of the file where this data -should be written. If `typeof position !== 'number'`, the data will be written -at the current position. See pwrite(2). - -It is unsafe to use `fsPromises.write()` multiple times on the same file -without waiting for the `Promise` to be resolved (or rejected). For this -scenario, `fs.createWriteStream` is strongly recommended. - -On Linux, positional writes do not work when the file is opened in append mode. -The kernel ignores the position argument and always appends the data to -the end of the file. - ### fsPromises.writeFile(file, data[, options]) -* `host` {string} The hostname to verify the certificate against +* `hostname` {string} The hostname to verify the certificate against * `cert` {Object} An object representing the peer's certificate. The returned object has some properties corresponding to the fields of the certificate. * Returns: {Error|undefined} -Verifies the certificate `cert` is issued to host `host`. +Verifies the certificate `cert` is issued to `hostname`. Returns {Error} object, populating it with the reason, host, and cert on failure. On success, returns {undefined}. diff --git a/lib/fs.js b/lib/fs.js index cc559a898dc4ea..983625bc9b0af3 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -36,36 +36,28 @@ const { W_OK, X_OK, O_WRONLY, - O_SYMLINK, - UV_FS_COPYFILE_EXCL, - UV_FS_COPYFILE_FICLONE, - UV_FS_COPYFILE_FICLONE_FORCE + O_SYMLINK } = constants; -const util = require('util'); + +const { _extend } = require('util'); const pathModule = require('path'); const { isUint8Array } = require('internal/util/types'); - const binding = process.binding('fs'); -const fs = exports; const { Buffer, kMaxLength } = require('buffer'); const errors = require('internal/errors'); const { ERR_FS_FILE_TOO_LARGE, ERR_INVALID_ARG_TYPE, - ERR_INVALID_CALLBACK, - ERR_OUT_OF_RANGE + ERR_INVALID_CALLBACK } = errors.codes; -const { Readable, Writable } = require('stream'); -const EventEmitter = require('events'); -const { FSReqWrap, statValues, kFsStatsFieldsLength } = binding; -const { FSEvent } = process.binding('fs_event_wrap'); + +const { FSReqWrap, statValues } = binding; const internalFS = require('internal/fs/utils'); const { getPathFromURL } = require('internal/url'); const internalUtil = require('internal/util'); const { copyObject, getOptions, - modeNum, nullCheck, preprocessSymlinkDestination, Stats, @@ -85,49 +77,30 @@ const { } = require('internal/constants'); const { isUint32, + validateMode, validateInteger, validateInt32, validateUint32 } = require('internal/validators'); -// Lazy loaded -let promises; - let promisesWarn = true; +let truncateWarn = true; +let fs; -Object.defineProperty(fs, 'promises', { - configurable: true, - enumerable: false, - get() { - if (promisesWarn) { - promises = require('internal/fs/promises'); - promisesWarn = false; - process.emitWarning('The fs.promises API is experimental', - 'ExperimentalWarning'); - } - return promises; - } -}); - -Object.defineProperty(exports, 'constants', { - configurable: false, - enumerable: true, - value: constants -}); - -let assert_ = null; -function lazyAssert() { - if (assert_ === null) { - assert_ = require('assert'); - } - return assert_; -} +// Lazy loaded +let promises; +let watchers; +let ReadFileContext; +let ReadStream; +let WriteStream; -const kMinPoolSpace = 128; +// These have to be separate because of how graceful-fs happens to do it's +// monkeypatching. +let FileReadStream; +let FileWriteStream; const isWindows = process.platform === 'win32'; -let truncateWarn = true; function showTruncateDeprecation() { if (truncateWarn) { @@ -189,23 +162,13 @@ function makeStatsCallback(cb) { const isFd = isUint32; -fs.Stats = Stats; - function isFileType(stats, fileType) { // Use stats array directly to avoid creating an fs.Stats instance just for // our internal use. return (stats[1/* mode */] & S_IFMT) === fileType; } -// Don't allow mode to accidentally be overwritten. -Object.defineProperties(fs, { - F_OK: { enumerable: true, value: F_OK || 0 }, - R_OK: { enumerable: true, value: R_OK || 0 }, - W_OK: { enumerable: true, value: W_OK || 0 }, - X_OK: { enumerable: true, value: X_OK || 0 }, -}); - -fs.access = function(path, mode, callback) { +function access(path, mode, callback) { if (typeof mode === 'function') { callback = mode; mode = F_OK; @@ -215,12 +178,12 @@ fs.access = function(path, mode, callback) { validatePath(path); mode = mode | 0; - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.access(pathModule.toNamespacedPath(path), mode, req); -}; +} -fs.accessSync = function(path, mode) { +function accessSync(path, mode) { path = getPathFromURL(path); validatePath(path); @@ -232,9 +195,9 @@ fs.accessSync = function(path, mode) { const ctx = { path }; binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.exists = function(path, callback) { +function exists(path, callback) { maybeCallback(callback); function suppressedCallback(err) { @@ -246,9 +209,9 @@ fs.exists = function(path, callback) { } catch (err) { return callback(false); } -}; +} -Object.defineProperty(fs.exists, internalUtil.promisify.custom, { +Object.defineProperty(exists, internalUtil.promisify.custom, { value: (path) => { const { createPromise, promiseResolve } = process.binding('util'); const promise = createPromise(); @@ -263,93 +226,17 @@ Object.defineProperty(fs.exists, internalUtil.promisify.custom, { // fs.existsSync(), would only get a false in return, so we cannot signal // validation errors to users properly out of compatibility concerns. // TODO(joyeecheung): deprecate the never-throw-on-invalid-arguments behavior -fs.existsSync = function(path) { +function existsSync(path) { try { fs.accessSync(path, F_OK); return true; } catch (e) { return false; } -}; - -fs.readFile = function(path, options, callback) { - callback = maybeCallback(callback || options); - options = getOptions(options, { flag: 'r' }); - var context = new ReadFileContext(callback, options.encoding); - context.isUserFd = isFd(path); // file descriptor ownership - var req = new FSReqWrap(); - req.context = context; - req.oncomplete = readFileAfterOpen; - - if (context.isUserFd) { - process.nextTick(function tick() { - req.oncomplete(null, path); - }); - return; - } - - path = getPathFromURL(path); - validatePath(path); - binding.open(pathModule.toNamespacedPath(path), - stringToFlags(options.flag || 'r'), - 0o666, - req); -}; - -const kReadFileBufferLength = 8 * 1024; - -function ReadFileContext(callback, encoding) { - this.fd = undefined; - this.isUserFd = undefined; - this.size = undefined; - this.callback = callback; - this.buffers = null; - this.buffer = null; - this.pos = 0; - this.encoding = encoding; - this.err = null; -} - -ReadFileContext.prototype.read = function() { - var buffer; - var offset; - var length; - - if (this.size === 0) { - buffer = this.buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength); - offset = 0; - length = kReadFileBufferLength; - } else { - buffer = this.buffer; - offset = this.pos; - length = Math.min(kReadFileBufferLength, this.size - this.pos); - } - - var req = new FSReqWrap(); - req.oncomplete = readFileAfterRead; - req.context = this; - - binding.read(this.fd, buffer, offset, length, -1, req); -}; - -ReadFileContext.prototype.close = function(err) { - var req = new FSReqWrap(); - req.oncomplete = readFileAfterClose; - req.context = this; - this.err = err; - - if (this.isUserFd) { - process.nextTick(function tick() { - req.oncomplete(null); - }); - return; - } - - binding.close(this.fd, req); -}; +} function readFileAfterOpen(err, fd) { - var context = this.context; + const context = this.context; if (err) { context.callback(err); @@ -358,23 +245,19 @@ function readFileAfterOpen(err, fd) { context.fd = fd; - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = readFileAfterStat; req.context = context; binding.fstat(fd, req); } function readFileAfterStat(err, stats) { - var context = this.context; + const context = this.context; if (err) return context.close(err); - var size; - if (isFileType(stats, S_IFREG)) - size = context.size = stats[8]; - else - size = context.size = 0; + const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0; if (size === 0) { context.buffers = []; @@ -395,52 +278,31 @@ function readFileAfterStat(err, stats) { context.read(); } -function readFileAfterRead(err, bytesRead) { - var context = this.context; - - if (err) - return context.close(err); - - if (bytesRead === 0) - return context.close(); - - context.pos += bytesRead; - - if (context.size !== 0) { - if (context.pos === context.size) - context.close(); - else - context.read(); - } else { - // unknown size, just read until we don't get bytes. - context.buffers.push(context.buffer.slice(0, bytesRead)); - context.read(); - } -} - -function readFileAfterClose(err) { - var context = this.context; - var buffer = null; - var callback = context.callback; +function readFile(path, options, callback) { + callback = maybeCallback(callback || options); + options = getOptions(options, { flag: 'r' }); + if (!ReadFileContext) + ReadFileContext = require('internal/fs/read_file_context'); + const context = new ReadFileContext(callback, options.encoding); + context.isUserFd = isFd(path); // file descriptor ownership - if (context.err || err) - return callback(context.err || err); + const req = new FSReqWrap(); + req.context = context; + req.oncomplete = readFileAfterOpen; - try { - if (context.size === 0) - buffer = Buffer.concat(context.buffers, context.pos); - else if (context.pos < context.size) - buffer = context.buffer.slice(0, context.pos); - else - buffer = context.buffer; - - if (context.encoding) - buffer = buffer.toString(context.encoding); - } catch (err) { - return callback(err); + if (context.isUserFd) { + process.nextTick(function tick() { + req.oncomplete(null, path); + }); + return; } - callback(null, buffer); + path = getPathFromURL(path); + validatePath(path); + binding.open(pathModule.toNamespacedPath(path), + stringToFlags(options.flag || 'r'), + 0o666, + req); } function tryStatSync(fd, isUserFd) { @@ -454,8 +316,8 @@ function tryStatSync(fd, isUserFd) { } function tryCreateBuffer(size, fd, isUserFd) { - var threw = true; - var buffer; + let threw = true; + let buffer; try { if (size > kMaxLength) { throw new ERR_FS_FILE_TOO_LARGE(size); @@ -469,8 +331,8 @@ function tryCreateBuffer(size, fd, isUserFd) { } function tryReadSync(fd, isUserFd, buffer, pos, len) { - var threw = true; - var bytesRead; + let threw = true; + let bytesRead; try { bytesRead = fs.readSync(fd, buffer, pos, len); threw = false; @@ -480,20 +342,16 @@ function tryReadSync(fd, isUserFd, buffer, pos, len) { return bytesRead; } -fs.readFileSync = function(path, options) { +function readFileSync(path, options) { options = getOptions(options, { flag: 'r' }); - var isUserFd = isFd(path); // file descriptor ownership - var fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666); + const isUserFd = isFd(path); // file descriptor ownership + const fd = isUserFd ? path : fs.openSync(path, options.flag || 'r', 0o666); const stats = tryStatSync(fd, isUserFd); - var size; - if (isFileType(stats, S_IFREG)) - size = stats[8]; - else - size = 0; - var pos = 0; - var buffer; // single buffer with file data - var buffers; // list for when size is unknown + const size = isFileType(stats, S_IFREG) ? stats[8] : 0; + let pos = 0; + let buffer; // single buffer with file data + let buffers; // list for when size is unknown if (size === 0) { buffers = []; @@ -501,7 +359,7 @@ fs.readFileSync = function(path, options) { buffer = tryCreateBuffer(size, fd, isUserFd); } - var bytesRead; + let bytesRead; if (size !== 0) { do { @@ -533,55 +391,60 @@ fs.readFileSync = function(path, options) { if (options.encoding) buffer = buffer.toString(options.encoding); return buffer; -}; +} -fs.close = function(fd, callback) { +function close(fd, callback) { validateUint32(fd, 'fd'); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.close(fd, req); -}; +} -fs.closeSync = function(fd) { +function closeSync(fd) { validateUint32(fd, 'fd'); const ctx = {}; binding.close(fd, undefined, ctx); handleErrorFromBinding(ctx); -}; - -fs.open = function(path, flags, mode, callback_) { - var callback = makeCallback(arguments[arguments.length - 1]); - mode = modeNum(mode, 0o666); +} +function open(path, flags, mode, callback) { path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + const flagsNumber = stringToFlags(flags); + if (arguments.length < 4) { + callback = makeCallback(mode); + mode = 0o666; + } else { + mode = validateMode(mode, 'mode', 0o666); + callback = makeCallback(callback); + } const req = new FSReqWrap(); req.oncomplete = callback; binding.open(pathModule.toNamespacedPath(path), - stringToFlags(flags), + flagsNumber, mode, req); -}; +} -fs.openSync = function(path, flags, mode) { - mode = modeNum(mode, 0o666); + +function openSync(path, flags, mode) { path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + const flagsNumber = stringToFlags(flags); + mode = validateMode(mode, 'mode', 0o666); const ctx = { path }; const result = binding.open(pathModule.toNamespacedPath(path), - stringToFlags(flags), mode, + flagsNumber, mode, undefined, ctx); handleErrorFromBinding(ctx); return result; -}; +} -fs.read = function(fd, buffer, offset, length, position, callback) { +function read(fd, buffer, offset, length, position, callback) { validateUint32(fd, 'fd'); validateBuffer(buffer); @@ -608,12 +471,12 @@ fs.read = function(fd, buffer, offset, length, position, callback) { req.oncomplete = wrapper; binding.read(fd, buffer, offset, length, position, req); -}; +} -Object.defineProperty(fs.read, internalUtil.customPromisifyArgs, +Object.defineProperty(read, internalUtil.customPromisifyArgs, { value: ['bytesRead', 'buffer'], enumerable: false }); -fs.readSync = function(fd, buffer, offset, length, position) { +function readSync(fd, buffer, offset, length, position) { validateUint32(fd, 'fd'); validateBuffer(buffer); @@ -634,13 +497,13 @@ fs.readSync = function(fd, buffer, offset, length, position) { undefined, ctx); handleErrorFromBinding(ctx); return result; -}; +} // usage: // fs.write(fd, buffer[, offset[, length[, position]]], callback); // OR // fs.write(fd, string[, position[, encoding]], callback); -fs.write = function(fd, buffer, offset, length, position, callback) { +function write(fd, buffer, offset, length, position, callback) { function wrapper(err, written) { // Retain a reference to buffer so that it can't be GC'ed too soon. callback(err, written || 0, buffer); @@ -676,16 +539,16 @@ fs.write = function(fd, buffer, offset, length, position, callback) { } callback = maybeCallback(position); return binding.writeString(fd, buffer, offset, length, req); -}; +} -Object.defineProperty(fs.write, internalUtil.customPromisifyArgs, +Object.defineProperty(write, internalUtil.customPromisifyArgs, { value: ['bytesWritten', 'buffer'], enumerable: false }); // usage: // fs.writeSync(fd, buffer[, offset[, length[, position]]]); // OR // fs.writeSync(fd, string[, position[, encoding]]); -fs.writeSync = function(fd, buffer, offset, length, position) { +function writeSync(fd, buffer, offset, length, position) { validateUint32(fd, 'fd'); const ctx = {}; let result; @@ -709,9 +572,9 @@ fs.writeSync = function(fd, buffer, offset, length, position) { } handleErrorFromBinding(ctx); return result; -}; +} -fs.rename = function(oldPath, newPath, callback) { +function rename(oldPath, newPath, callback) { callback = makeCallback(callback); oldPath = getPathFromURL(oldPath); validatePath(oldPath, 'oldPath'); @@ -722,9 +585,9 @@ fs.rename = function(oldPath, newPath, callback) { binding.rename(pathModule.toNamespacedPath(oldPath), pathModule.toNamespacedPath(newPath), req); -}; +} -fs.renameSync = function(oldPath, newPath) { +function renameSync(oldPath, newPath) { oldPath = getPathFromURL(oldPath); validatePath(oldPath, 'oldPath'); newPath = getPathFromURL(newPath); @@ -733,9 +596,9 @@ fs.renameSync = function(oldPath, newPath) { binding.rename(pathModule.toNamespacedPath(oldPath), pathModule.toNamespacedPath(newPath), undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.truncate = function(path, len, callback) { +function truncate(path, len, callback) { if (typeof path === 'number') { showTruncateDeprecation(); return fs.ftruncate(path, len, callback); @@ -751,7 +614,7 @@ fs.truncate = function(path, len, callback) { 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); @@ -759,9 +622,9 @@ fs.truncate = function(path, len, callback) { }; binding.ftruncate(fd, len, req); }); -}; +} -fs.truncateSync = function(path, len) { +function truncateSync(path, len) { if (typeof path === 'number') { // legacy showTruncateDeprecation(); @@ -771,8 +634,8 @@ fs.truncateSync = function(path, len) { len = 0; } // allow error to be thrown, but still close fd. - var fd = fs.openSync(path, 'r+'); - var ret; + const fd = fs.openSync(path, 'r+'); + let ret; try { ret = fs.ftruncateSync(fd, len); @@ -780,103 +643,102 @@ fs.truncateSync = function(path, len) { fs.closeSync(fd); } return ret; -}; +} -fs.ftruncate = function(fd, len = 0, callback) { +function ftruncate(fd, len = 0, callback) { if (typeof len === 'function') { callback = len; len = 0; } validateUint32(fd, 'fd'); - // TODO(BridgeAR): This does not seem right. - // There does not seem to be any validation before and if there is any, it - // should work similar to validateUint32 or not have a upper cap at all. - // This applies to all usage of `validateInt32(len, 'len')`. - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.ftruncate(fd, len, req); -}; +} -fs.ftruncateSync = function(fd, len = 0) { +function ftruncateSync(fd, len = 0) { validateUint32(fd, 'fd'); - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); const ctx = {}; binding.ftruncate(fd, len, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.rmdir = function(path, callback) { +function rmdir(path, callback) { callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); const req = new FSReqWrap(); req.oncomplete = callback; binding.rmdir(pathModule.toNamespacedPath(path), req); -}; +} -fs.rmdirSync = function(path) { +function rmdirSync(path) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.fdatasync = function(fd, callback) { +function fdatasync(fd, callback) { validateUint32(fd, 'fd'); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fdatasync(fd, req); -}; +} -fs.fdatasyncSync = function(fd) { +function fdatasyncSync(fd) { validateUint32(fd, 'fd'); const ctx = {}; binding.fdatasync(fd, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.fsync = function(fd, callback) { +function fsync(fd, callback) { validateUint32(fd, 'fd'); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fsync(fd, req); -}; +} -fs.fsyncSync = function(fd) { +function fsyncSync(fd) { validateUint32(fd, 'fd'); const ctx = {}; binding.fsync(fd, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.mkdir = function(path, mode, callback) { - if (typeof mode === 'function') callback = mode; - callback = makeCallback(callback); +function mkdir(path, mode, callback) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode, 0o777); - validateUint32(mode, 'mode'); + + if (arguments.length < 3) { + callback = makeCallback(mode); + mode = 0o777; + } else { + callback = makeCallback(callback); + mode = validateMode(mode, 'mode', 0o777); + } const req = new FSReqWrap(); req.oncomplete = callback; binding.mkdir(pathModule.toNamespacedPath(path), mode, req); -}; +} -fs.mkdirSync = function(path, mode) { +function mkdirSync(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode, 0o777); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode', 0o777); const ctx = { path }; binding.mkdir(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.readdir = function(path, options, callback) { +function readdir(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); path = getPathFromURL(path); @@ -885,9 +747,9 @@ fs.readdir = function(path, options, callback) { const req = new FSReqWrap(); req.oncomplete = callback; binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req); -}; +} -fs.readdirSync = function(path, options) { +function readdirSync(path, options) { options = getOptions(options, {}); path = getPathFromURL(path); validatePath(path); @@ -896,42 +758,42 @@ fs.readdirSync = function(path, options) { options.encoding, undefined, ctx); handleErrorFromBinding(ctx); return result; -}; +} -fs.fstat = function(fd, callback) { +function fstat(fd, callback) { validateUint32(fd, 'fd'); const req = new FSReqWrap(); req.oncomplete = makeStatsCallback(callback); binding.fstat(fd, req); -}; +} -fs.lstat = function(path, callback) { +function lstat(path, callback) { callback = makeStatsCallback(callback); path = getPathFromURL(path); validatePath(path); const req = new FSReqWrap(); req.oncomplete = callback; binding.lstat(pathModule.toNamespacedPath(path), req); -}; +} -fs.stat = function(path, callback) { +function stat(path, callback) { callback = makeStatsCallback(callback); path = getPathFromURL(path); validatePath(path); const req = new FSReqWrap(); req.oncomplete = callback; binding.stat(pathModule.toNamespacedPath(path), req); -}; +} -fs.fstatSync = function(fd) { +function fstatSync(fd) { validateUint32(fd, 'fd'); const ctx = { fd }; const stats = binding.fstat(fd, undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); -}; +} -fs.lstatSync = function(path) { +function lstatSync(path) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; @@ -939,9 +801,9 @@ fs.lstatSync = function(path) { undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); -}; +} -fs.statSync = function(path) { +function statSync(path) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; @@ -949,9 +811,9 @@ fs.statSync = function(path) { undefined, ctx); handleErrorFromBinding(ctx); return getStatsFromBinding(stats); -}; +} -fs.readlink = function(path, options, callback) { +function readlink(path, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); path = getPathFromURL(path); @@ -959,9 +821,9 @@ fs.readlink = function(path, options, callback) { const req = new FSReqWrap(); req.oncomplete = callback; binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req); -}; +} -fs.readlinkSync = function(path, options) { +function readlinkSync(path, options) { options = getOptions(options, {}); path = getPathFromURL(path); validatePath(path, 'oldPath'); @@ -970,11 +832,11 @@ fs.readlinkSync = function(path, options) { options.encoding, undefined, ctx); handleErrorFromBinding(ctx); return result; -}; +} -fs.symlink = function(target, path, type_, callback_) { - var type = (typeof type_ === 'string' ? type_ : null); - var callback = makeCallback(arguments[arguments.length - 1]); +function symlink(target, path, type_, callback_) { + const type = (typeof type_ === 'string' ? type_ : null); + const callback = makeCallback(arguments[arguments.length - 1]); target = getPathFromURL(target); path = getPathFromURL(path); @@ -987,9 +849,9 @@ fs.symlink = function(target, path, type_, callback_) { binding.symlink(preprocessSymlinkDestination(target, type, path), pathModule.toNamespacedPath(path), flags, req); -}; +} -fs.symlinkSync = function(target, path, type) { +function symlinkSync(target, path, type) { type = (typeof type === 'string' ? type : null); target = getPathFromURL(target); path = getPathFromURL(path); @@ -1002,9 +864,9 @@ fs.symlinkSync = function(target, path, type) { pathModule.toNamespacedPath(path), flags, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.link = function(existingPath, newPath, callback) { +function link(existingPath, newPath, callback) { callback = makeCallback(callback); existingPath = getPathFromURL(existingPath); @@ -1018,9 +880,9 @@ fs.link = function(existingPath, newPath, callback) { binding.link(pathModule.toNamespacedPath(existingPath), pathModule.toNamespacedPath(newPath), req); -}; +} -fs.linkSync = function(existingPath, newPath) { +function linkSync(existingPath, newPath) { existingPath = getPathFromURL(existingPath); newPath = getPathFromURL(newPath); validatePath(existingPath, 'existingPath'); @@ -1032,137 +894,125 @@ fs.linkSync = function(existingPath, newPath) { undefined, ctx); handleErrorFromBinding(ctx); return result; -}; +} -fs.unlink = function(path, callback) { +function unlink(path, callback) { callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); const req = new FSReqWrap(); req.oncomplete = callback; binding.unlink(pathModule.toNamespacedPath(path), req); -}; +} -fs.unlinkSync = function(path) { +function unlinkSync(path) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.fchmod = function(fd, mode, callback) { - mode = modeNum(mode); - validateUint32(fd, 'fd'); - validateUint32(mode, 'mode'); - // Values for mode < 0 are already checked via the validateUint32 function - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); +function fchmod(fd, mode, callback) { + validateInt32(fd, 'fd', 0); + mode = validateMode(mode, 'mode'); + callback = makeCallback(callback); const req = new FSReqWrap(); - req.oncomplete = makeCallback(callback); + req.oncomplete = callback; binding.fchmod(fd, mode, req); -}; +} -fs.fchmodSync = function(fd, mode) { - mode = modeNum(mode); - validateUint32(fd, 'fd'); - validateUint32(mode, 'mode'); - // Values for mode < 0 are already checked via the validateUint32 function - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); +function fchmodSync(fd, mode) { + validateInt32(fd, 'fd', 0); + mode = validateMode(mode, 'mode'); const ctx = {}; binding.fchmod(fd, mode, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -if (O_SYMLINK !== undefined) { - fs.lchmod = function(path, mode, callback) { - callback = maybeCallback(callback); - fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) { - if (err) { - callback(err); - return; - } - // Prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function(err) { - fs.close(fd, function(err2) { - callback(err || err2); - }); +function lchmod(path, mode, callback) { + callback = maybeCallback(callback); + fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) { + if (err) { + callback(err); + return; + } + // Prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function(err) { + fs.close(fd, function(err2) { + callback(err || err2); }); }); - }; + }); +} - fs.lchmodSync = function(path, mode) { - const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); +function lchmodSync(path, mode) { + const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); - // Prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - let ret; - try { - ret = fs.fchmodSync(fd, mode); - } finally { - fs.closeSync(fd); - } - return ret; - }; + // Prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + let ret; + try { + ret = fs.fchmodSync(fd, mode); + } finally { + fs.closeSync(fd); + } + return ret; } -fs.chmod = function(path, mode, callback) { - callback = makeCallback(callback); +function chmod(path, mode, callback) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode'); + callback = makeCallback(callback); const req = new FSReqWrap(); req.oncomplete = callback; binding.chmod(pathModule.toNamespacedPath(path), mode, req); -}; +} -fs.chmodSync = function(path, mode) { +function chmodSync(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode'); + const ctx = { path }; binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -if (O_SYMLINK !== undefined) { - fs.lchown = function(path, uid, gid, callback) { - callback = maybeCallback(callback); - fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) { - if (err) { - callback(err); - return; - } - // Prefer to return the chown error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchown(fd, uid, gid, function(err) { - fs.close(fd, function(err2) { - callback(err || err2); - }); +function lchown(path, uid, gid, callback) { + callback = maybeCallback(callback); + fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) { + if (err) { + callback(err); + return; + } + // Prefer to return the chown error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchown(fd, uid, gid, function(err) { + fs.close(fd, function(err2) { + callback(err || err2); }); }); - }; + }); +} - fs.lchownSync = function(path, uid, gid) { - const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); - let ret; - try { - ret = fs.fchownSync(fd, uid, gid); - } finally { - fs.closeSync(fd); - } - return ret; - }; +function lchownSync(path, uid, gid) { + const fd = fs.openSync(path, O_WRONLY | O_SYMLINK); + let ret; + try { + ret = fs.fchownSync(fd, uid, gid); + } finally { + fs.closeSync(fd); + } + return ret; } -fs.fchown = function(fd, uid, gid, callback) { +function fchown(fd, uid, gid, callback) { validateUint32(fd, 'fd'); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1170,9 +1020,9 @@ fs.fchown = function(fd, uid, gid, callback) { const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchown(fd, uid, gid, req); -}; +} -fs.fchownSync = function(fd, uid, gid) { +function fchownSync(fd, uid, gid) { validateUint32(fd, 'fd'); validateUint32(uid, 'uid'); validateUint32(gid, 'gid'); @@ -1180,9 +1030,9 @@ fs.fchownSync = function(fd, uid, gid) { const ctx = {}; binding.fchown(fd, uid, gid, undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.chown = function(path, uid, gid, callback) { +function chown(path, uid, gid, callback) { callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); @@ -1192,9 +1042,9 @@ fs.chown = function(path, uid, gid, callback) { const req = new FSReqWrap(); req.oncomplete = callback; binding.chown(pathModule.toNamespacedPath(path), uid, gid, req); -}; +} -fs.chownSync = function(path, uid, gid) { +function chownSync(path, uid, gid) { path = getPathFromURL(path); validatePath(path); validateUint32(uid, 'uid'); @@ -1202,12 +1052,9 @@ fs.chownSync = function(path, uid, gid) { const ctx = { path }; binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx); handleErrorFromBinding(ctx); -}; - -// exported for unit tests, not for public consumption -fs._toUnixTimestamp = toUnixTimestamp; +} -fs.utimes = function(path, atime, mtime, callback) { +function utimes(path, atime, mtime, callback) { callback = makeCallback(callback); path = getPathFromURL(path); validatePath(path); @@ -1218,9 +1065,9 @@ fs.utimes = function(path, atime, mtime, callback) { toUnixTimestamp(atime), toUnixTimestamp(mtime), req); -}; +} -fs.utimesSync = function(path, atime, mtime) { +function utimesSync(path, atime, mtime) { path = getPathFromURL(path); validatePath(path); const ctx = { path }; @@ -1228,25 +1075,25 @@ fs.utimesSync = function(path, atime, mtime) { toUnixTimestamp(atime), toUnixTimestamp(mtime), undefined, ctx); handleErrorFromBinding(ctx); -}; +} -fs.futimes = function(fd, atime, mtime, callback) { +function futimes(fd, atime, mtime, callback) { validateUint32(fd, 'fd'); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.futimes(fd, atime, mtime, req); -}; +} -fs.futimesSync = function(fd, atime, mtime) { +function futimesSync(fd, atime, mtime) { validateUint32(fd, 'fd'); atime = toUnixTimestamp(atime, 'atime'); mtime = toUnixTimestamp(mtime, 'mtime'); const ctx = {}; binding.futimes(fd, atime, mtime, undefined, ctx); handleErrorFromBinding(ctx); -}; +} function writeAll(fd, isUserFd, buffer, offset, length, position, callback) { // write(fd, buffer, offset, length, position, callback) @@ -1276,7 +1123,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, position, callback) { }); } -fs.writeFile = function(path, data, options, callback) { +function writeFile(path, data, options, callback) { callback = maybeCallback(callback || options); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); const flag = options.flag || 'w'; @@ -1295,30 +1142,30 @@ fs.writeFile = function(path, data, options, callback) { }); function writeFd(fd, isUserFd) { - var buffer = isUint8Array(data) ? + const buffer = isUint8Array(data) ? data : Buffer.from('' + data, options.encoding || 'utf8'); - var position = /a/.test(flag) ? null : 0; + const position = /a/.test(flag) ? null : 0; writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback); } -}; +} -fs.writeFileSync = function(path, data, options) { +function writeFileSync(path, data, options) { options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' }); const flag = options.flag || 'w'; - var isUserFd = isFd(path); // file descriptor ownership - var fd = isUserFd ? path : fs.openSync(path, flag, options.mode); + const isUserFd = isFd(path); // file descriptor ownership + const fd = isUserFd ? path : fs.openSync(path, flag, options.mode); if (!isUint8Array(data)) { data = Buffer.from('' + data, options.encoding || 'utf8'); } - var offset = 0; - var length = data.length; - var position = /a/.test(flag) ? null : 0; + let offset = 0; + let length = data.length; + let position = /a/.test(flag) ? null : 0; try { while (length > 0) { - var written = fs.writeSync(fd, data, offset, length, position); + const written = fs.writeSync(fd, data, offset, length, position); offset += written; length -= written; if (position !== null) { @@ -1328,9 +1175,9 @@ fs.writeFileSync = function(path, data, options) { } finally { if (!isUserFd) fs.closeSync(fd); } -}; +} -fs.appendFile = function(path, data, options, callback) { +function appendFile(path, data, options, callback) { callback = maybeCallback(callback || options); options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); @@ -1342,9 +1189,9 @@ fs.appendFile = function(path, data, options, callback) { options.flag = 'a'; fs.writeFile(path, data, options, callback); -}; +} -fs.appendFileSync = function(path, data, options) { +function appendFileSync(path, data, options) { options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' }); // Don't make changes directly on options object @@ -1355,84 +1202,9 @@ fs.appendFileSync = function(path, data, options) { options.flag = 'a'; fs.writeFileSync(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) { - // TODO(joyeecheung): we may check self._handle.initialized here - // and return if that is false. This allows us to avoid firing the event - // after the handle is closed, and to fire both UV_RENAME and UV_CHANGE - // if they are set by libuv at the same time. - if (status < 0) { - self._handle.close(); - const error = errors.uvException({ - errno: status, - syscall: 'watch', - path: filename - }); - error.filename = filename; - self.emit('error', error); - } else { - self.emit('change', eventType, filename); - } - }; } -util.inherits(FSWatcher, EventEmitter); - -// FIXME(joyeecheung): this method is not documented. -// At the moment if filename is undefined, we -// 1. Throw an Error if it's the first time .start() is called -// 2. Return silently if .start() has already been called -// on a valid filename and the wrap has been initialized -// This method is a noop if the watcher has already been started. -FSWatcher.prototype.start = function(filename, - persistent, - recursive, - encoding) { - lazyAssert()(this._handle instanceof FSEvent, 'handle must be a FSEvent'); - if (this._handle.initialized) { - return; - } - - filename = getPathFromURL(filename); - validatePath(filename, 'filename'); - const err = this._handle.start(pathModule.toNamespacedPath(filename), - persistent, - recursive, - encoding); - if (err) { - const error = errors.uvException({ - errno: err, - syscall: 'watch', - path: filename - }); - error.filename = filename; - throw error; - } -}; - -// This method is a noop if the watcher has not been started. -FSWatcher.prototype.close = function() { - lazyAssert()(this._handle instanceof FSEvent, 'handle must be a FSEvent'); - if (!this._handle.initialized) { - return; - } - this._handle.close(); - process.nextTick(emitCloseNT, this); -}; - -function emitCloseNT(self) { - self.emit('close'); -} - -fs.watch = function(filename, options, listener) { +function watch(filename, options, listener) { if (typeof options === 'function') { listener = options; } @@ -1444,7 +1216,9 @@ fs.watch = function(filename, options, listener) { if (options.persistent === undefined) options.persistent = true; if (options.recursive === undefined) options.recursive = false; - const watcher = new FSWatcher(); + if (!watchers) + watchers = require('internal/fs/watchers'); + const watcher = new watchers.FSWatcher(); watcher.start(filename, options.persistent, options.recursive, @@ -1455,94 +1229,18 @@ fs.watch = function(filename, options, listener) { } return watcher; -}; - - -// Stat Change Watchers - -function emitStop(self) { - self.emit('stop'); } -function StatWatcher() { - EventEmitter.call(this); - - var self = this; - this._handle = new binding.StatWatcher(); - - // uv_fs_poll is a little more powerful than ev_stat but we curb it for - // the sake of backwards compatibility - var oldStatus = -1; - - this._handle.onchange = function(newStatus, stats) { - if (oldStatus === -1 && - newStatus === -1 && - stats[2/* new nlink */] === stats[16/* old nlink */]) return; - - oldStatus = newStatus; - self.emit('change', getStatsFromBinding(stats), - getStatsFromBinding(stats, kFsStatsFieldsLength)); - }; - - this._handle.onstop = function() { - process.nextTick(emitStop, self); - }; -} -util.inherits(StatWatcher, EventEmitter); - - -// FIXME(joyeecheung): this method is not documented. -// At the moment if filename is undefined, we -// 1. Throw an Error if it's the first time .start() is called -// 2. Return silently if .start() has already been called -// on a valid filename and the wrap has been initialized -// This method is a noop if the watcher has already been started. -StatWatcher.prototype.start = function(filename, persistent, interval) { - lazyAssert()(this._handle instanceof binding.StatWatcher, - 'handle must be a StatWatcher'); - if (this._handle.isActive) { - return; - } - - filename = getPathFromURL(filename); - validatePath(filename, 'filename'); - validateUint32(interval, 'interval'); - const err = this._handle.start(pathModule.toNamespacedPath(filename), - persistent, interval); - if (err) { - const error = errors.uvException({ - errno: err, - syscall: 'watch', - path: filename - }); - error.filename = filename; - throw error; - } -}; - -// FIXME(joyeecheung): this method is not documented while there is -// another documented fs.unwatchFile(). The counterpart in -// FSWatcher is .close() -// This method is a noop if the watcher has not been started. -StatWatcher.prototype.stop = function() { - lazyAssert()(this._handle instanceof binding.StatWatcher, - 'handle must be a StatWatcher'); - if (!this._handle.isActive) { - return; - } - this._handle.stop(); -}; - const statWatchers = new Map(); -fs.watchFile = function(filename, options, listener) { +function watchFile(filename, options, listener) { filename = getPathFromURL(filename); validatePath(filename); filename = pathModule.resolve(filename); - var stat; + let stat; - var defaults = { + const defaults = { // Poll interval in milliseconds. 5007 is what libev used to use. It's // a little on the slow side but let's stick with it for now to keep // behavioral changes to a minimum. @@ -1551,7 +1249,7 @@ fs.watchFile = function(filename, options, listener) { }; if (options !== null && typeof options === 'object') { - options = util._extend(defaults, options); + options = _extend(defaults, options); } else { listener = options; options = defaults; @@ -1564,20 +1262,22 @@ fs.watchFile = function(filename, options, listener) { stat = statWatchers.get(filename); if (stat === undefined) { - stat = new StatWatcher(); + if (!watchers) + watchers = require('internal/fs/watchers'); + stat = new watchers.StatWatcher(); stat.start(filename, options.persistent, options.interval); statWatchers.set(filename, stat); } stat.addListener('change', listener); return stat; -}; +} -fs.unwatchFile = function(filename, listener) { +function unwatchFile(filename, listener) { filename = getPathFromURL(filename); validatePath(filename); filename = pathModule.resolve(filename); - var stat = statWatchers.get(filename); + const stat = statWatchers.get(filename); if (stat === undefined) return; @@ -1591,10 +1291,10 @@ fs.unwatchFile = function(filename, listener) { stat.stop(); statWatchers.delete(filename); } -}; +} -var splitRoot; +let splitRoot; if (isWindows) { // Regex to find the device root on Windows (e.g. 'c:\\'), including trailing // slash. @@ -1624,7 +1324,7 @@ function encodeRealpathResult(result, options) { } // Finds the next portion of a (partial) path, up to the next path delimiter -var nextPart; +let nextPart; if (isWindows) { nextPart = function nextPart(p, i) { for (; i < p.length; ++i) { @@ -1641,7 +1341,7 @@ if (isWindows) { } const emptyObj = Object.create(null); -fs.realpathSync = function realpathSync(p, options) { +function realpathSync(p, options) { if (!options) options = emptyObj; else @@ -1664,13 +1364,13 @@ fs.realpathSync = function realpathSync(p, options) { const original = p; // current character position in p - var pos; + let pos; // the partial path so far, including a trailing slash if any - var current; + let current; // the partial path without a trailing slash (except when pointing at a root) - var base; + let base; // the partial path scanned in the previous round, with slash - var previous; + let previous; // Skip over roots current = base = splitRoot(p); @@ -1689,10 +1389,10 @@ fs.realpathSync = function realpathSync(p, options) { // NB: p.length changes. while (pos < p.length) { // find the next part - var result = nextPart(p, pos); + const result = nextPart(p, pos); previous = current; if (result === -1) { - var last = p.slice(pos); + const last = p.slice(pos); current += last; base = previous + last; pos = p.length; @@ -1711,15 +1411,15 @@ fs.realpathSync = function realpathSync(p, options) { continue; } - var resolvedLink; - var maybeCachedResolved = cache && cache.get(base); + let resolvedLink; + const maybeCachedResolved = cache && cache.get(base); if (maybeCachedResolved) { resolvedLink = maybeCachedResolved; } else { // Use stats array directly to avoid creating an fs.Stats instance just // for our internal use. - var baseLong = pathModule.toNamespacedPath(base); + const baseLong = pathModule.toNamespacedPath(base); const ctx = { path: base }; const stats = binding.lstat(baseLong, undefined, ctx); handleErrorFromBinding(ctx); @@ -1732,11 +1432,11 @@ fs.realpathSync = function realpathSync(p, options) { // read the link if it wasn't read before // dev/ino always return 0 on windows, so skip the check. - var linkTarget = null; - var id; + let linkTarget = null; + let id; if (!isWindows) { - var dev = stats[0].toString(32); - var ino = stats[7].toString(32); + const dev = stats[0].toString(32); + const ino = stats[7].toString(32); id = `${dev}:${ino}`; if (seenLinks[id]) { linkTarget = seenLinks[id]; @@ -1773,10 +1473,10 @@ fs.realpathSync = function realpathSync(p, options) { if (cache) cache.set(original, p); return encodeRealpathResult(p, options); -}; +} -fs.realpathSync.native = function(path, options) { +realpathSync.native = function(path, options) { options = getOptions(options, {}); path = getPathFromURL(path); validatePath(path); @@ -1787,7 +1487,7 @@ fs.realpathSync.native = function(path, options) { }; -fs.realpath = function realpath(p, options, callback) { +function realpath(p, options, callback) { callback = maybeCallback(typeof options === 'function' ? options : callback); if (!options) options = emptyObj; @@ -1804,13 +1504,13 @@ fs.realpath = function realpath(p, options, callback) { const knownHard = Object.create(null); // current character position in p - var pos; + let pos; // the partial path so far, including a trailing slash if any - var current; + let current; // the partial path without a trailing slash (except when pointing at a root) - var base; + let base; // the partial path scanned in the previous round, with slash - var previous; + let previous; current = base = splitRoot(p); pos = current.length; @@ -1835,10 +1535,10 @@ fs.realpath = function realpath(p, options, callback) { } // find the next part - var result = nextPart(p, pos); + const result = nextPart(p, pos); previous = current; if (result === -1) { - var last = p.slice(pos); + const last = p.slice(pos); current += last; base = previous + last; pos = p.length; @@ -1874,8 +1574,8 @@ fs.realpath = function realpath(p, options, callback) { // dev/ino always return 0 on windows, so skip the check. let id; if (!isWindows) { - var dev = stats.dev.toString(32); - var ino = stats.ino.toString(32); + const dev = stats.dev.toString(32); + const ino = stats.ino.toString(32); id = `${dev}:${ino}`; if (seenLinks[id]) { return gotTarget(null, seenLinks[id], base); @@ -1894,8 +1594,7 @@ fs.realpath = function realpath(p, options, callback) { function gotTarget(err, target, base) { if (err) return callback(err); - var resolvedLink = pathModule.resolve(previous, target); - gotResolvedLink(resolvedLink); + gotResolvedLink(pathModule.resolve(previous, target)); } function gotResolvedLink(resolvedLink) { @@ -1915,10 +1614,10 @@ fs.realpath = function realpath(p, options, callback) { process.nextTick(LOOP); } } -}; +} -fs.realpath.native = function(path, options, callback) { +realpath.native = function(path, options, callback) { callback = makeCallback(callback || options); options = getOptions(options, {}); path = getPathFromURL(path); @@ -1928,20 +1627,20 @@ fs.realpath.native = function(path, options, callback) { return binding.realpath(path, options.encoding, req); }; -fs.mkdtemp = function(prefix, options, callback) { +function mkdtemp(prefix, options, callback) { callback = makeCallback(typeof options === 'function' ? options : callback); options = getOptions(options, {}); if (!prefix || typeof prefix !== 'string') { throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix); } nullCheck(prefix, 'prefix'); - var req = new FSReqWrap(); + const req = new FSReqWrap(); req.oncomplete = callback; binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req); -}; +} -fs.mkdtempSync = function(prefix, options) { +function mkdtempSync(prefix, options) { options = getOptions(options, {}); if (!prefix || typeof prefix !== 'string') { throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix); @@ -1953,24 +1652,10 @@ fs.mkdtempSync = function(prefix, options) { undefined, ctx); handleErrorFromBinding(ctx); return result; -}; - - -// Define copyFile() flags. -Object.defineProperties(fs.constants, { - COPYFILE_EXCL: { enumerable: true, value: UV_FS_COPYFILE_EXCL }, - COPYFILE_FICLONE: { - enumerable: true, - value: UV_FS_COPYFILE_FICLONE - }, - COPYFILE_FICLONE_FORCE: { - enumerable: true, - value: UV_FS_COPYFILE_FICLONE_FORCE - } -}); +} -fs.copyFile = function(src, dest, flags, callback) { +function copyFile(src, dest, flags, callback) { if (typeof flags === 'function') { callback = flags; flags = 0; @@ -1989,10 +1674,10 @@ fs.copyFile = function(src, dest, flags, callback) { const req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.copyFile(src, dest, flags, req); -}; +} -fs.copyFileSync = function(src, dest, flags) { +function copyFileSync(src, dest, flags) { src = getPathFromURL(src); dest = getPathFromURL(dest); validatePath(src, 'src'); @@ -2005,361 +1690,170 @@ fs.copyFileSync = function(src, dest, flags) { flags = flags | 0; binding.copyFile(src, dest, flags, undefined, ctx); handleErrorFromBinding(ctx); -}; - - -var pool; - -function allocNewPool(poolSize) { - pool = Buffer.allocUnsafe(poolSize); - pool.used = 0; } - -fs.createReadStream = function(path, options) { - return new ReadStream(path, options); -}; - -util.inherits(ReadStream, Readable); -fs.ReadStream = ReadStream; - -function ReadStream(path, options) { - if (!(this instanceof ReadStream)) - return new ReadStream(path, options); - - // a little bit bigger buffer and water marks by default - options = copyObject(getOptions(options, {})); - if (options.highWaterMark === undefined) - options.highWaterMark = 64 * 1024; - - // for backwards compat do not emit close on destroy. - options.emitClose = false; - - Readable.call(this, options); - - // path will be ignored when fd is specified, so it can be falsy - 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; - - this.start = options.start; - this.end = options.end; - this.autoClose = options.autoClose === undefined ? true : options.autoClose; - this.pos = undefined; - this.bytesRead = 0; - this.closed = false; - - if (this.start !== undefined) { - if (typeof this.start !== 'number' || Number.isNaN(this.start)) { - throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); - } - if (this.end === undefined) { - this.end = Infinity; - } else if (typeof this.end !== 'number' || Number.isNaN(this.end)) { - throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end); - } - - if (this.start > this.end) { - const errVal = `{start: ${this.start}, end: ${this.end}}`; - throw new ERR_OUT_OF_RANGE('start', '<= "end"', errVal); - } - - this.pos = this.start; +function lazyLoadStreams() { + if (!ReadStream) { + ({ ReadStream, WriteStream } = require('internal/fs/streams')); + [ FileReadStream, FileWriteStream ] = [ ReadStream, WriteStream ]; } - - // Backwards compatibility: Make sure `end` is a number regardless of `start`. - // TODO(addaleax): Make the above typecheck not depend on `start` instead. - // (That is a semver-major change). - if (typeof this.end !== 'number') - this.end = Infinity; - else if (Number.isNaN(this.end)) - throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end); - - if (typeof this.fd !== 'number') - this.open(); - - this.on('end', function() { - if (this.autoClose) { - this.destroy(); - } - }); } -fs.FileReadStream = fs.ReadStream; // support the legacy name - -ReadStream.prototype.open = function() { - var self = this; - fs.open(this.path, this.flags, this.mode, function(er, fd) { - if (er) { - if (self.autoClose) { - self.destroy(); - } - self.emit('error', er); - return; - } - - self.fd = fd; - self.emit('open', fd); - self.emit('ready'); - // start the flow of data. - self.read(); - }); -}; - -ReadStream.prototype._read = function(n) { - if (typeof this.fd !== 'number') { - return this.once('open', function() { - this._read(n); - }); - } - - if (this.destroyed) - return; - - if (!pool || pool.length - pool.used < kMinPoolSpace) { - // discard the old pool. - allocNewPool(this.readableHighWaterMark); - } - - // Grab another reference to the pool in the case that while we're - // in the thread pool another read() finishes up the pool, and - // allocates a new one. - var thisPool = pool; - var toRead = Math.min(pool.length - pool.used, n); - var start = pool.used; - - if (this.pos !== undefined) - toRead = Math.min(this.end - this.pos + 1, toRead); - else - toRead = Math.min(this.end - this.bytesRead + 1, toRead); - - // already read everything we were supposed to read! - // treat as EOF. - if (toRead <= 0) - return this.push(null); - - // the actual read. - fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { - if (er) { - if (this.autoClose) { - this.destroy(); - } - this.emit('error', er); - } else { - var b = null; - if (bytesRead > 0) { - this.bytesRead += bytesRead; - b = thisPool.slice(start, start + bytesRead); - } - - this.push(b); - } - }); - - // move the pool positions, and internal position for reading. - if (this.pos !== undefined) - this.pos += toRead; - pool.used += toRead; -}; - -ReadStream.prototype._destroy = function(err, cb) { - const isOpen = typeof this.fd !== 'number'; - if (isOpen) { - this.once('open', closeFsStream.bind(null, this, cb, err)); - return; - } - - closeFsStream(this, cb, err); - this.fd = null; -}; - -function closeFsStream(stream, cb, err) { - fs.close(stream.fd, (er) => { - er = er || err; - cb(er); - stream.closed = true; - if (!er) - stream.emit('close'); - }); +function createReadStream(path, options) { + lazyLoadStreams(); + return new ReadStream(path, options); } -ReadStream.prototype.close = function(cb) { - this.destroy(null, cb); -}; - -fs.createWriteStream = function(path, options) { +function createWriteStream(path, options) { + lazyLoadStreams(); return new WriteStream(path, options); -}; - -util.inherits(WriteStream, Writable); -fs.WriteStream = WriteStream; -function WriteStream(path, options) { - if (!(this instanceof WriteStream)) - return new WriteStream(path, options); - - options = copyObject(getOptions(options, {})); - - // for backwards compat do not emit close on destroy. - options.emitClose = false; - - Writable.call(this, options); - - // path will be ignored when fd is specified, so it can be falsy - 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; - - this.start = options.start; - this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; - this.pos = undefined; - this.bytesWritten = 0; - this.closed = false; - - if (this.start !== undefined) { - if (typeof this.start !== 'number') { - throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); - } - if (this.start < 0) { - const errVal = `{start: ${this.start}}`; - throw new ERR_OUT_OF_RANGE('start', '>= 0', errVal); - } - - this.pos = this.start; - } - - if (options.encoding) - this.setDefaultEncoding(options.encoding); - - if (typeof this.fd !== 'number') - this.open(); } -fs.FileWriteStream = fs.WriteStream; // support the legacy name - -WriteStream.prototype._final = function(callback) { - if (this.autoClose) { - this.destroy(); - } - - callback(); -}; - -WriteStream.prototype.open = function() { - fs.open(this.path, this.flags, this.mode, (er, fd) => { - if (er) { - if (this.autoClose) { - this.destroy(); - } - this.emit('error', er); - return; - } - - this.fd = fd; - this.emit('open', fd); - this.emit('ready'); - }); -}; - - -WriteStream.prototype._write = function(data, encoding, cb) { - if (!(data instanceof Buffer)) { - const err = new ERR_INVALID_ARG_TYPE('data', 'Buffer', data); - return this.emit('error', err); - } - - if (typeof this.fd !== 'number') { - return this.once('open', function() { - this._write(data, encoding, cb); - }); - } - - fs.write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { - if (er) { - if (this.autoClose) { - this.destroy(); - } - return cb(er); - } - this.bytesWritten += bytes; - cb(); - }); - - if (this.pos !== undefined) - this.pos += data.length; -}; +module.exports = fs = { + appendFile, + appendFileSync, + access, + accessSync, + chown, + chownSync, + chmod, + chmodSync, + close, + closeSync, + copyFile, + copyFileSync, + createReadStream, + createWriteStream, + exists, + existsSync, + fchown, + fchownSync, + fchmod, + fchmodSync, + fdatasync, + fdatasyncSync, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + futimes, + futimesSync, + lchown: constants.O_SYMLINK !== undefined ? lchown : undefined, + lchownSync: constants.O_SYMLINK !== undefined ? lchownSync : undefined, + lchmod: constants.O_SYMLINK !== undefined ? lchmod : undefined, + lchmodSync: constants.O_SYMLINK !== undefined ? lchmodSync : undefined, + link, + linkSync, + lstat, + lstatSync, + mkdir, + mkdirSync, + mkdtemp, + mkdtempSync, + open, + openSync, + readdir, + readdirSync, + read, + readSync, + readFile, + readFileSync, + readlink, + readlinkSync, + realpath, + realpathSync, + rename, + renameSync, + rmdir, + rmdirSync, + stat, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + unwatchFile, + unlink, + unlinkSync, + utimes, + utimesSync, + watch, + watchFile, + writeFile, + writeFileSync, + write, + writeSync, + Stats, -function writev(fd, chunks, position, callback) { - function wrapper(err, written) { - // Retain a reference to chunks so that they can't be GC'ed too soon. - callback(err, written || 0, chunks); - } + get ReadStream() { + lazyLoadStreams(); + return ReadStream; + }, - const req = new FSReqWrap(); - req.oncomplete = wrapper; - binding.writeBuffers(fd, chunks, position, req); -} + set ReadStream(val) { + ReadStream = val; + }, + get WriteStream() { + lazyLoadStreams(); + return WriteStream; + }, -WriteStream.prototype._writev = function(data, cb) { - if (typeof this.fd !== 'number') { - return this.once('open', function() { - this._writev(data, cb); - }); - } + set WriteStream(val) { + WriteStream = val; + }, - const self = this; - const len = data.length; - const chunks = new Array(len); - var size = 0; + // Legacy names... these have to be separate because of how graceful-fs + // (and possibly other) modules monkey patch the values. + get FileReadStream() { + lazyLoadStreams(); + return FileReadStream; + }, - for (var i = 0; i < len; i++) { - var chunk = data[i].chunk; + set FileReadStream(val) { + FileReadStream = val; + }, - chunks[i] = chunk; - size += chunk.length; - } + get FileWriteStream() { + lazyLoadStreams(); + return FileWriteStream; + }, - writev(this.fd, chunks, this.pos, function(er, bytes) { - if (er) { - self.destroy(); - return cb(er); - } - self.bytesWritten += bytes; - cb(); - }); + set FileWriteStream(val) { + FileWriteStream = val; + }, - if (this.pos !== undefined) - this.pos += size; + // For tests + _toUnixTimestamp: toUnixTimestamp }; - -WriteStream.prototype._destroy = ReadStream.prototype._destroy; -WriteStream.prototype.close = function(cb) { - if (cb) { - if (this.closed) { - process.nextTick(cb); - return; - } else { - this.on('close', cb); +Object.defineProperties(fs, { + F_OK: { enumerable: true, value: F_OK || 0 }, + R_OK: { enumerable: true, value: R_OK || 0 }, + W_OK: { enumerable: true, value: W_OK || 0 }, + X_OK: { enumerable: true, value: X_OK || 0 }, + constants: { + configurable: false, + enumerable: true, + value: constants + }, + promises: { + configurable: true, + enumerable: false, + get() { + if (promisesWarn) { + promises = require('internal/fs/promises'); + promisesWarn = false; + process.emitWarning('The fs.promises API is experimental', + 'ExperimentalWarning'); + } + return promises; } } - - // If we are not autoClosing, we should call - // destroy on 'finish'. - if (!this.autoClose) { - this.on('finish', this.destroy.bind(this)); - } - - // we use end() instead of destroy() because of - // https://github.com/nodejs/node/issues/2006 - this.end(); -}; - -// There is no shutdown() for files. -WriteStream.prototype.destroySoon = WriteStream.prototype.end; +}); // SyncWriteStream is internal. DO NOT USE. // This undocumented API was never intended to be made public. diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 4d95529c4b58e6..6477c2d8282f43 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -10,10 +10,19 @@ // process.binding(), process._linkedBinding(), internalBinding() and // NativeModule. And then { internalBinding, NativeModule } will be passed // into this bootstrapper to bootstrap Node.js core. - 'use strict'; -(function bootstrapNodeJSCore(process, { internalBinding, NativeModule }) { +(function bootstrapNodeJSCore(process, + // bootstrapper properties... destructured to + // avoid retaining a reference to the bootstrap + // object. + { _setupProcessObject, _setupNextTick, + _setupPromises, _chdir, _cpuUsage, + _hrtime, _memoryUsage, _rawDebug, + _umask, _initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups, + _shouldAbortOnUncaughtToggle }, + { internalBinding, NativeModule }) { const exceptionHandlerState = { captureFn: null }; function startup() { @@ -36,10 +45,24 @@ const _process = NativeModule.require('internal/process'); _process.setupConfig(NativeModule._source); _process.setupSignalHandlers(); - _process.setupUncaughtExceptionCapture(exceptionHandlerState); + _process.setupUncaughtExceptionCapture(exceptionHandlerState, + _shouldAbortOnUncaughtToggle); NativeModule.require('internal/process/warning').setup(); - NativeModule.require('internal/process/next_tick').setup(); + NativeModule.require('internal/process/next_tick').setup(_setupNextTick, + _setupPromises); NativeModule.require('internal/process/stdio').setup(); + NativeModule.require('internal/process/methods').setup(_chdir, + _cpuUsage, + _hrtime, + _memoryUsage, + _rawDebug, + _umask, + _initgroups, + _setegid, + _seteuid, + _setgid, + _setuid, + _setgroups); const perf = process.binding('performance'); const { @@ -54,9 +77,9 @@ NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END } = perf.constants; - _process.setup_hrtime(); - _process.setup_cpuUsage(); - _process.setupMemoryUsage(); + _process.setup_hrtime(_hrtime); + _process.setup_cpuUsage(_cpuUsage); + _process.setupMemoryUsage(_memoryUsage); _process.setupKillAndExit(); if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); @@ -78,7 +101,7 @@ } _process.setupChannel(); - _process.setupRawDebug(); + _process.setupRawDebug(_rawDebug); const browserGlobals = !process._noBrowserGlobals; if (browserGlobals) { @@ -293,7 +316,7 @@ } function setupProcessObject() { - process._setupProcessObject(pushValueToArray); + _setupProcessObject(pushValueToArray); function pushValueToArray() { for (var i = 0; i < arguments.length; i++) diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index 4c0a256f5ad66c..43956dae3f4d18 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -12,8 +12,7 @@ const { Buffer, kMaxLength } = require('buffer'); const { ERR_FS_FILE_TOO_LARGE, ERR_INVALID_ARG_TYPE, - ERR_METHOD_NOT_IMPLEMENTED, - ERR_OUT_OF_RANGE + ERR_METHOD_NOT_IMPLEMENTED } = require('internal/errors').codes; const { getPathFromURL } = require('internal/url'); const { isUint8Array } = require('internal/util/types'); @@ -21,7 +20,6 @@ const { copyObject, getOptions, getStatsFromBinding, - modeNum, nullCheck, preprocessSymlinkDestination, stringToFlags, @@ -34,7 +32,8 @@ const { } = require('internal/fs/utils'); const { isUint32, - validateInt32, + validateMode, + validateInteger, validateUint32 } = require('internal/validators'); const pathModule = require('path'); @@ -191,10 +190,9 @@ async function copyFile(src, dest, flags) { // Note that unlike fs.open() which uses numeric file descriptors, // fsPromises.open() uses the fs.FileHandle class. async function open(path, flags, mode) { - mode = modeNum(mode, 0o666); path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode', 0o666); return new FileHandle( await binding.openFileHandle(pathModule.toNamespacedPath(path), stringToFlags(flags), @@ -265,7 +263,7 @@ async function truncate(path, len = 0) { async function ftruncate(handle, len = 0) { validateFileHandle(handle); - validateInt32(len, 'len'); + validateInteger(len, 'len'); len = Math.max(0, len); return binding.ftruncate(handle.fd, len, kUsePromises); } @@ -287,10 +285,9 @@ async function fsync(handle) { } async function mkdir(path, mode) { - mode = modeNum(mode, 0o777); path = getPathFromURL(path); validatePath(path); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode', 0o777); return binding.mkdir(pathModule.toNamespacedPath(path), mode, kUsePromises); } @@ -361,19 +358,15 @@ async function unlink(path) { } async function fchmod(handle, mode) { - mode = modeNum(mode); validateFileHandle(handle); - validateUint32(mode, 'mode'); - if (mode > 0o777) - throw new ERR_OUT_OF_RANGE('mode', undefined, mode); + mode = validateMode(mode, 'mode'); return binding.fchmod(handle.fd, mode, kUsePromises); } async function chmod(path, mode) { path = getPathFromURL(path); validatePath(path); - mode = modeNum(mode); - validateUint32(mode, 'mode'); + mode = validateMode(mode, 'mode'); return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises); } @@ -474,31 +467,22 @@ module.exports = { access, copyFile, open, - read, - write, rename, truncate, - ftruncate, rmdir, - fdatasync, - fsync, mkdir, readdir, readlink, symlink, - fstat, lstat, stat, link, unlink, - fchmod, chmod, lchmod, lchown, - fchown, chown, utimes, - futimes, realpath, mkdtemp, writeFile, diff --git a/lib/internal/fs/read_file_context.js b/lib/internal/fs/read_file_context.js new file mode 100644 index 00000000000000..b3e81a5db168ab --- /dev/null +++ b/lib/internal/fs/read_file_context.js @@ -0,0 +1,108 @@ +'use strict'; + +const { Buffer } = require('buffer'); +const { FSReqWrap, close, read } = process.binding('fs'); + +const kReadFileBufferLength = 8 * 1024; + +function readFileAfterRead(err, bytesRead) { + const context = this.context; + + if (err) + return context.close(err); + + if (bytesRead === 0) + return context.close(); + + context.pos += bytesRead; + + if (context.size !== 0) { + if (context.pos === context.size) + context.close(); + else + context.read(); + } else { + // unknown size, just read until we don't get bytes. + context.buffers.push(context.buffer.slice(0, bytesRead)); + context.read(); + } +} + +function readFileAfterClose(err) { + const context = this.context; + const callback = context.callback; + let buffer = null; + + if (context.err || err) + return callback(context.err || err); + + try { + if (context.size === 0) + buffer = Buffer.concat(context.buffers, context.pos); + else if (context.pos < context.size) + buffer = context.buffer.slice(0, context.pos); + else + buffer = context.buffer; + + if (context.encoding) + buffer = buffer.toString(context.encoding); + } catch (err) { + return callback(err); + } + + callback(null, buffer); +} + +class ReadFileContext { + constructor(callback, encoding) { + this.fd = undefined; + this.isUserFd = undefined; + this.size = undefined; + this.callback = callback; + this.buffers = null; + this.buffer = null; + this.pos = 0; + this.encoding = encoding; + this.err = null; + } + + read() { + let buffer; + let offset; + let length; + + if (this.size === 0) { + buffer = this.buffer = Buffer.allocUnsafeSlow(kReadFileBufferLength); + offset = 0; + length = kReadFileBufferLength; + } else { + buffer = this.buffer; + offset = this.pos; + length = Math.min(kReadFileBufferLength, this.size - this.pos); + } + + const req = new FSReqWrap(); + req.oncomplete = readFileAfterRead; + req.context = this; + + read(this.fd, buffer, offset, length, -1, req); + } + + close(err) { + const req = new FSReqWrap(); + req.oncomplete = readFileAfterClose; + req.context = this; + this.err = err; + + if (this.isUserFd) { + process.nextTick(function tick() { + req.oncomplete(null); + }); + return; + } + + close(this.fd, req); + } +} + +module.exports = ReadFileContext; diff --git a/lib/internal/fs/streams.js b/lib/internal/fs/streams.js new file mode 100644 index 00000000000000..e3fa83fd26f406 --- /dev/null +++ b/lib/internal/fs/streams.js @@ -0,0 +1,362 @@ +'use strict'; + +const { + FSReqWrap, + writeBuffers +} = process.binding('fs'); +const { + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE +} = require('internal/errors').codes; +const fs = require('fs'); +const { Buffer } = require('buffer'); +const { + copyObject, + getOptions, +} = require('internal/fs/utils'); +const { Readable, Writable } = require('stream'); +const { getPathFromURL } = require('internal/url'); +const util = require('util'); + +const kMinPoolSpace = 128; + +let pool; + +function allocNewPool(poolSize) { + pool = Buffer.allocUnsafe(poolSize); + pool.used = 0; +} + +function ReadStream(path, options) { + if (!(this instanceof ReadStream)) + return new ReadStream(path, options); + + // a little bit bigger buffer and water marks by default + options = copyObject(getOptions(options, {})); + if (options.highWaterMark === undefined) + options.highWaterMark = 64 * 1024; + + // for backwards compat do not emit close on destroy. + options.emitClose = false; + + Readable.call(this, options); + + // path will be ignored when fd is specified, so it can be falsy + 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; + + this.start = options.start; + this.end = options.end; + this.autoClose = options.autoClose === undefined ? true : options.autoClose; + this.pos = undefined; + this.bytesRead = 0; + this.closed = false; + + if (this.start !== undefined) { + if (typeof this.start !== 'number' || Number.isNaN(this.start)) { + throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); + } + if (this.end === undefined) { + this.end = Infinity; + } else if (typeof this.end !== 'number' || Number.isNaN(this.end)) { + throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end); + } + + if (this.start > this.end) { + const errVal = `{start: ${this.start}, end: ${this.end}}`; + throw new ERR_OUT_OF_RANGE('start', '<= "end"', errVal); + } + + this.pos = this.start; + } + + // Backwards compatibility: Make sure `end` is a number regardless of `start`. + // TODO(addaleax): Make the above typecheck not depend on `start` instead. + // (That is a semver-major change). + if (typeof this.end !== 'number') + this.end = Infinity; + else if (Number.isNaN(this.end)) + throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end); + + if (typeof this.fd !== 'number') + this.open(); + + this.on('end', function() { + if (this.autoClose) { + this.destroy(); + } + }); +} +util.inherits(ReadStream, Readable); + +ReadStream.prototype.open = function() { + fs.open(this.path, this.flags, this.mode, (er, fd) => { + if (er) { + if (this.autoClose) { + this.destroy(); + } + this.emit('error', er); + return; + } + + this.fd = fd; + this.emit('open', fd); + this.emit('ready'); + // start the flow of data. + this.read(); + }); +}; + +ReadStream.prototype._read = function(n) { + if (typeof this.fd !== 'number') { + return this.once('open', function() { + this._read(n); + }); + } + + if (this.destroyed) + return; + + if (!pool || pool.length - pool.used < kMinPoolSpace) { + // discard the old pool. + allocNewPool(this.readableHighWaterMark); + } + + // Grab another reference to the pool in the case that while we're + // in the thread pool another read() finishes up the pool, and + // allocates a new one. + const thisPool = pool; + let toRead = Math.min(pool.length - pool.used, n); + const start = pool.used; + + if (this.pos !== undefined) + toRead = Math.min(this.end - this.pos + 1, toRead); + else + toRead = Math.min(this.end - this.bytesRead + 1, toRead); + + // already read everything we were supposed to read! + // treat as EOF. + if (toRead <= 0) + return this.push(null); + + // the actual read. + fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { + if (er) { + if (this.autoClose) { + this.destroy(); + } + this.emit('error', er); + } else { + let b = null; + if (bytesRead > 0) { + this.bytesRead += bytesRead; + b = thisPool.slice(start, start + bytesRead); + } + + this.push(b); + } + }); + + // move the pool positions, and internal position for reading. + if (this.pos !== undefined) + this.pos += toRead; + pool.used += toRead; +}; + +ReadStream.prototype._destroy = function(err, cb) { + const isOpen = typeof this.fd !== 'number'; + if (isOpen) { + this.once('open', closeFsStream.bind(null, this, cb, err)); + return; + } + + closeFsStream(this, cb, err); + this.fd = null; +}; + +function closeFsStream(stream, cb, err) { + fs.close(stream.fd, (er) => { + er = er || err; + cb(er); + stream.closed = true; + if (!er) + stream.emit('close'); + }); +} + +ReadStream.prototype.close = function(cb) { + this.destroy(null, cb); +}; + +function WriteStream(path, options) { + if (!(this instanceof WriteStream)) + return new WriteStream(path, options); + + options = copyObject(getOptions(options, {})); + + // for backwards compat do not emit close on destroy. + options.emitClose = false; + + Writable.call(this, options); + + // path will be ignored when fd is specified, so it can be falsy + 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; + + this.start = options.start; + this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; + this.pos = undefined; + this.bytesWritten = 0; + this.closed = false; + + if (this.start !== undefined) { + if (typeof this.start !== 'number') { + throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start); + } + if (this.start < 0) { + const errVal = `{start: ${this.start}}`; + throw new ERR_OUT_OF_RANGE('start', '>= 0', errVal); + } + + this.pos = this.start; + } + + if (options.encoding) + this.setDefaultEncoding(options.encoding); + + if (typeof this.fd !== 'number') + this.open(); +} +util.inherits(WriteStream, Writable); + +WriteStream.prototype._final = function(callback) { + if (this.autoClose) { + this.destroy(); + } + + callback(); +}; + +WriteStream.prototype.open = function() { + fs.open(this.path, this.flags, this.mode, (er, fd) => { + if (er) { + if (this.autoClose) { + this.destroy(); + } + this.emit('error', er); + return; + } + + this.fd = fd; + this.emit('open', fd); + this.emit('ready'); + }); +}; + + +WriteStream.prototype._write = function(data, encoding, cb) { + if (!(data instanceof Buffer)) { + const err = new ERR_INVALID_ARG_TYPE('data', 'Buffer', data); + return this.emit('error', err); + } + + if (typeof this.fd !== 'number') { + return this.once('open', function() { + this._write(data, encoding, cb); + }); + } + + fs.write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { + if (er) { + if (this.autoClose) { + this.destroy(); + } + return cb(er); + } + this.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) + this.pos += data.length; +}; + + +function writev(fd, chunks, position, callback) { + function wrapper(err, written) { + // Retain a reference to chunks so that they can't be GC'ed too soon. + callback(err, written || 0, chunks); + } + + const req = new FSReqWrap(); + req.oncomplete = wrapper; + writeBuffers(fd, chunks, position, req); +} + + +WriteStream.prototype._writev = function(data, cb) { + if (typeof this.fd !== 'number') { + return this.once('open', function() { + this._writev(data, cb); + }); + } + + const self = this; + const len = data.length; + const chunks = new Array(len); + let size = 0; + + for (var i = 0; i < len; i++) { + const chunk = data[i].chunk; + + chunks[i] = chunk; + size += chunk.length; + } + + writev(this.fd, chunks, this.pos, function(er, bytes) { + if (er) { + self.destroy(); + return cb(er); + } + self.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) + this.pos += size; +}; + + +WriteStream.prototype._destroy = ReadStream.prototype._destroy; +WriteStream.prototype.close = function(cb) { + if (cb) { + if (this.closed) { + process.nextTick(cb); + return; + } else { + this.on('close', cb); + } + } + + // If we are not autoClosing, we should call + // destroy on 'finish'. + if (!this.autoClose) { + this.on('finish', this.destroy.bind(this)); + } + + // we use end() instead of destroy() because of + // https://github.com/nodejs/node/issues/2006 + this.end(); +}; + +// There is no shutdown() for files. +WriteStream.prototype.destroySoon = WriteStream.prototype.end; + +module.exports = { + ReadStream, + WriteStream +}; diff --git a/lib/internal/fs/sync_write_stream.js b/lib/internal/fs/sync_write_stream.js new file mode 100644 index 00000000000000..b365474663d8c2 --- /dev/null +++ b/lib/internal/fs/sync_write_stream.js @@ -0,0 +1,45 @@ +'use strict'; + +const { Writable } = require('stream'); +const { inherits } = require('util'); +const { closeSync, writeSync } = require('fs'); + +function SyncWriteStream(fd, options) { + Writable.call(this); + + options = options || {}; + + this.fd = fd; + this.readable = false; + this.autoClose = options.autoClose === undefined ? true : options.autoClose; + + this.on('end', () => this._destroy()); +} + +inherits(SyncWriteStream, Writable); + +SyncWriteStream.prototype._write = function(chunk, encoding, cb) { + writeSync(this.fd, chunk, 0, chunk.length); + cb(); + return true; +}; + +SyncWriteStream.prototype._destroy = function() { + if (this.fd === null) // already destroy()ed + return; + + if (this.autoClose) + closeSync(this.fd); + + this.fd = null; + return true; +}; + +SyncWriteStream.prototype.destroySoon = +SyncWriteStream.prototype.destroy = function() { + this._destroy(); + this.emit('close'); + return true; +}; + +module.exports = SyncWriteStream; diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index 2f7a8d8ced176e..a8c64e2b04d56b 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -1,7 +1,6 @@ 'use strict'; const { Buffer, kMaxLength } = require('buffer'); -const { Writable } = require('stream'); const { ERR_FS_INVALID_SYMLINK_TYPE, ERR_INVALID_ARG_TYPE, @@ -11,7 +10,6 @@ const { ERR_OUT_OF_RANGE } = require('internal/errors').codes; const { isUint8Array } = require('internal/util/types'); -const fs = require('fs'); const pathModule = require('path'); const util = require('util'); @@ -70,21 +68,6 @@ function getOptions(options, defaultOptions) { return options; } -function modeNum(m, def) { - if (typeof m === 'number') - return m; - if (typeof m === 'string') { - const parsed = parseInt(m, 8); - if (Number.isNaN(parsed)) - return m; - return parsed; - } - // TODO(BridgeAR): Only return `def` in case `m == null` - if (def !== undefined) - return def; - return m; -} - // Check if the path contains null types if it is a string nor Uint8Array, // otherwise return silently. function nullCheck(path, propName, throwError = true) { @@ -271,45 +254,6 @@ function stringToSymlinkType(type) { return flags; } -// Temporary hack for process.stdout and process.stderr when piped to files. -function SyncWriteStream(fd, options) { - Writable.call(this); - - options = options || {}; - - this.fd = fd; - this.readable = false; - this.autoClose = options.autoClose === undefined ? true : options.autoClose; - - this.on('end', () => this._destroy()); -} - -util.inherits(SyncWriteStream, Writable); - -SyncWriteStream.prototype._write = function(chunk, encoding, cb) { - fs.writeSync(this.fd, chunk, 0, chunk.length); - cb(); - return true; -}; - -SyncWriteStream.prototype._destroy = function() { - if (this.fd === null) // already destroy()ed - return; - - if (this.autoClose) - fs.closeSync(this.fd); - - this.fd = null; - return true; -}; - -SyncWriteStream.prototype.destroySoon = -SyncWriteStream.prototype.destroy = function() { - this._destroy(); - this.emit('close'); - return true; -}; - // converts Date or number to a fractional UNIX timestamp function toUnixTimestamp(time, name = 'time') { // eslint-disable-next-line eqeqeq @@ -391,7 +335,6 @@ module.exports = { assertEncoding, copyObject, getOptions, - modeNum, nullCheck, preprocessSymlinkDestination, realpathCacheKey: Symbol('realpathCacheKey'), @@ -399,7 +342,6 @@ module.exports = { stringToFlags, stringToSymlinkType, Stats, - SyncWriteStream, toUnixTimestamp, validateBuffer, validateOffsetLengthRead, diff --git a/lib/internal/fs/watchers.js b/lib/internal/fs/watchers.js new file mode 100644 index 00000000000000..1007a5a13614d2 --- /dev/null +++ b/lib/internal/fs/watchers.js @@ -0,0 +1,181 @@ +'use strict'; + +const errors = require('internal/errors'); +const { + kFsStatsFieldsLength, + StatWatcher: _StatWatcher +} = process.binding('fs'); +const { FSEvent } = process.binding('fs_event_wrap'); +const { EventEmitter } = require('events'); +const { + getStatsFromBinding, + validatePath +} = require('internal/fs/utils'); +const { toNamespacedPath } = require('path'); +const { validateUint32 } = require('internal/validators'); +const { getPathFromURL } = require('internal/url'); +const util = require('util'); +const assert = require('assert'); + +function emitStop(self) { + self.emit('stop'); +} + +function StatWatcher() { + EventEmitter.call(this); + + this._handle = new _StatWatcher(); + + // uv_fs_poll is a little more powerful than ev_stat but we curb it for + // the sake of backwards compatibility + let oldStatus = -1; + + this._handle.onchange = (newStatus, stats) => { + if (oldStatus === -1 && + newStatus === -1 && + stats[2/* new nlink */] === stats[16/* old nlink */]) return; + + oldStatus = newStatus; + this.emit('change', getStatsFromBinding(stats), + getStatsFromBinding(stats, kFsStatsFieldsLength)); + }; + + this._handle.onstop = () => { + process.nextTick(emitStop, this); + }; +} +util.inherits(StatWatcher, EventEmitter); + + +// FIXME(joyeecheung): this method is not documented. +// At the moment if filename is undefined, we +// 1. Throw an Error if it's the first time .start() is called +// 2. Return silently if .start() has already been called +// on a valid filename and the wrap has been initialized +// This method is a noop if the watcher has already been started. +StatWatcher.prototype.start = function(filename, persistent, interval) { + assert(this._handle instanceof _StatWatcher, 'handle must be a StatWatcher'); + if (this._handle.isActive) { + return; + } + + filename = getPathFromURL(filename); + validatePath(filename, 'filename'); + validateUint32(interval, 'interval'); + const err = this._handle.start(toNamespacedPath(filename), + persistent, interval); + if (err) { + const error = errors.uvException({ + errno: err, + syscall: 'watch', + path: filename + }); + error.filename = filename; + throw error; + } +}; + +// FIXME(joyeecheung): this method is not documented while there is +// another documented fs.unwatchFile(). The counterpart in +// FSWatcher is .close() +// This method is a noop if the watcher has not been started. +StatWatcher.prototype.stop = function() { + assert(this._handle instanceof _StatWatcher, 'handle must be a StatWatcher'); + if (!this._handle.isActive) { + return; + } + this._handle.stop(); +}; + + +function FSWatcher() { + EventEmitter.call(this); + + this._handle = new FSEvent(); + this._handle.owner = this; + + this._handle.onchange = (status, eventType, filename) => { + // TODO(joyeecheung): we may check self._handle.initialized here + // and return if that is false. This allows us to avoid firing the event + // after the handle is closed, and to fire both UV_RENAME and UV_CHANGE + // if they are set by libuv at the same time. + if (status < 0) { + if (this._handle !== null) { + // We don't use this.close() here to avoid firing the close event. + this._handle.close(); + this._handle = null; // make the handle garbage collectable + } + const error = errors.uvException({ + errno: status, + syscall: 'watch', + path: filename + }); + error.filename = filename; + this.emit('error', error); + } else { + this.emit('change', eventType, filename); + } + }; +} +util.inherits(FSWatcher, EventEmitter); + +// FIXME(joyeecheung): this method is not documented. +// At the moment if filename is undefined, we +// 1. Throw an Error if it's the first time .start() is called +// 2. Return silently if .start() has already been called +// on a valid filename and the wrap has been initialized +// 3. Return silently if the watcher has already been closed +// This method is a noop if the watcher has already been started. +FSWatcher.prototype.start = function(filename, + persistent, + recursive, + encoding) { + if (this._handle === null) { // closed + return; + } + assert(this._handle instanceof FSEvent, 'handle must be a FSEvent'); + if (this._handle.initialized) { // already started + return; + } + + filename = getPathFromURL(filename); + validatePath(filename, 'filename'); + + const err = this._handle.start(toNamespacedPath(filename), + persistent, + recursive, + encoding); + if (err) { + const error = errors.uvException({ + errno: err, + syscall: 'watch', + path: filename + }); + error.filename = filename; + throw error; + } +}; + +// This method is a noop if the watcher has not been started or +// has already been closed. +FSWatcher.prototype.close = function() { + if (this._handle === null) { // closed + return; + } + assert(this._handle instanceof FSEvent, 'handle must be a FSEvent'); + if (!this._handle.initialized) { // not started + return; + } + this._handle.close(); + this._handle = null; // make the handle garbage collectable + process.nextTick(emitCloseNT, this); +}; + +function emitCloseNT(self) { + self.emit('close'); +} + +module.exports = { + FSWatcher, + StatWatcher +}; diff --git a/lib/internal/process.js b/lib/internal/process.js index 6d1958db6db0a2..bcefca316a236e 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -25,10 +25,7 @@ process.assert = deprecate( 'DEP0100'); // Set up the process.cpuUsage() function. -function setup_cpuUsage() { - // Get the native function, which will be replaced with a JS version. - const _cpuUsage = process.cpuUsage; - +function setup_cpuUsage(_cpuUsage) { // Create the argument array that will be passed to the native function. const cpuValues = new Float64Array(2); @@ -92,8 +89,7 @@ function setup_cpuUsage() { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. -function setup_hrtime() { - const _hrtime = process.hrtime; +function setup_hrtime(_hrtime) { const hrValues = new Uint32Array(3); process.hrtime = function hrtime(time) { @@ -120,12 +116,11 @@ function setup_hrtime() { }; } -function setupMemoryUsage() { - const memoryUsage_ = process.memoryUsage; +function setupMemoryUsage(_memoryUsage) { const memValues = new Float64Array(4); process.memoryUsage = function memoryUsage() { - memoryUsage_(memValues); + _memoryUsage(memValues); return { rss: memValues[0], heapTotal: memValues[1], @@ -245,18 +240,17 @@ function setupChannel() { } -function setupRawDebug() { - const rawDebug = process._rawDebug; +function setupRawDebug(_rawDebug) { process._rawDebug = function() { - rawDebug(util.format.apply(null, arguments)); + _rawDebug(util.format.apply(null, arguments)); }; } -function setupUncaughtExceptionCapture(exceptionHandlerState) { - // This is a typed array for faster communication with JS. - const shouldAbortOnUncaughtToggle = process._shouldAbortOnUncaughtToggle; - delete process._shouldAbortOnUncaughtToggle; +function setupUncaughtExceptionCapture(exceptionHandlerState, + shouldAbortOnUncaughtToggle) { + // shouldAbortOnUncaughtToggle is a typed array for faster + // communication with JS. process.setUncaughtExceptionCaptureCallback = function(fn) { if (fn === null) { diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js new file mode 100644 index 00000000000000..209702ed7b9b76 --- /dev/null +++ b/lib/internal/process/methods.js @@ -0,0 +1,49 @@ +'use strict'; + +function setupProcessMethods(_chdir, _cpuUsage, _hrtime, _memoryUsage, + _rawDebug, _umask, _initgroups, _setegid, + _seteuid, _setgid, _setuid, _setgroups) { + // Non-POSIX platforms like Windows don't have certain methods. + if (_setgid !== undefined) { + setupPosixMethods(_initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups); + } + + process.chdir = function chdir(...args) { + return _chdir(...args); + }; + + process.umask = function umask(...args) { + return _umask(...args); + }; +} + +function setupPosixMethods(_initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups) { + + process.initgroups = function initgroups(...args) { + return _initgroups(...args); + }; + + process.setegid = function setegid(...args) { + return _setegid(...args); + }; + + process.seteuid = function seteuid(...args) { + return _seteuid(...args); + }; + + process.setgid = function setgid(...args) { + return _setgid(...args); + }; + + process.setuid = function setuid(...args) { + return _setuid(...args); + }; + + process.setgroups = function setgroups(...args) { + return _setgroups(...args); + }; +} + +exports.setup = setupProcessMethods; diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index dbe0ce8cdbdacf..df6984aabc8c07 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -2,7 +2,7 @@ exports.setup = setupNextTick; -function setupNextTick() { +function setupNextTick(_setupNextTick, _setupPromises) { const { getDefaultTriggerAsyncId, newAsyncId, @@ -14,10 +14,10 @@ function setupNextTick() { emitDestroy, symbols: { async_id_symbol, trigger_async_id_symbol } } = require('internal/async_hooks'); - const promises = require('internal/process/promises'); + const emitPromiseRejectionWarnings = + require('internal/process/promises').setup(_setupPromises); const { ERR_INVALID_CALLBACK } = require('internal/errors').codes; const FixedQueue = require('internal/fixed_queue'); - const { emitPromiseRejectionWarnings } = promises; // tickInfo is used so that the C++ code in src/node.cc can // have easy access to our nextTick state, and avoid unnecessary @@ -26,7 +26,7 @@ function setupNextTick() { const [ tickInfo, runMicrotasks - ] = process._setupNextTick(_tickCallback); + ] = _setupNextTick(_tickCallback); // *Must* match Environment::TickInfo::Fields in src/env.h. const kHasScheduled = 0; diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 6cc366d8b2b09a..f54f34b9ae92f4 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -7,11 +7,12 @@ const pendingUnhandledRejections = []; const asyncHandledRejections = []; let lastPromiseId = 0; -module.exports = { - emitPromiseRejectionWarnings -}; +exports.setup = setupPromises; -process._setupPromises(unhandledRejection, handledRejection); +function setupPromises(_setupPromises) { + _setupPromises(unhandledRejection, handledRejection); + return emitPromiseRejectionWarnings; +} function unhandledRejection(promise, reason) { maybeUnhandledPromises.set(promise, { diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js index ce84142938f066..eaba4dfca13a47 100644 --- a/lib/internal/process/stdio.js +++ b/lib/internal/process/stdio.js @@ -167,8 +167,8 @@ function createWritableStdioStream(fd) { break; case 'FILE': - var fs = require('internal/fs/utils'); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); + const SyncWriteStream = require('internal/fs/sync_write_stream'); + stream = new SyncWriteStream(fd, { autoClose: false }); stream._type = 'fs'; break; diff --git a/lib/internal/validators.js b/lib/internal/validators.js index aabe71ef33979a..b7b21d29bfa097 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -2,6 +2,7 @@ const { ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE } = require('internal/errors').codes; @@ -13,6 +14,51 @@ function isUint32(value) { return value === (value >>> 0); } +const octalReg = /^[0-7]+$/; +const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; + +/** + * Validate values that will be converted into mode_t (the S_* constants). + * Only valid numbers and octal strings are allowed. They could be converted + * to 32-bit unsigned integers or non-negative signed integers in the C++ + * land, but any value higher than 0o777 will result in platform-specific + * behaviors. + * + * @param {*} value Values to be validated + * @param {string} name Name of the argument + * @param {number} def If specified, will be returned for invalid values + * @returns {number} + */ +function validateMode(value, name, def) { + if (isUint32(value)) { + return value; + } + + if (typeof value === 'number') { + if (!Number.isInteger(value)) { + throw new ERR_OUT_OF_RANGE(name, 'an integer', value); + } else { + // 2 ** 32 === 4294967296 + throw new ERR_OUT_OF_RANGE(name, '>= 0 && < 4294967296', value); + } + } + + if (typeof value === 'string') { + if (!octalReg.test(value)) { + throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); + } + const parsed = parseInt(value, 8); + return parsed; + } + + // TODO(BridgeAR): Only return `def` in case `value == null` + if (def !== undefined) { + return def; + } + + throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); +} + function validateInteger(value, name) { let err; @@ -28,6 +74,7 @@ function validateInteger(value, name) { } function validateInt32(value, name, min = -2147483648, max = 2147483647) { + // The defaults for min and max correspond to the limits of 32-bit integers. if (!isInt32(value)) { let err; if (typeof value !== 'number') { @@ -35,11 +82,14 @@ function validateInt32(value, name, min = -2147483648, max = 2147483647) { } else if (!Number.isInteger(value)) { err = new ERR_OUT_OF_RANGE(name, 'an integer', value); } else { - // 2 ** 31 === 2147483648 - err = new ERR_OUT_OF_RANGE(name, '> -2147483649 && < 2147483648', value); + err = new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } Error.captureStackTrace(err, validateInt32); throw err; + } else if (value < min || value > max) { + const err = new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + Error.captureStackTrace(err, validateInt32); + throw err; } } @@ -67,6 +117,7 @@ function validateUint32(value, name, positive) { module.exports = { isInt32, isUint32, + validateMode, validateInteger, validateInt32, validateUint32 diff --git a/lib/tls.js b/lib/tls.js index 1e444d5d8898c2..f4b72851907862 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -169,14 +169,14 @@ function check(hostParts, pattern, wildcards) { return true; } -exports.checkServerIdentity = function checkServerIdentity(host, cert) { +exports.checkServerIdentity = function checkServerIdentity(hostname, cert) { const subject = cert.subject; const altNames = cert.subjectaltname; const dnsNames = []; const uriNames = []; const ips = []; - host = '' + host; + hostname = '' + hostname; if (altNames) { for (const name of altNames.split(', ')) { @@ -194,14 +194,14 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { let valid = false; let reason = 'Unknown reason'; - if (net.isIP(host)) { - valid = ips.includes(canonicalizeIP(host)); + if (net.isIP(hostname)) { + valid = ips.includes(canonicalizeIP(hostname)); if (!valid) - reason = `IP: ${host} is not in the cert's list: ${ips.join(', ')}`; + reason = `IP: ${hostname} is not in the cert's list: ${ips.join(', ')}`; // TODO(bnoordhuis) Also check URI SANs that are IP addresses. } else if (subject) { - host = unfqdn(host); // Remove trailing dot for error messages. - const hostParts = splitHost(host); + hostname = unfqdn(hostname); // Remove trailing dot for error messages. + const hostParts = splitHost(hostname); const wildcard = (pattern) => check(hostParts, pattern, true); const noWildcard = (pattern) => check(hostParts, pattern, false); @@ -215,11 +215,12 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { valid = wildcard(cn); if (!valid) - reason = `Host: ${host}. is not cert's CN: ${cn}`; + reason = `Host: ${hostname}. is not cert's CN: ${cn}`; } else { valid = dnsNames.some(wildcard) || uriNames.some(noWildcard); if (!valid) - reason = `Host: ${host}. is not in the cert's altnames: ${altNames}`; + reason = + `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; } } else { reason = 'Cert is empty'; @@ -228,7 +229,7 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) { if (!valid) { const err = new ERR_TLS_CERT_ALTNAME_INVALID(reason); err.reason = reason; - err.host = host; + err.host = hostname; err.cert = cert; return err; } diff --git a/node.gyp b/node.gyp index 19367d9bd9517d..709ce226033af0 100644 --- a/node.gyp +++ b/node.gyp @@ -104,7 +104,11 @@ 'lib/internal/fixed_queue.js', 'lib/internal/freelist.js', 'lib/internal/fs/promises.js', + 'lib/internal/fs/read_file_context.js', + 'lib/internal/fs/streams.js', + 'lib/internal/fs/sync_write_stream.js', 'lib/internal/fs/utils.js', + 'lib/internal/fs/watchers.js', 'lib/internal/http.js', 'lib/internal/inspector_async_hook.js', 'lib/internal/linkedlist.js', @@ -120,6 +124,7 @@ 'lib/internal/net.js', 'lib/internal/os.js', 'lib/internal/process/esm_loader.js', + 'lib/internal/process/methods.js', 'lib/internal/process/next_tick.js', 'lib/internal/process/promises.js', 'lib/internal/process/stdio.js', @@ -303,6 +308,7 @@ 'sources': [ 'src/async_wrap.cc', + 'src/bootstrapper.cc', 'src/callback_scope.cc', 'src/cares_wrap.cc', 'src/connection_wrap.cc', diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc new file mode 100644 index 00000000000000..6c7c1af3e31cf6 --- /dev/null +++ b/src/bootstrapper.cc @@ -0,0 +1,133 @@ +#include "node.h" +#include "env.h" +#include "env-inl.h" +#include "node_internals.h" +#include "v8.h" + +namespace node { + +using v8::Array; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::Promise; +using v8::PromiseRejectEvent; +using v8::PromiseRejectMessage; +using v8::Value; + +void SetupProcessObject(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsFunction()); + env->set_push_values_to_array_function(args[0].As()); +} + +void RunMicrotasks(const FunctionCallbackInfo& args) { + args.GetIsolate()->RunMicrotasks(); +} + +void SetupNextTick(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + Local context = env->context(); + + CHECK(args[0]->IsFunction()); + + env->set_tick_callback_function(args[0].As()); + + Local run_microtasks_fn = + env->NewFunctionTemplate(RunMicrotasks)->GetFunction(context) + .ToLocalChecked(); + run_microtasks_fn->SetName(FIXED_ONE_BYTE_STRING(isolate, "runMicrotasks")); + + Local ret = Array::New(isolate, 2); + ret->Set(context, 0, env->tick_info()->fields().GetJSArray()).FromJust(); + ret->Set(context, 1, run_microtasks_fn).FromJust(); + + args.GetReturnValue().Set(ret); +} + +void PromiseRejectCallback(PromiseRejectMessage message) { + Local promise = message.GetPromise(); + Isolate* isolate = promise->GetIsolate(); + PromiseRejectEvent event = message.GetEvent(); + + Environment* env = Environment::GetCurrent(isolate); + Local callback; + Local value; + + if (event == v8::kPromiseRejectWithNoHandler) { + callback = env->promise_reject_unhandled_function(); + value = message.GetValue(); + + if (value.IsEmpty()) + value = Undefined(isolate); + } else if (event == v8::kPromiseHandlerAddedAfterReject) { + callback = env->promise_reject_handled_function(); + value = Undefined(isolate); + } else { + UNREACHABLE(); + } + + Local args[] = { promise, value }; + MaybeLocal ret = callback->Call(env->context(), + Undefined(isolate), + arraysize(args), + args); + + if (!ret.IsEmpty() && ret.ToLocalChecked()->IsTrue()) + env->tick_info()->promise_rejections_toggle_on(); +} + +void SetupPromises(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + + CHECK(args[0]->IsFunction()); + CHECK(args[1]->IsFunction()); + + isolate->SetPromiseRejectCallback(PromiseRejectCallback); + env->set_promise_reject_unhandled_function(args[0].As()); + env->set_promise_reject_handled_function(args[1].As()); +} + +#define BOOTSTRAP_METHOD(name, fn) env->SetMethod(bootstrapper, #name, fn) + +// The Bootstrapper object is an ephemeral object that is used only during +// the bootstrap process of the Node.js environment. A reference to the +// bootstrap object must not be kept around after the bootstrap process +// completes so that it can be gc'd as soon as possible. +void SetupBootstrapObject(Environment* env, + Local bootstrapper) { + BOOTSTRAP_METHOD(_setupProcessObject, SetupProcessObject); + BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick); + BOOTSTRAP_METHOD(_setupPromises, SetupPromises); + BOOTSTRAP_METHOD(_chdir, Chdir); + BOOTSTRAP_METHOD(_cpuUsage, CPUUsage); + BOOTSTRAP_METHOD(_hrtime, Hrtime); + BOOTSTRAP_METHOD(_memoryUsage, MemoryUsage); + BOOTSTRAP_METHOD(_rawDebug, RawDebug); + BOOTSTRAP_METHOD(_umask, Umask); + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) + BOOTSTRAP_METHOD(_initgroups, InitGroups); + BOOTSTRAP_METHOD(_setegid, SetEGid); + BOOTSTRAP_METHOD(_seteuid, SetEUid); + BOOTSTRAP_METHOD(_setgid, SetGid); + BOOTSTRAP_METHOD(_setuid, SetUid); + BOOTSTRAP_METHOD(_setgroups, SetGroups); +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + + auto should_abort_on_uncaught_toggle = + FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); + CHECK(bootstrapper->Set(env->context(), + should_abort_on_uncaught_toggle, + env->should_abort_on_uncaught_toggle().GetJSArray()) + .FromJust()); +} +#undef BOOTSTRAP_METHOD + +} // namespace node diff --git a/src/node.cc b/src/node.cc index eda97d81df1d9e..20097b42907c7d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -159,7 +159,6 @@ using v8::Number; using v8::Object; using v8::ObjectTemplate; using v8::Promise; -using v8::PromiseRejectMessage; using v8::PropertyCallbackInfo; using v8::ScriptOrigin; using v8::SealHandleScope; @@ -616,97 +615,6 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) { !env->inside_should_not_abort_on_uncaught_scope(); } - -void RunMicrotasks(const FunctionCallbackInfo& args) { - args.GetIsolate()->RunMicrotasks(); -} - - -void SetupProcessObject(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsFunction()); - - env->set_push_values_to_array_function(args[0].As()); - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "_setupProcessObject")).FromJust(); -} - - -void SetupNextTick(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsFunction()); - - env->set_tick_callback_function(args[0].As()); - - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "_setupNextTick")).FromJust(); - - v8::Local run_microtasks_fn = - env->NewFunctionTemplate(RunMicrotasks)->GetFunction(env->context()) - .ToLocalChecked(); - run_microtasks_fn->SetName( - FIXED_ONE_BYTE_STRING(env->isolate(), "runMicrotasks")); - - Local ret = Array::New(env->isolate(), 2); - ret->Set(env->context(), 0, - env->tick_info()->fields().GetJSArray()).FromJust(); - ret->Set(env->context(), 1, run_microtasks_fn).FromJust(); - - args.GetReturnValue().Set(ret); -} - -void PromiseRejectCallback(PromiseRejectMessage message) { - Local promise = message.GetPromise(); - Isolate* isolate = promise->GetIsolate(); - v8::PromiseRejectEvent event = message.GetEvent(); - - Environment* env = Environment::GetCurrent(isolate); - Local callback; - Local value; - - if (event == v8::kPromiseRejectWithNoHandler) { - callback = env->promise_reject_unhandled_function(); - value = message.GetValue(); - - if (value.IsEmpty()) - value = Undefined(isolate); - } else if (event == v8::kPromiseHandlerAddedAfterReject) { - callback = env->promise_reject_handled_function(); - value = Undefined(isolate); - } else { - UNREACHABLE(); - } - - Local args[] = { promise, value }; - MaybeLocal ret = callback->Call(env->context(), - Undefined(isolate), - arraysize(args), - args); - - if (!ret.IsEmpty() && ret.ToLocalChecked()->IsTrue()) - env->tick_info()->promise_rejections_toggle_on(); -} - -void SetupPromises(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Isolate* isolate = env->isolate(); - - CHECK(args[0]->IsFunction()); - CHECK(args[1]->IsFunction()); - - isolate->SetPromiseRejectCallback(PromiseRejectCallback); - env->set_promise_reject_unhandled_function(args[0].As()); - env->set_promise_reject_handled_function(args[1].As()); - - env->process_object()->Delete( - env->context(), - FIXED_ONE_BYTE_STRING(isolate, "_setupPromises")).FromJust(); -} - } // anonymous namespace @@ -1314,7 +1222,7 @@ static void Abort(const FunctionCallbackInfo& args) { } -static void Chdir(const FunctionCallbackInfo& args) { +void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (args.Length() != 1 || !args[0]->IsString()) { @@ -1352,7 +1260,7 @@ static void Cwd(const FunctionCallbackInfo& args) { } -static void Umask(const FunctionCallbackInfo& args) { +void Umask(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); uint32_t old; @@ -1513,7 +1421,7 @@ static void GetEGid(const FunctionCallbackInfo& args) { } -static void SetGid(const FunctionCallbackInfo& args) { +void SetGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsUint32() && !args[0]->IsString()) { @@ -1532,7 +1440,7 @@ static void SetGid(const FunctionCallbackInfo& args) { } -static void SetEGid(const FunctionCallbackInfo& args) { +void SetEGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsUint32() && !args[0]->IsString()) { @@ -1551,7 +1459,7 @@ static void SetEGid(const FunctionCallbackInfo& args) { } -static void SetUid(const FunctionCallbackInfo& args) { +void SetUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsUint32() && !args[0]->IsString()) { @@ -1570,7 +1478,7 @@ static void SetUid(const FunctionCallbackInfo& args) { } -static void SetEUid(const FunctionCallbackInfo& args) { +void SetEUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsUint32() && !args[0]->IsString()) { @@ -1627,7 +1535,7 @@ static void GetGroups(const FunctionCallbackInfo& args) { } -static void SetGroups(const FunctionCallbackInfo& args) { +void SetGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsArray()) { @@ -1658,7 +1566,7 @@ static void SetGroups(const FunctionCallbackInfo& args) { } -static void InitGroups(const FunctionCallbackInfo& args) { +void InitGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsUint32() && !args[0]->IsString()) { @@ -1747,7 +1655,7 @@ static void Uptime(const FunctionCallbackInfo& args) { } -static void MemoryUsage(const FunctionCallbackInfo& args) { +void MemoryUsage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); size_t rss; @@ -1799,7 +1707,7 @@ static void Kill(const FunctionCallbackInfo& args) { // broken into the upper/lower 32 bits to be converted back in JS, // because there is no Uint64Array in JS. // The third entry contains the remaining nanosecond part of the value. -static void Hrtime(const FunctionCallbackInfo& args) { +void Hrtime(const FunctionCallbackInfo& args) { uint64_t t = uv_hrtime(); Local ab = args[0].As()->Buffer(); @@ -1818,7 +1726,7 @@ static void Hrtime(const FunctionCallbackInfo& args) { // which are uv_timeval_t structs (long tv_sec, long tv_usec). // Returns those values as Float64 microseconds in the elements of the array // passed to the function. -static void CPUUsage(const FunctionCallbackInfo& args) { +void CPUUsage(const FunctionCallbackInfo& args) { uv_rusage_t rusage; // Call libuv to get the values we'll return. @@ -2843,13 +2751,6 @@ void SetupProcessObject(Environment* env, FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"), GetParentProcessId).FromJust()); - auto should_abort_on_uncaught_toggle = - FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle"); - CHECK(process->Set(env->context(), - should_abort_on_uncaught_toggle, - env->should_abort_on_uncaught_toggle().GetJSArray()) - .FromJust()); - // -e, --eval if (eval_string) { READONLY_PROPERTY(process, @@ -2992,17 +2893,9 @@ void SetupProcessObject(Environment* env, #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) env->SetMethod(process, "getuid", GetUid); env->SetMethod(process, "geteuid", GetEUid); - env->SetMethod(process, "setuid", SetUid); - env->SetMethod(process, "seteuid", SetEUid); - - env->SetMethod(process, "setgid", SetGid); - env->SetMethod(process, "setegid", SetEGid); env->SetMethod(process, "getgid", GetGid); env->SetMethod(process, "getegid", GetEGid); - env->SetMethod(process, "getgroups", GetGroups); - env->SetMethod(process, "setgroups", SetGroups); - env->SetMethod(process, "initgroups", InitGroups); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) env->SetMethod(process, "_kill", Kill); @@ -3018,10 +2911,6 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "uptime", Uptime); env->SetMethod(process, "memoryUsage", MemoryUsage); - - env->SetMethod(process, "_setupProcessObject", SetupProcessObject); - env->SetMethod(process, "_setupNextTick", SetupNextTick); - env->SetMethod(process, "_setupPromises", SetupPromises); } @@ -3046,7 +2935,7 @@ void SignalExit(int signo) { // to the process.stderr stream. However, in some cases, such as // when debugging the stream.Writable class or the process.nextTick // function, it is useful to bypass JavaScript entirely. -static void RawDebug(const FunctionCallbackInfo& args) { +void RawDebug(const FunctionCallbackInfo& args) { CHECK(args.Length() == 1 && args[0]->IsString() && "must be called with a single string"); node::Utf8Value message(args.GetIsolate(), args[0]); @@ -3177,9 +3066,12 @@ void LoadEnvironment(Environment* env) { } // Bootstrap Node.js + Local bootstrapper = Object::New(env->isolate()); + SetupBootstrapObject(env, bootstrapper); Local bootstrapped_node; Local node_bootstrapper_args[] = { env->process_object(), + bootstrapper, bootstrapped_loaders }; if (!ExecuteBootstrapper(env, node_bootstrapper, diff --git a/src/node_constants.cc b/src/node_constants.cc index 68339c0032804b..c1e39244b359b4 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -1162,6 +1162,27 @@ void DefineSystemConstants(Local target) { #ifdef X_OK NODE_DEFINE_CONSTANT(target, X_OK); #endif + +#ifdef UV_FS_COPYFILE_EXCL +# define COPYFILE_EXCL UV_FS_COPYFILE_EXCL + NODE_DEFINE_CONSTANT(target, UV_FS_COPYFILE_EXCL); + NODE_DEFINE_CONSTANT(target, COPYFILE_EXCL); +# undef COPYFILE_EXCL +#endif + +#ifdef UV_FS_COPYFILE_FICLONE +# define COPYFILE_FICLONE UV_FS_COPYFILE_FICLONE + NODE_DEFINE_CONSTANT(target, UV_FS_COPYFILE_FICLONE); + NODE_DEFINE_CONSTANT(target, COPYFILE_FICLONE); +# undef COPYFILE_FICLONE +#endif + +#ifdef UV_FS_COPYFILE_FICLONE_FORCE +# define COPYFILE_FICLONE_FORCE UV_FS_COPYFILE_FICLONE_FORCE + NODE_DEFINE_CONSTANT(target, UV_FS_COPYFILE_FICLONE_FORCE); + NODE_DEFINE_CONSTANT(target, COPYFILE_FICLONE_FORCE); +# undef COPYFILE_FICLONE_FORCE +#endif } void DefineCryptoConstants(Local target) { @@ -1305,9 +1326,6 @@ void DefineConstants(v8::Isolate* isolate, Local target) { // Define libuv constants. NODE_DEFINE_CONSTANT(os_constants, UV_UDP_REUSEADDR); - NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_EXCL); - NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_FICLONE); - NODE_DEFINE_CONSTANT(fs_constants, UV_FS_COPYFILE_FICLONE_FORCE); os_constants->Set(OneByteString(isolate, "dlopen"), dlopen_constants); os_constants->Set(OneByteString(isolate, "errno"), err_constants); diff --git a/src/node_internals.h b/src/node_internals.h index 1ba56458b194c2..3014a0e5f7a442 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -357,6 +357,8 @@ inline v8::Local FillGlobalStatsArray(Environment* env, return node::FillStatsArray(env->fs_stats_field_array(), s, offset); } +void SetupBootstrapObject(Environment* env, + v8::Local bootstrapper); void SetupProcessObject(Environment* env, int argc, const char* const* argv, @@ -873,6 +875,24 @@ static inline const char *errno_string(int errorno) { TRACING_CATEGORY_NODE "." #one "," \ TRACING_CATEGORY_NODE "." #one "." #two +// Functions defined in node.cc that are exposed via the bootstrapper object + +void Chdir(const v8::FunctionCallbackInfo& args); +void CPUUsage(const v8::FunctionCallbackInfo& args); +void Hrtime(const v8::FunctionCallbackInfo& args); +void MemoryUsage(const v8::FunctionCallbackInfo& args); +void RawDebug(const v8::FunctionCallbackInfo& args); +void Umask(const v8::FunctionCallbackInfo& args); + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) +void SetGid(const v8::FunctionCallbackInfo& args); +void SetEGid(const v8::FunctionCallbackInfo& args); +void SetUid(const v8::FunctionCallbackInfo& args); +void SetEUid(const v8::FunctionCallbackInfo& args); +void SetGroups(const v8::FunctionCallbackInfo& args); +void InitGroups(const v8::FunctionCallbackInfo& args); +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + } // namespace node void napi_module_register_by_symbol(v8::Local exports, diff --git a/test/parallel/test-fs-chmod-mask.js b/test/parallel/test-fs-chmod-mask.js new file mode 100644 index 00000000000000..9564ca142bd643 --- /dev/null +++ b/test/parallel/test-fs-chmod-mask.js @@ -0,0 +1,95 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs APIs. + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; +// On Windows chmod is only able to manipulate write permission +if (common.isWindows) { + mode = 0o444; // read-only +} else { + mode = 0o777; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `chmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmod(file, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } + + { + const file = path.join(tmpdir.path, `chmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmodSync(file, input); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = path.join(tmpdir.path, `fchmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.open(file, 'w', common.mustCall((err, fd) => { + assert.ifError(err); + + fs.fchmod(fd, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.close(fd, assert.ifError); + })); + })); + } + + { + const file = path.join(tmpdir.path, `fchmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + const fd = fs.openSync(file, 'w'); + + fs.fchmodSync(fd, input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + + fs.close(fd, assert.ifError); + } + + if (fs.lchmod) { + const link = path.join(tmpdir.path, `lchmod-src-${suffix}`); + const file = path.join(tmpdir.path, `lchmod-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmod(link, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + })); + } + + if (fs.lchmodSync) { + const link = path.join(tmpdir.path, `lchmodSync-src-${suffix}`); + const file = path.join(tmpdir.path, `lchmodSync-dest-${suffix}`); + fs.writeFileSync(file, 'test', 'utf-8'); + fs.symlinkSync(file, link); + + fs.lchmodSync(link, input); + assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-chmod.js b/test/parallel/test-fs-chmod.js index 6727ec2144f2ce..ed5919ce887bc8 100644 --- a/test/parallel/test-fs-chmod.js +++ b/test/parallel/test-fs-chmod.js @@ -62,7 +62,7 @@ function closeSync() { } -// On Windows chmod is only able to manipulate read-only bit +// On Windows chmod is only able to manipulate write permission if (common.isWindows) { mode_async = 0o400; // read-only mode_sync = 0o600; // read-write @@ -112,10 +112,10 @@ fs.open(file2, 'w', common.mustCall((err, fd) => { common.expectsError( () => fs.fchmod(fd, {}), { - code: 'ERR_INVALID_ARG_TYPE', + code: 'ERR_INVALID_ARG_VALUE', type: TypeError, - message: 'The "mode" argument must be of type number. ' + - 'Received type object' + message: 'The argument \'mode\' must be a 32-bit unsigned integer ' + + 'or an octal string. Received {}' } ); diff --git a/test/parallel/test-fs-fchmod.js b/test/parallel/test-fs-fchmod.js index 63d780a57dfc66..4f6350e63c241a 100644 --- a/test/parallel/test-fs-fchmod.js +++ b/test/parallel/test-fs-fchmod.js @@ -1,6 +1,7 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); +const util = require('util'); const fs = require('fs'); // This test ensures that input for fchmod is valid, testing for valid @@ -16,7 +17,16 @@ const fs = require('fs'); }; assert.throws(() => fs.fchmod(input), errObj); assert.throws(() => fs.fchmodSync(input), errObj); - errObj.message = errObj.message.replace('fd', 'mode'); +}); + + +[false, null, undefined, {}, [], '', '123x'].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError [ERR_INVALID_ARG_VALUE]', + message: 'The argument \'mode\' must be a 32-bit unsigned integer or an ' + + `octal string. Received ${util.inspect(input)}` + }; assert.throws(() => fs.fchmod(1, input), errObj); assert.throws(() => fs.fchmodSync(1, input), errObj); }); @@ -25,12 +35,21 @@ const fs = require('fs'); const errObj = { code: 'ERR_OUT_OF_RANGE', name: 'RangeError [ERR_OUT_OF_RANGE]', - message: 'The value of "fd" is out of range. It must be >= 0 && < ' + - `${2 ** 32}. Received ${input}` + message: 'The value of "fd" is out of range. It must be >= 0 && <= ' + + `2147483647. Received ${input}` }; assert.throws(() => fs.fchmod(input), errObj); assert.throws(() => fs.fchmodSync(input), errObj); - errObj.message = errObj.message.replace('fd', 'mode'); +}); + +[-1, 2 ** 32].forEach((input) => { + const errObj = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError [ERR_OUT_OF_RANGE]', + message: 'The value of "mode" is out of range. It must be >= 0 && < ' + + `4294967296. Received ${input}` + }; + assert.throws(() => fs.fchmod(1, input), errObj); assert.throws(() => fs.fchmodSync(1, input), errObj); }); @@ -62,27 +81,3 @@ const fs = require('fs'); assert.throws(() => fs.fchmod(1, input), errObj); assert.throws(() => fs.fchmodSync(1, input), errObj); }); - -// Check for mode values range -const modeUpperBoundaryValue = 0o777; -fs.fchmod(1, modeUpperBoundaryValue, common.mustCall()); -fs.fchmodSync(1, modeUpperBoundaryValue); - -// umask of 0o777 is equal to 775 -const modeOutsideUpperBoundValue = 776; -assert.throws( - () => fs.fchmod(1, modeOutsideUpperBoundValue), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]', - message: 'The value of "mode" is out of range. Received 776' - } -); -assert.throws( - () => fs.fchmodSync(1, modeOutsideUpperBoundValue), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]', - message: 'The value of "mode" is out of range. Received 776' - } -); diff --git a/test/parallel/test-fs-mkdir-mode-mask.js b/test/parallel/test-fs-mkdir-mode-mask.js new file mode 100644 index 00000000000000..515b982054b82b --- /dev/null +++ b/test/parallel/test-fs-mkdir-mode-mask.js @@ -0,0 +1,45 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs.mkdir(). + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; + +if (common.isWindows) { + common.skip('mode is not supported in mkdir on Windows'); + return; +} else { + mode = 0o644; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const dir = path.join(tmpdir.path, `mkdirSync-${suffix}`); + fs.mkdirSync(dir, input); + assert.strictEqual(fs.statSync(dir).mode & 0o777, mode); + } + + { + const dir = path.join(tmpdir.path, `mkdir-${suffix}`); + fs.mkdir(dir, input, common.mustCall((err) => { + assert.ifError(err); + assert.strictEqual(fs.statSync(dir).mode & 0o777, mode); + })); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-open-mode-mask.js b/test/parallel/test-fs-open-mode-mask.js new file mode 100644 index 00000000000000..0cd9a2c5923a71 --- /dev/null +++ b/test/parallel/test-fs-open-mode-mask.js @@ -0,0 +1,48 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs.open(). + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode; + +if (common.isWindows) { + mode = 0o444; +} else { + mode = 0o644; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `openSync-${suffix}.txt`); + const fd = fs.openSync(file, 'w+', input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = path.join(tmpdir.path, `open-${suffix}.txt`); + fs.open(file, 'w+', input, common.mustCall((err, fd) => { + assert.ifError(err); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } +} + +test(mode, true); +test(mode, false); diff --git a/test/parallel/test-fs-promises-file-handle-truncate.js b/test/parallel/test-fs-promises-file-handle-truncate.js new file mode 100644 index 00000000000000..44d919a042e276 --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-truncate.js @@ -0,0 +1,26 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const { open, readFile } = require('fs').promises; +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); +common.crashOnUnhandledRejection(); + +async function validateTruncate() { + const text = 'Hello world'; + const filename = path.resolve(tmpdir.path, 'truncate-file.txt'); + const fileHandle = await open(filename, 'w+'); + + const buffer = Buffer.from(text, 'utf8'); + await fileHandle.write(buffer, 0, buffer.length); + + assert.deepStrictEqual((await readFile(filename)).toString(), text); + + await fileHandle.truncate(5); + assert.deepStrictEqual((await readFile(filename)).toString(), 'Hello'); +} + +validateTruncate().then(common.mustCall()); diff --git a/test/parallel/test-fs-promises.js b/test/parallel/test-fs-promises.js index b7e01544162e81..6871a6762b68e2 100644 --- a/test/parallel/test-fs-promises.js +++ b/test/parallel/test-fs-promises.js @@ -11,19 +11,13 @@ const { access, chmod, copyFile, - fchmod, - fdatasync, - fstat, - fsync, - ftruncate, - futimes, link, lchmod, lstat, mkdir, mkdtemp, open, - read, + readFile, readdir, readlink, realpath, @@ -31,7 +25,7 @@ const { rmdir, stat, symlink, - write, + truncate, unlink, utimes } = fsPromises; @@ -75,13 +69,13 @@ function verifyStatObject(stat) { const handle = await open(dest, 'r+'); assert.strictEqual(typeof handle, 'object'); - let stats = await fstat(handle); + let stats = await handle.stat(); verifyStatObject(stats); assert.strictEqual(stats.size, 35); - await ftruncate(handle, 1); + await handle.truncate(1); - stats = await fstat(handle); + stats = await handle.stat(); verifyStatObject(stats); assert.strictEqual(stats.size, 1); @@ -91,15 +85,13 @@ function verifyStatObject(stat) { stats = await handle.stat(); verifyStatObject(stats); - await fdatasync(handle); await handle.datasync(); - await fsync(handle); await handle.sync(); const buf = Buffer.from('hello fsPromises'); const bufLen = buf.length; - await write(handle, buf); - const ret = await read(handle, Buffer.alloc(bufLen), 0, bufLen, 0); + await handle.write(buf); + const ret = await handle.read(Buffer.alloc(bufLen), 0, bufLen, 0); assert.strictEqual(ret.bytesRead, bufLen); assert.deepStrictEqual(ret.buffer, buf); @@ -109,38 +101,18 @@ function verifyStatObject(stat) { const ret2 = await handle.read(Buffer.alloc(buf2Len), 0, buf2Len, 0); assert.strictEqual(ret2.bytesRead, buf2Len); assert.deepStrictEqual(ret2.buffer, buf2); + await truncate(dest, 5); + assert.deepStrictEqual((await readFile(dest)).toString(), 'hello'); await chmod(dest, 0o666); - await fchmod(handle, 0o666); await handle.chmod(0o666); - // `mode` can't be > 0o777 - assert.rejects( - async () => chmod(handle, (0o777 + 1)), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError [ERR_INVALID_ARG_TYPE]' - } - ); - assert.rejects( - async () => fchmod(handle, (0o777 + 1)), - { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError [ERR_OUT_OF_RANGE]' - } - ); - assert.rejects( - async () => handle.chmod(handle, (0o777 + 1)), - { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError [ERR_INVALID_ARG_TYPE]' - } - ); + await chmod(dest, (0o10777)); + await handle.chmod(0o10777); await utimes(dest, new Date(), new Date()); try { - await futimes(handle, new Date(), new Date()); await handle.utimes(new Date(), new Date()); } catch (err) { // Some systems do not have futimes. If there is an error, diff --git a/test/parallel/test-fs-truncate.js b/test/parallel/test-fs-truncate.js index 347cc0e10492d6..735385f61c5249 100644 --- a/test/parallel/test-fs-truncate.js +++ b/test/parallel/test-fs-truncate.js @@ -220,17 +220,14 @@ function testFtruncate(cb) { `an integer. Received ${input}` } ); - }); - // 2 ** 31 = 2147483648 - [2147483648, -2147483649].forEach((input) => { assert.throws( () => fs.ftruncate(fd, input), { code: 'ERR_OUT_OF_RANGE', name: 'RangeError [ERR_OUT_OF_RANGE]', message: 'The value of "len" is out of range. It must be ' + - `> -2147483649 && < 2147483648. Received ${input}` + `an integer. Received ${input}` } ); }); diff --git a/test/parallel/test-fs-watch-close-when-destroyed.js b/test/parallel/test-fs-watch-close-when-destroyed.js new file mode 100644 index 00000000000000..fb5239bc446eff --- /dev/null +++ b/test/parallel/test-fs-watch-close-when-destroyed.js @@ -0,0 +1,38 @@ +'use strict'; + +// This tests that closing a watcher when the underlying handle is +// already destroyed will result in a noop instead of a crash. + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); +const root = path.join(tmpdir.path, 'watched-directory'); +fs.mkdirSync(root); + +const watcher = fs.watch(root, { persistent: false, recursive: false }); + +// The following listeners may or may not be invoked. + +watcher.addListener('error', () => { + setTimeout( + () => { watcher.close(); }, // Should not crash if it's invoked + common.platformTimeout(10) + ); +}); + +watcher.addListener('change', () => { + setTimeout( + () => { watcher.close(); }, + common.platformTimeout(10) + ); +}); + +fs.rmdirSync(root); +// Wait for the listener to hit +setTimeout( + common.mustCall(() => {}), + common.platformTimeout(100) +); diff --git a/test/parallel/test-fs-watch.js b/test/parallel/test-fs-watch.js index ffa82e52c73b72..e596f32a766926 100644 --- a/test/parallel/test-fs-watch.js +++ b/test/parallel/test-fs-watch.js @@ -46,7 +46,8 @@ for (const testCase of cases) { fs.writeFileSync(testCase.filePath, content1); let interval; - const watcher = fs.watch(testCase[testCase.field]); + const pathToWatch = testCase[testCase.field]; + const watcher = fs.watch(pathToWatch); watcher.on('error', (err) => { if (interval) { clearInterval(interval); @@ -54,7 +55,11 @@ for (const testCase of cases) { } assert.fail(err); }); - watcher.on('close', common.mustCall()); + watcher.on('close', common.mustCall(() => { + watcher.close(); // Closing a closed watcher should be a noop + // Starting a closed watcher should be a noop + watcher.start(); + })); watcher.on('change', common.mustCall(function(eventType, argFilename) { if (interval) { clearInterval(interval); @@ -66,10 +71,16 @@ for (const testCase of cases) { assert.strictEqual(eventType, 'change'); assert.strictEqual(argFilename, testCase.fileName); - watcher.start(); // Starting a started watcher should be a noop - // End of test case + // Starting a started watcher should be a noop + watcher.start(); + watcher.start(pathToWatch); + watcher.close(); + + // We document that watchers cannot be used anymore when it's closed, + // here we turn the methods into noops instead of throwing watcher.close(); // Closing a closed watcher should be a noop + watcher.start(); // Starting a closed watcher should be a noop })); // Long content so it's actually flushed. toUpperCase so there's real change. diff --git a/test/parallel/test-internal-fs-syncwritestream.js b/test/parallel/test-internal-fs-syncwritestream.js index c751baf555d20f..49c29e073818e8 100644 --- a/test/parallel/test-internal-fs-syncwritestream.js +++ b/test/parallel/test-internal-fs-syncwritestream.js @@ -5,7 +5,7 @@ const common = require('../common'); const assert = require('assert'); const fs = require('fs'); const path = require('path'); -const SyncWriteStream = require('internal/fs/utils').SyncWriteStream; +const SyncWriteStream = require('internal/fs/sync_write_stream'); const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); diff --git a/test/parallel/test-process-umask-mask.js b/test/parallel/test-process-umask-mask.js new file mode 100644 index 00000000000000..8ec8fc0074ac1b --- /dev/null +++ b/test/parallel/test-process-umask-mask.js @@ -0,0 +1,29 @@ +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in +// process.umask() + +const common = require('../common'); +const assert = require('assert'); + +let mask; + +if (common.isWindows) { + mask = 0o600; +} else { + mask = 0o664; +} + +const maskToIgnore = 0o10000; + +const old = process.umask(); + +function test(input, output) { + process.umask(input); + assert.strictEqual(process.umask(), output); + + process.umask(old); +} + +test(mask | maskToIgnore, mask); +test((mask | maskToIgnore).toString(8), mask); diff --git a/test/parallel/test-umask.js b/test/parallel/test-process-umask.js similarity index 92% rename from test/parallel/test-umask.js rename to test/parallel/test-process-umask.js index 1ac32cb7018906..c0c00f3ba397f8 100644 --- a/test/parallel/test-umask.js +++ b/test/parallel/test-process-umask.js @@ -33,13 +33,13 @@ if (common.isWindows) { const old = process.umask(mask); -assert.strictEqual(parseInt(mask, 8), process.umask(old)); +assert.strictEqual(process.umask(old), parseInt(mask, 8)); // confirm reading the umask does not modify it. // 1. If the test fails, this call will succeed, but the mask will be set to 0 -assert.strictEqual(old, process.umask()); +assert.strictEqual(process.umask(), old); // 2. If the test fails, process.umask() will return 0 -assert.strictEqual(old, process.umask()); +assert.strictEqual(process.umask(), old); assert.throws(() => { process.umask({}); diff --git a/test/parallel/test-repl-underscore.js b/test/parallel/test-repl-underscore.js index 628a7739fecf4e..f7fe0bcea80ca0 100644 --- a/test/parallel/test-repl-underscore.js +++ b/test/parallel/test-repl-underscore.js @@ -178,7 +178,7 @@ function testError() { // The sync error, with individual property echoes /Error: ENOENT: no such file or directory, scandir '.*nonexistent.*'/, - /fs\.readdirSync/, + /Object\.readdirSync/, "'ENOENT'", "'scandir'", diff --git a/test/parallel/test-sync-io-option.js b/test/parallel/test-sync-io-option.js index fb0b2333e82e4c..3dfe622963a0e2 100644 --- a/test/parallel/test-sync-io-option.js +++ b/test/parallel/test-sync-io-option.js @@ -20,7 +20,7 @@ if (process.argv[2] === 'child') { execFile(process.execPath, args, function(err, stdout, stderr) { assert.strictEqual(err, null); assert.strictEqual(stdout, ''); - if (/WARNING[\s\S]*fs\.readFileSync/.test(stderr)) + if (/WARNING[\s\S]*readFileSync/.test(stderr)) cntr++; if (args[0] === '--trace-sync-io') { assert.strictEqual(cntr, 1);