summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2018-05-15 12:34:49 -0700
committerJames M Snell <jasnell@gmail.com>2018-05-20 14:15:56 -0700
commit7f0f978affda555c5f7151f2d6abd212e753d4f1 (patch)
tree3f40904376635a00f0a15cd33186be09715e903c
parenta14a0fa8dc401188f67d26a82daae9423c54b41f (diff)
downloadandroid-node-v8-7f0f978affda555c5f7151f2d6abd212e753d4f1.tar.gz
android-node-v8-7f0f978affda555c5f7151f2d6abd212e753d4f1.tar.bz2
android-node-v8-7f0f978affda555c5f7151f2d6abd212e753d4f1.zip
fs: refactor fs module
PR-URL: https://github.com/nodejs/node/pull/20764 Reviewed-By: Michaƫl Zasso <targos@protonmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
-rw-r--r--lib/fs.js1382
-rw-r--r--lib/internal/fs/read_file_context.js108
-rw-r--r--lib/internal/fs/streams.js383
-rw-r--r--lib/internal/fs/sync_write_stream.js45
-rw-r--r--lib/internal/fs/utils.js42
-rw-r--r--lib/internal/fs/watchers.js168
-rw-r--r--lib/internal/process/stdio.js4
-rw-r--r--node.gyp4
-rw-r--r--src/node_constants.cc24
-rw-r--r--test/parallel/test-internal-fs-syncwritestream.js2
-rw-r--r--test/parallel/test-repl-underscore.js2
-rw-r--r--test/parallel/test-sync-io-option.js2
12 files changed, 1164 insertions, 1002 deletions
diff --git a/lib/fs.js b/lib/fs.js
index f46eabf9a0..221f79ae0b 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');
@@ -89,44 +82,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) {
@@ -188,23 +161,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;
@@ -214,12 +177,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);
@@ -231,9 +194,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) {
@@ -245,9 +208,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();
@@ -262,93 +225,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);
@@ -357,23 +244,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 = [];
@@ -394,52 +277,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) {
@@ -453,8 +315,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);
@@ -468,8 +330,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;
@@ -479,20 +341,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 = [];
@@ -500,7 +358,7 @@ fs.readFileSync = function(path, options) {
buffer = tryCreateBuffer(size, fd, isUserFd);
}
- var bytesRead;
+ let bytesRead;
if (size !== 0) {
do {
@@ -532,24 +390,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);
@@ -568,9 +426,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);
@@ -582,9 +441,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);
@@ -611,12 +470,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);
@@ -637,13 +496,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);
@@ -679,16 +538,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;
@@ -712,9 +571,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');
@@ -725,9 +584,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);
@@ -736,9 +595,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);
@@ -753,7 +612,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);
@@ -761,9 +620,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();
@@ -773,8 +632,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);
@@ -782,9 +641,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;
@@ -799,63 +658,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');
validateInt32(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);
@@ -870,18 +729,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);
@@ -890,9 +749,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);
@@ -901,42 +760,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 };
@@ -944,9 +803,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 };
@@ -954,9 +813,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);
@@ -964,9 +823,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');
@@ -975,11 +834,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);
@@ -992,9 +851,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);
@@ -1007,9 +866,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);
@@ -1023,9 +882,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');
@@ -1037,26 +896,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);
@@ -1064,51 +923,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');
@@ -1117,9 +974,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');
@@ -1127,39 +984,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');
@@ -1167,9 +1022,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');
@@ -1177,9 +1032,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);
@@ -1189,9 +1044,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');
@@ -1199,12 +1054,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);
@@ -1215,9 +1067,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 };
@@ -1225,25 +1077,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)
@@ -1273,7 +1125,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';
@@ -1292,30 +1144,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) {
@@ -1325,9 +1177,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' });
@@ -1339,9 +1191,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
@@ -1352,84 +1204,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;
}
@@ -1441,7 +1218,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,
@@ -1452,94 +1231,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.
@@ -1548,7 +1251,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;
@@ -1561,20 +1264,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;
@@ -1588,10 +1293,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.
@@ -1621,7 +1326,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) {
@@ -1638,7 +1343,7 @@ if (isWindows) {
}
const emptyObj = Object.create(null);
-fs.realpathSync = function realpathSync(p, options) {
+function realpathSync(p, options) {
if (!options)
options = emptyObj;
else
@@ -1661,13 +1366,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);
@@ -1686,10 +1391,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;
@@ -1708,15 +1413,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);
@@ -1729,11 +1434,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];
@@ -1770,10 +1475,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);
@@ -1784,7 +1489,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;
@@ -1801,13 +1506,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;
@@ -1832,10 +1537,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;
@@ -1871,8 +1576,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);
@@ -1891,8 +1596,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) {
@@ -1912,10 +1616,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);
@@ -1925,20 +1629,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);
@@ -1950,24 +1654,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;
@@ -1986,10 +1676,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');
@@ -2002,379 +1692,167 @@ 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 (!Number.isSafeInteger(this.start)) {
- if (typeof this.start !== 'number')
- throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start);
- if (!Number.isInteger(this.start))
- throw new ERR_OUT_OF_RANGE('start', 'an integer', this.start);
- throw new ERR_OUT_OF_RANGE(
- 'start',
- '>= 0 and <= 2 ** 53 - 1',
- this.start
- );
- }
- if (this.start < 0) {
- throw new ERR_OUT_OF_RANGE(
- 'start',
- '>= 0 and <= 2 ** 53 - 1',
- this.start
- );
- }
-
- this.pos = this.start;
- }
-
- if (this.end === undefined) {
- this.end = Infinity;
- } else if (this.end !== Infinity) {
- if (!Number.isSafeInteger(this.end)) {
- if (typeof this.end !== 'number')
- throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end);
- if (!Number.isInteger(this.end))
- throw new ERR_OUT_OF_RANGE('end', 'an integer', this.end);
- throw new ERR_OUT_OF_RANGE('end', '>= 0 and <= 2 ** 53 - 1', this.end);
- }
-
- if (this.end < 0) {
- throw new ERR_OUT_OF_RANGE('end', '>= 0 and <= 2 ** 53 - 1', this.end);
- }
-
- if (this.start !== undefined && this.start > this.end) {
- throw new ERR_OUT_OF_RANGE(
- 'start',
- `<= "end" (here: ${this.end})`,
- this.start
- );
- }
+function lazyLoadStreams() {
+ if (!ReadStream) {
+ ({ ReadStream, WriteStream } = require('internal/fs/streams'));
+ [ FileReadStream, FileWriteStream ] = [ ReadStream, WriteStream ];
}
-
- 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;
+});
diff --git a/lib/internal/fs/read_file_context.js b/lib/internal/fs/read_file_context.js
new file mode 100644
index 0000000000..b3e81a5db1
--- /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 0000000000..f527b1de4b
--- /dev/null
+++ b/lib/internal/fs/streams.js
@@ -0,0 +1,383 @@
+'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 (!Number.isSafeInteger(this.start)) {
+ if (typeof this.start !== 'number')
+ throw new ERR_INVALID_ARG_TYPE('start', 'number', this.start);
+ if (!Number.isInteger(this.start))
+ throw new ERR_OUT_OF_RANGE('start', 'an integer', this.start);
+ throw new ERR_OUT_OF_RANGE(
+ 'start',
+ '>= 0 and <= 2 ** 53 - 1',
+ this.start
+ );
+ }
+ if (this.start < 0) {
+ throw new ERR_OUT_OF_RANGE(
+ 'start',
+ '>= 0 and <= 2 ** 53 - 1',
+ this.start
+ );
+ }
+
+ this.pos = this.start;
+ }
+
+ if (this.end === undefined) {
+ this.end = Infinity;
+ } else if (this.end !== Infinity) {
+ if (!Number.isSafeInteger(this.end)) {
+ if (typeof this.end !== 'number')
+ throw new ERR_INVALID_ARG_TYPE('end', 'number', this.end);
+ if (!Number.isInteger(this.end))
+ throw new ERR_OUT_OF_RANGE('end', 'an integer', this.end);
+ throw new ERR_OUT_OF_RANGE('end', '>= 0 and <= 2 ** 53 - 1', this.end);
+ }
+
+ if (this.end < 0) {
+ throw new ERR_OUT_OF_RANGE('end', '>= 0 and <= 2 ** 53 - 1', this.end);
+ }
+
+ if (this.start !== undefined && this.start > this.end) {
+ throw new ERR_OUT_OF_RANGE(
+ 'start',
+ `<= "end" (here: ${this.end})`,
+ this.start
+ );
+ }
+ }
+
+ 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 0000000000..b365474663
--- /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 46b3a97f74..a8c64e2b04 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 0000000000..685d5be5e4
--- /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 ce84142938..eaba4dfca1 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 927ad56a3e..bf768c1995 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 68339c0032..c1e39244b3 100644
--- a/src/node_constants.cc
+++ b/src/node_constants.cc
@@ -1162,6 +1162,27 @@ void DefineSystemConstants(Local<Object> 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<Object> target) {
@@ -1305,9 +1326,6 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> 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 c751baf555..49c29e0738 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 57929244ae..79b313195b 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 fb0b2333e8..3dfe622963 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);