From bacb2cb550553d27ba7303e4bdac2c3dc7d9a54e Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 15 May 2018 12:34:49 -0700 Subject: [PATCH] fs: refactor fs module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/20764 Reviewed-By: Michaƫl Zasso Reviewed-By: Joyee Cheung Backport-PR-URL: https://github.com/nodejs/node/pull/21172 --- lib/fs.js | 1361 ++++++----------- lib/internal/fs/read_file_context.js | 108 ++ lib/internal/fs/streams.js | 362 +++++ lib/internal/fs/sync_write_stream.js | 45 + lib/internal/fs/utils.js | 42 - lib/internal/fs/watchers.js | 168 ++ lib/internal/process/stdio.js | 4 +- node.gyp | 4 + src/node_constants.cc | 24 +- .../test-internal-fs-syncwritestream.js | 2 +- test/parallel/test-repl-underscore.js | 2 +- test/parallel/test-sync-io-option.js | 2 +- 12 files changed, 1143 insertions(+), 981 deletions(-) create mode 100644 lib/internal/fs/read_file_context.js create mode 100644 lib/internal/fs/streams.js create mode 100644 lib/internal/fs/sync_write_stream.js create mode 100644 lib/internal/fs/watchers.js diff --git a/lib/fs.js b/lib/fs.js index 33d9967f54584a..49050af6677cd6 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -36,29 +36,22 @@ 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'); @@ -90,44 +83,24 @@ const { 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,24 +391,24 @@ 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) { +function open(path, flags, mode, callback) { path = getPathFromURL(path); validatePath(path); const flagsNumber = stringToFlags(flags); @@ -569,9 +427,10 @@ fs.open = function(path, flags, mode, callback) { flagsNumber, mode, req); -}; +} + -fs.openSync = function(path, flags, mode) { +function openSync(path, flags, mode) { path = getPathFromURL(path); validatePath(path); const flagsNumber = stringToFlags(flags); @@ -583,9 +442,9 @@ fs.openSync = function(path, flags, 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); @@ -612,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); @@ -638,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); @@ -680,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; @@ -713,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'); @@ -726,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); @@ -737,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); @@ -755,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); @@ -763,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(); @@ -775,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); @@ -784,9 +643,9 @@ 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; @@ -797,63 +656,63 @@ fs.ftruncate = function(fd, len = 0, callback) { 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'); 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) { +function mkdir(path, mode, callback) { path = getPathFromURL(path); validatePath(path); @@ -868,18 +727,18 @@ fs.mkdir = function(path, mode, callback) { 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 = validateAndMaskMode(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); @@ -888,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); @@ -899,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 }; @@ -942,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 }; @@ -952,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); @@ -962,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'); @@ -973,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); @@ -990,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); @@ -1005,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); @@ -1021,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'); @@ -1035,26 +894,26 @@ 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) { +function fchmod(fd, mode, callback) { validateInt32(fd, 'fd', 0); mode = validateAndMaskMode(mode, 'mode'); callback = makeCallback(callback); @@ -1062,51 +921,49 @@ fs.fchmod = function(fd, mode, callback) { const req = new FSReqWrap(); req.oncomplete = callback; binding.fchmod(fd, mode, req); -}; +} -fs.fchmodSync = function(fd, mode) { +function fchmodSync(fd, mode) { validateInt32(fd, 'fd', 0); mode = validateAndMaskMode(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) { +function chmod(path, mode, callback) { path = getPathFromURL(path); validatePath(path); mode = validateAndMaskMode(mode, 'mode'); @@ -1115,9 +972,9 @@ fs.chmod = function(path, mode, 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 = validateAndMaskMode(mode, 'mode'); @@ -1125,39 +982,37 @@ fs.chmodSync = function(path, 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'); @@ -1165,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'); @@ -1175,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); @@ -1187,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'); @@ -1197,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); @@ -1213,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 }; @@ -1223,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) @@ -1271,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'; @@ -1290,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) { @@ -1323,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' }); @@ -1337,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 @@ -1350,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; } @@ -1439,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, @@ -1450,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. @@ -1546,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; @@ -1559,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; @@ -1586,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. @@ -1619,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) { @@ -1636,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 @@ -1659,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); @@ -1684,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; @@ -1706,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); @@ -1727,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]; @@ -1768,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); @@ -1782,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; @@ -1799,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; @@ -1830,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; @@ -1869,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); @@ -1889,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) { @@ -1910,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); @@ -1923,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); @@ -1948,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; @@ -1984,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'); @@ -2000,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/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 46b3a97f741572..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'); @@ -256,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 @@ -383,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..685d5be5e4db3f --- /dev/null +++ b/lib/internal/fs/watchers.js @@ -0,0 +1,168 @@ +'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) { + this._handle.close(); + 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 +// This method is a noop if the watcher has already been started. +FSWatcher.prototype.start = function(filename, + persistent, + recursive, + encoding) { + assert(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(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() { + assert(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'); +} + +module.exports = { + FSWatcher, + StatWatcher +}; 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/node.gyp b/node.gyp index 7bcc17ce843798..d6b32cd3b75b6c 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', 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/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-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);