0
0
mirror of https://github.com/nodejs/node.git synced 2024-12-01 16:10:02 +01:00
nodejs/lib/fs.js

2381 lines
62 KiB
JavaScript
Raw Normal View History

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Maintainers, keep in mind that ES1-style octal literals (`0666`) are not
// allowed in strict mode. Use ES6-style octal literals instead (`0o666`).
'use strict';
const { fs: constants } = process.binding('constants');
const {
S_IFIFO,
S_IFLNK,
S_IFMT,
S_IFREG,
S_IFSOCK,
F_OK,
R_OK,
W_OK,
X_OK,
O_WRONLY,
O_SYMLINK,
UV_FS_COPYFILE_EXCL,
UV_FS_COPYFILE_FICLONE,
UV_FS_COPYFILE_FICLONE_FORCE
} = constants;
const util = 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
} = errors.codes;
const { Readable, Writable } = require('stream');
const EventEmitter = require('events');
const { FSReqWrap, statValues, kFsStatsFieldsLength } = binding;
const { FSEvent } = process.binding('fs_event_wrap');
const internalFS = require('internal/fs/utils');
const { getPathFromURL } = require('internal/url');
const internalUtil = require('internal/util');
const {
copyObject,
getOptions,
nullCheck,
preprocessSymlinkDestination,
Stats,
getStatsFromBinding,
realpathCacheKey,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
validateBuffer,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath
} = internalFS;
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
} = require('internal/constants');
const {
isUint32,
validateAndMaskMode,
validateInt32,
validateUint32
} = require('internal/validators');
2010-03-11 23:32:10 +01:00
// Lazy loaded
let promises;
let promisesWarn = true;
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_;
}
const kMinPoolSpace = 128;
const isWindows = process.platform === 'win32';
let truncateWarn = true;
function showTruncateDeprecation() {
if (truncateWarn) {
process.emitWarning(
'Using fs.truncate with a file descriptor is deprecated. Please use ' +
'fs.ftruncate with a file descriptor instead.',
'DeprecationWarning', 'DEP0081');
truncateWarn = false;
}
}
function handleErrorFromBinding(ctx) {
if (ctx.errno !== undefined) { // libuv error numbers
const err = errors.uvException(ctx);
Error.captureStackTrace(err, handleErrorFromBinding);
throw err;
} else if (ctx.error !== undefined) { // errors created in C++ land.
// TODO(joyeecheung): currently, ctx.error are encoding errors
// usually caused by memory problems. We need to figure out proper error
// code(s) for this.
Error.captureStackTrace(ctx.error, handleErrorFromBinding);
throw ctx.error;
}
}
function maybeCallback(cb) {
if (typeof cb === 'function')
return cb;
throw new ERR_INVALID_CALLBACK();
}
// Ensure that callbacks run in the global context. Only use this function
// for callbacks that are passed to the binding layer, callbacks that are
// invoked from JS already run in the proper scope.
function makeCallback(cb) {
if (typeof cb !== 'function') {
throw new ERR_INVALID_CALLBACK();
}
return function(...args) {
return Reflect.apply(cb, undefined, args);
};
}
// Special case of `makeCallback()` that is specific to async `*stat()` calls as
// an optimization, since the data passed back to the callback needs to be
// transformed anyway.
function makeStatsCallback(cb) {
if (typeof cb !== 'function') {
throw new ERR_INVALID_CALLBACK();
}
return function(err, stats) {
if (err) return cb(err);
cb(err, getStatsFromBinding(stats));
};
}
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) {
if (typeof mode === 'function') {
callback = mode;
mode = F_OK;
}
path = getPathFromURL(path);
validatePath(path);
mode = mode | 0;
var req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.access(pathModule.toNamespacedPath(path), mode, req);
};
fs.accessSync = function(path, mode) {
path = getPathFromURL(path);
validatePath(path);
if (mode === undefined)
mode = F_OK;
else
mode = mode | 0;
const ctx = { path };
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
};
fs.exists = function(path, callback) {
maybeCallback(callback);
function suppressedCallback(err) {
callback(err ? false : true);
}
try {
fs.access(path, F_OK, suppressedCallback);
} catch (err) {
return callback(false);
}
};
Object.defineProperty(fs.exists, internalUtil.promisify.custom, {
value: (path) => {
const { createPromise, promiseResolve } = process.binding('util');
const promise = createPromise();
fs.exists(path, (exists) => promiseResolve(promise, exists));
return promise;
}
});
// fs.existsSync never throws, it only returns true or false.
// Since fs.existsSync never throws, users have established
// the expectation that passing invalid arguments to it, even like
// 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) {
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;
2010-12-02 02:43:30 +01:00
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;
fs: partition readFile against pool exhaustion Problem: Node implements fs.readFile as: - a call to stat, then - a C++ -> libuv request to read the entire file using the stat size Why is this bad? The effect is to place on the libuv threadpool a potentially-large read request, occupying the libuv thread until it completes. While readFile certainly requires buffering the entire file contents, it can partition the read into smaller buffers (as is done on other read paths) along the way to avoid threadpool exhaustion. If the file is relatively large or stored on a slow medium, reading the entire file in one shot seems particularly harmful, and presents a possible DoS vector. Solution: Partition the read into multiple smaller requests. Considerations: 1. Correctness I don't think partitioning the read like this raises any additional risk of read-write races on the FS. If the application is concurrently readFile'ing and modifying the file, it will already see funny behavior. Though libuv uses preadv where available, this doesn't guarantee read atomicity in the presence of concurrent writes. 2. Performance Downside: Partitioning means that a single large readFile will require into many "out and back" requests to libuv, introducing overhead. Upside: In between each "out and back", other work pending on the threadpool can take a turn. In short, although partitioning will slow down a large request, it will lead to better throughput if the threadpool is handling more than one type of request. Fixes: https://github.com/nodejs/node/issues/17047 PR-URL: https://github.com/nodejs/node/pull/17054 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
2017-11-15 18:28:04 +01:00
length = Math.min(kReadFileBufferLength, this.size - this.pos);
2012-05-16 02:35:42 +02:00
}
var req = new FSReqWrap();
req.oncomplete = readFileAfterRead;
req.context = this;
2012-05-16 02:35:42 +02:00
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;
if (err) {
context.callback(err);
return;
2012-05-16 02:35:42 +02:00
}
context.fd = fd;
var req = new FSReqWrap();
req.oncomplete = readFileAfterStat;
req.context = context;
binding.fstat(fd, req);
}
function readFileAfterStat(err, stats) {
var 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;
if (size === 0) {
context.buffers = [];
context.read();
return;
2012-05-16 02:35:42 +02:00
}
if (size > kMaxLength) {
err = new ERR_FS_FILE_TOO_LARGE(size);
return context.close(err);
}
try {
context.buffer = Buffer.allocUnsafeSlow(size);
} catch (err) {
return context.close(err);
}
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;
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);
}
2010-03-15 18:41:58 +01:00
function tryStatSync(fd, isUserFd) {
const ctx = {};
const stats = binding.fstat(fd, undefined, ctx);
if (ctx.errno !== undefined && !isUserFd) {
fs.closeSync(fd);
throw errors.uvException(ctx);
}
return stats;
}
function tryCreateBuffer(size, fd, isUserFd) {
var threw = true;
var buffer;
try {
if (size > kMaxLength) {
throw new ERR_FS_FILE_TOO_LARGE(size);
}
buffer = Buffer.allocUnsafe(size);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
return buffer;
}
function tryReadSync(fd, isUserFd, buffer, pos, len) {
var threw = true;
var bytesRead;
try {
bytesRead = fs.readSync(fd, buffer, pos, len);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
return bytesRead;
}
fs.readFileSync = function(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 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
2012-05-16 02:35:42 +02:00
if (size === 0) {
buffers = [];
} else {
buffer = tryCreateBuffer(size, fd, isUserFd);
}
2010-03-15 18:41:58 +01:00
var bytesRead;
if (size !== 0) {
do {
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
pos += bytesRead;
} while (bytesRead !== 0 && pos < size);
} else {
do {
// the kernel lies about many files.
// Go ahead and try to read some bytes.
buffer = Buffer.allocUnsafe(8192);
bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192);
if (bytesRead !== 0) {
buffers.push(buffer.slice(0, bytesRead));
}
pos += bytesRead;
} while (bytesRead !== 0);
}
if (!isUserFd)
fs.closeSync(fd);
2012-05-16 02:35:42 +02:00
if (size === 0) {
// data was collected into the buffers list.
buffer = Buffer.concat(buffers, pos);
} else if (pos < size) {
buffer = buffer.slice(0, pos);
}
if (options.encoding) buffer = buffer.toString(options.encoding);
return buffer;
2010-03-15 18:41:58 +01:00
};
2010-12-02 02:43:30 +01:00
fs.close = function(fd, callback) {
validateUint32(fd, 'fd');
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.close(fd, req);
};
2010-12-02 02:43:30 +01:00
fs.closeSync = function(fd) {
validateUint32(fd, 'fd');
const ctx = {};
binding.close(fd, undefined, ctx);
handleErrorFromBinding(ctx);
};
fs.open = function(path, flags, mode, callback) {
path = getPathFromURL(path);
validatePath(path);
const flagsNumber = stringToFlags(flags);
if (arguments.length < 4) {
callback = makeCallback(mode);
mode = 0o666;
} else {
mode = validateAndMaskMode(mode, 'mode', 0o666);
callback = makeCallback(callback);
}
const req = new FSReqWrap();
req.oncomplete = callback;
binding.open(pathModule.toNamespacedPath(path),
flagsNumber,
2012-02-19 00:01:35 +01:00
mode,
req);
};
2010-12-02 02:43:30 +01:00
fs.openSync = function(path, flags, mode) {
path = getPathFromURL(path);
validatePath(path);
const flagsNumber = stringToFlags(flags);
mode = validateAndMaskMode(mode, 'mode', 0o666);
const ctx = { path };
const result = binding.open(pathModule.toNamespacedPath(path),
flagsNumber, mode,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
2010-12-02 02:43:30 +01:00
fs.read = function(fd, buffer, offset, length, position, callback) {
validateUint32(fd, 'fd');
validateBuffer(buffer);
offset |= 0;
length |= 0;
if (length === 0) {
return process.nextTick(function tick() {
callback && callback(null, 0, buffer);
});
}
validateOffsetLengthRead(offset, length, buffer.length);
if (!isUint32(position))
position = -1;
function wrapper(err, bytesRead) {
// Retain a reference to buffer so that it can't be GC'ed too soon.
callback && callback(err, bytesRead || 0, buffer);
}
const req = new FSReqWrap();
req.oncomplete = wrapper;
binding.read(fd, buffer, offset, length, position, req);
};
Object.defineProperty(fs.read, internalUtil.customPromisifyArgs,
{ value: ['bytesRead', 'buffer'], enumerable: false });
2010-12-02 02:43:30 +01:00
fs.readSync = function(fd, buffer, offset, length, position) {
validateUint32(fd, 'fd');
validateBuffer(buffer);
offset |= 0;
length |= 0;
if (length === 0) {
return 0;
}
validateOffsetLengthRead(offset, length, buffer.length);
if (!isUint32(position))
position = -1;
const ctx = {};
const result = binding.read(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);
2010-12-02 02:43:30 +01:00
fs.write = function(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);
}
validateUint32(fd, 'fd');
const req = new FSReqWrap();
req.oncomplete = wrapper;
if (isUint8Array(buffer)) {
callback = maybeCallback(callback || position || length || offset);
if (typeof offset !== 'number')
offset = 0;
if (typeof length !== 'number')
length = buffer.length - offset;
if (typeof position !== 'number')
position = null;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
return binding.writeBuffer(fd, buffer, offset, length, position, req);
}
if (typeof buffer !== 'string')
buffer += '';
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
callback = maybeCallback(position);
return binding.writeString(fd, buffer, offset, length, req);
};
Object.defineProperty(fs.write, internalUtil.customPromisifyArgs,
{ value: ['bytesWritten', 'buffer'], enumerable: false });
// usage:
// fs.writeSync(fd, buffer[, offset[, length[, position]]]);
// OR
// fs.writeSync(fd, string[, position[, encoding]]);
2010-12-02 02:43:30 +01:00
fs.writeSync = function(fd, buffer, offset, length, position) {
validateUint32(fd, 'fd');
const ctx = {};
let result;
if (isUint8Array(buffer)) {
if (position === undefined)
position = null;
if (typeof offset !== 'number')
offset = 0;
if (typeof length !== 'number')
length = buffer.length - offset;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
result = binding.writeBuffer(fd, buffer, offset, length, position,
undefined, ctx);
} else {
if (typeof buffer !== 'string')
buffer += '';
if (offset === undefined)
offset = null;
result = binding.writeString(fd, buffer, offset, length,
undefined, ctx);
}
handleErrorFromBinding(ctx);
return result;
};
2010-12-02 02:43:30 +01:00
fs.rename = function(oldPath, newPath, callback) {
callback = makeCallback(callback);
oldPath = getPathFromURL(oldPath);
validatePath(oldPath, 'oldPath');
newPath = getPathFromURL(newPath);
validatePath(newPath, 'newPath');
const req = new FSReqWrap();
req.oncomplete = callback;
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
req);
};
2010-12-02 02:43:30 +01:00
fs.renameSync = function(oldPath, newPath) {
oldPath = getPathFromURL(oldPath);
validatePath(oldPath, 'oldPath');
newPath = getPathFromURL(newPath);
validatePath(newPath, 'newPath');
const ctx = { path: oldPath, dest: newPath };
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath), undefined, ctx);
handleErrorFromBinding(ctx);
};
fs.truncate = function(path, len, callback) {
if (typeof path === 'number') {
showTruncateDeprecation();
return fs.ftruncate(path, len, callback);
}
if (typeof len === 'function') {
callback = len;
len = 0;
} else if (len === undefined) {
len = 0;
}
callback = maybeCallback(callback);
fs.open(path, 'r+', function(er, fd) {
if (er) return callback(er);
var req = new FSReqWrap();
req.oncomplete = function oncomplete(er) {
fs.close(fd, function(er2) {
callback(er || er2);
});
};
binding.ftruncate(fd, len, req);
});
};
fs.truncateSync = function(path, len) {
if (typeof path === 'number') {
// legacy
showTruncateDeprecation();
return fs.ftruncateSync(path, len);
}
if (len === undefined) {
len = 0;
}
// allow error to be thrown, but still close fd.
var fd = fs.openSync(path, 'r+');
var ret;
try {
ret = fs.ftruncateSync(fd, len);
} finally {
fs.closeSync(fd);
}
return ret;
};
fs.ftruncate = function(fd, len = 0, callback) {
if (typeof len === 'function') {
callback = len;
len = 0;
}
validateUint32(fd, 'fd');
// TODO(BridgeAR): This does not seem right.
// There does not seem to be any validation before and if there is any, it
// should work similar to validateUint32 or not have a upper cap at all.
// This applies to all usage of `validateInt32(len, 'len')`.
validateInt32(len, 'len');
len = Math.max(0, len);
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.ftruncate(fd, len, req);
};
fs.ftruncateSync = function(fd, len = 0) {
validateUint32(fd, 'fd');
validateInt32(len, 'len');
len = Math.max(0, len);
const ctx = {};
binding.ftruncate(fd, len, undefined, ctx);
handleErrorFromBinding(ctx);
};
2010-12-02 02:43:30 +01:00
fs.rmdir = function(path, callback) {
callback = makeCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.rmdir(pathModule.toNamespacedPath(path), req);
};
2010-12-02 02:43:30 +01:00
fs.rmdirSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
handleErrorFromBinding(ctx);
};
2010-12-02 02:43:30 +01:00
fs.fdatasync = function(fd, callback) {
validateUint32(fd, 'fd');
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.fdatasync(fd, req);
};
2010-12-02 02:43:30 +01:00
fs.fdatasyncSync = function(fd) {
validateUint32(fd, 'fd');
const ctx = {};
binding.fdatasync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
};
2010-12-02 02:43:30 +01:00
fs.fsync = function(fd, callback) {
validateUint32(fd, 'fd');
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.fsync(fd, req);
};
2010-12-02 02:43:30 +01:00
fs.fsyncSync = function(fd) {
validateUint32(fd, 'fd');
const ctx = {};
binding.fsync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
};
2010-12-02 02:43:30 +01:00
fs.mkdir = function(path, mode, callback) {
path = getPathFromURL(path);
validatePath(path);
if (arguments.length < 3) {
callback = makeCallback(mode);
mode = 0o777;
} else {
callback = makeCallback(callback);
mode = validateAndMaskMode(mode, 'mode', 0o777);
}
const req = new FSReqWrap();
req.oncomplete = callback;
binding.mkdir(pathModule.toNamespacedPath(path), mode, req);
};
2010-12-02 02:43:30 +01:00
fs.mkdirSync = function(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) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
};
fs.readdirSync = function(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
2010-12-02 02:43:30 +01:00
fs.fstat = function(fd, callback) {
validateUint32(fd, 'fd');
const req = new FSReqWrap();
req.oncomplete = makeStatsCallback(callback);
binding.fstat(fd, req);
};
2010-12-02 02:43:30 +01:00
fs.lstat = function(path, callback) {
callback = makeStatsCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.lstat(pathModule.toNamespacedPath(path), req);
};
2010-12-02 02:43:30 +01:00
fs.stat = function(path, callback) {
callback = makeStatsCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.stat(pathModule.toNamespacedPath(path), req);
};
2010-12-02 02:43:30 +01:00
fs.fstatSync = function(fd) {
validateUint32(fd, 'fd');
const ctx = { fd };
const stats = binding.fstat(fd, undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
};
2010-12-02 02:43:30 +01:00
fs.lstatSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
const stats = binding.lstat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
};
2010-12-02 02:43:30 +01:00
fs.statSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
const stats = binding.stat(pathModule.toNamespacedPath(path),
undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
};
fs.readlink = function(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path, 'oldPath');
const req = new FSReqWrap();
req.oncomplete = callback;
binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req);
};
fs.readlinkSync = function(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path, 'oldPath');
const ctx = { path };
const result = binding.readlink(pathModule.toNamespacedPath(path),
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]);
target = getPathFromURL(target);
path = getPathFromURL(path);
validatePath(target, 'target');
validatePath(path);
const flags = stringToSymlinkType(type);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path), flags, req);
};
fs.symlinkSync = function(target, path, type) {
type = (typeof type === 'string' ? type : null);
target = getPathFromURL(target);
path = getPathFromURL(path);
validatePath(target, 'target');
validatePath(path);
const flags = stringToSymlinkType(type);
const ctx = { path: target, dest: path };
binding.symlink(preprocessSymlinkDestination(target, type, path),
pathModule.toNamespacedPath(path), flags, undefined, ctx);
handleErrorFromBinding(ctx);
};
fs.link = function(existingPath, newPath, callback) {
callback = makeCallback(callback);
existingPath = getPathFromURL(existingPath);
newPath = getPathFromURL(newPath);
validatePath(existingPath, 'existingPath');
validatePath(newPath, 'newPath');
const req = new FSReqWrap();
req.oncomplete = callback;
binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
req);
};
fs.linkSync = function(existingPath, newPath) {
existingPath = getPathFromURL(existingPath);
newPath = getPathFromURL(newPath);
validatePath(existingPath, 'existingPath');
validatePath(newPath, 'newPath');
const ctx = { path: existingPath, dest: newPath };
const result = binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
2010-12-02 02:43:30 +01:00
fs.unlink = function(path, callback) {
callback = makeCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.unlink(pathModule.toNamespacedPath(path), req);
};
2010-12-02 02:43:30 +01:00
fs.unlinkSync = function(path) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx);
handleErrorFromBinding(ctx);
};
2011-03-30 00:31:41 +02:00
fs.fchmod = function(fd, mode, callback) {
validateUint32(fd, 'fd');
mode = validateAndMaskMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.fchmod(fd, mode, req);
2011-03-30 00:31:41 +02:00
};
fs.fchmodSync = function(fd, mode) {
validateUint32(fd, 'fd');
mode = validateAndMaskMode(mode, 'mode');
const ctx = {};
binding.fchmod(fd, mode, undefined, ctx);
handleErrorFromBinding(ctx);
2011-03-30 00:31:41 +02:00
};
if (O_SYMLINK !== undefined) {
2011-03-30 01:34:05 +02:00
fs.lchmod = function(path, mode, callback) {
callback = maybeCallback(callback);
fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) {
2011-03-30 01:34:05 +02:00
if (err) {
callback(err);
return;
}
// Prefer to return the chmod error, if one occurs,
2011-11-22 22:10:57 +01:00
// 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);
});
});
2011-03-30 01:34:05 +02:00
});
};
fs.lchmodSync = function(path, mode) {
const fd = fs.openSync(path, O_WRONLY | O_SYMLINK);
2011-11-22 22:10:57 +01:00
// Prefer to return the chmod error, if one occurs,
2011-11-22 22:10:57 +01:00
// but still try to close, and report closing errors if they occur.
let ret;
2011-11-22 22:10:57 +01:00
try {
ret = fs.fchmodSync(fd, mode);
} finally {
fs.closeSync(fd);
2011-11-22 22:10:57 +01:00
}
return ret;
2011-03-30 01:34:05 +02:00
};
}
2010-12-02 02:43:30 +01:00
fs.chmod = function(path, mode, callback) {
path = getPathFromURL(path);
validatePath(path);
mode = validateAndMaskMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.chmod(pathModule.toNamespacedPath(path), mode, req);
};
2010-12-02 02:43:30 +01:00
fs.chmodSync = function(path, mode) {
path = getPathFromURL(path);
validatePath(path);
mode = validateAndMaskMode(mode, 'mode');
const ctx = { path };
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
};
if (O_SYMLINK !== undefined) {
2011-03-30 01:34:05 +02:00
fs.lchown = function(path, uid, gid, callback) {
callback = maybeCallback(callback);
fs.open(path, O_WRONLY | O_SYMLINK, function(err, fd) {
2011-03-30 01:34:05 +02:00
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);
});
});
2011-03-30 01:34:05 +02:00
});
};
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;
2011-03-30 01:34:05 +02:00
};
}
2011-03-30 00:31:41 +02:00
fs.fchown = function(fd, uid, gid, callback) {
validateUint32(fd, 'fd');
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.fchown(fd, uid, gid, req);
2011-03-30 00:31:41 +02:00
};
fs.fchownSync = function(fd, uid, gid) {
validateUint32(fd, 'fd');
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
const ctx = {};
binding.fchown(fd, uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
2011-03-30 00:31:41 +02:00
};
fs.chown = function(path, uid, gid, callback) {
callback = makeCallback(callback);
path = getPathFromURL(path);
validatePath(path);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
const req = new FSReqWrap();
req.oncomplete = callback;
binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
};
fs.chownSync = function(path, uid, gid) {
path = getPathFromURL(path);
validatePath(path);
validateUint32(uid, 'uid');
validateUint32(gid, 'gid');
const ctx = { path };
binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
};
2010-10-29 12:38:13 +02:00
// exported for unit tests, not for public consumption
fs._toUnixTimestamp = toUnixTimestamp;
fs.utimes = function(path, atime, mtime, callback) {
callback = makeCallback(callback);
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
req);
2010-10-29 12:38:13 +02:00
};
fs.utimesSync = function(path, atime, mtime) {
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime), toUnixTimestamp(mtime),
undefined, ctx);
handleErrorFromBinding(ctx);
2010-10-29 12:38:13 +02:00
};
fs.futimes = function(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);
2010-10-29 12:38:13 +02:00
};
fs.futimesSync = function(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);
2010-10-29 12:38:13 +02:00
};
function writeAll(fd, isUserFd, buffer, offset, length, position, callback) {
// write(fd, buffer, offset, length, position, callback)
fs.write(fd, buffer, offset, length, position, function(writeErr, written) {
if (writeErr) {
if (isUserFd) {
callback(writeErr);
} else {
fs.close(fd, function close() {
callback(writeErr);
});
}
} else if (written === length) {
if (isUserFd) {
callback(null);
} else {
fs.close(fd, callback);
}
} else {
offset += written;
length -= written;
if (position !== null) {
position += written;
}
writeAll(fd, isUserFd, buffer, offset, length, position, callback);
}
});
}
fs.writeFile = function(path, data, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
const flag = options.flag || 'w';
if (isFd(path)) {
writeFd(path, true);
return;
}
fs.open(path, flag, options.mode, function(openErr, fd) {
if (openErr) {
callback(openErr);
} else {
writeFd(fd, false);
}
});
function writeFd(fd, isUserFd) {
var buffer = isUint8Array(data) ?
data : Buffer.from('' + data, options.encoding || 'utf8');
var position = /a/.test(flag) ? null : 0;
writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback);
}
};
fs.writeFileSync = function(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);
if (!isUint8Array(data)) {
data = Buffer.from('' + data, options.encoding || 'utf8');
}
var offset = 0;
var length = data.length;
var position = /a/.test(flag) ? null : 0;
try {
while (length > 0) {
var written = fs.writeSync(fd, data, offset, length, position);
offset += written;
length -= written;
if (position !== null) {
position += written;
}
}
} finally {
if (!isUserFd) fs.closeSync(fd);
}
};
fs.appendFile = function(path, data, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
// Don't make changes directly on options object
options = copyObject(options);
// force append behavior when using a supplied file descriptor
if (!options.flag || isFd(path))
options.flag = 'a';
fs.writeFile(path, data, options, callback);
};
fs.appendFileSync = function(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
// Don't make changes directly on options object
options = copyObject(options);
// force append behavior when using a supplied file descriptor
if (!options.flag || isFd(path))
options.flag = 'a';
fs.writeFileSync(path, data, options);
};
2011-09-23 01:18:08 +02:00
2011-09-23 09:45:41 +02:00
function FSWatcher() {
EventEmitter.call(this);
2011-09-23 01:18:08 +02:00
var self = this;
this._handle = new FSEvent();
this._handle.owner = this;
2011-09-23 01:18:08 +02:00
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);
2011-09-23 01:18:08 +02:00
} else {
self.emit('change', eventType, filename);
2011-09-23 01:18:08 +02:00
}
};
}
2011-09-23 09:45:41 +02:00
util.inherits(FSWatcher, EventEmitter);
2011-09-23 01:18:08 +02:00
// 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;
2011-09-23 01:18:08 +02:00
}
};
// This method is a noop if the watcher has not been started.
2011-09-23 09:45:41 +02:00
FSWatcher.prototype.close = function() {
lazyAssert()(this._handle instanceof FSEvent, 'handle must be a FSEvent');
if (!this._handle.initialized) {
return;
}
2011-09-23 01:18:08 +02:00
this._handle.close();
process.nextTick(emitCloseNT, this);
2011-09-23 01:18:08 +02:00
};
function emitCloseNT(self) {
self.emit('close');
}
fs.watch = function(filename, options, listener) {
if (typeof options === 'function') {
listener = options;
2011-09-23 01:18:08 +02:00
}
options = getOptions(options, {});
2011-09-23 01:18:08 +02:00
// Don't make changes directly on options object
options = copyObject(options);
if (options.persistent === undefined) options.persistent = true;
if (options.recursive === undefined) options.recursive = false;
2011-09-23 01:18:08 +02:00
const watcher = new FSWatcher();
watcher.start(filename,
options.persistent,
options.recursive,
options.encoding);
2011-09-23 01:18:08 +02:00
if (listener) {
watcher.addListener('change', listener);
}
2011-09-23 01:18:08 +02:00
return watcher;
};
2010-03-01 19:42:37 +01:00
// 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) {
filename = getPathFromURL(filename);
validatePath(filename);
filename = pathModule.resolve(filename);
2010-03-01 19:42:37 +01:00
var stat;
var 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.
interval: 5007,
persistent: true
};
if (options !== null && typeof options === 'object') {
options = util._extend(defaults, options);
2010-03-01 19:42:37 +01:00
} else {
listener = options;
options = defaults;
2010-03-01 19:42:37 +01:00
}
if (typeof listener !== 'function') {
throw new ERR_INVALID_ARG_TYPE('listener', 'Function', listener);
}
stat = statWatchers.get(filename);
if (stat === undefined) {
stat = new StatWatcher();
2010-03-01 19:42:37 +01:00
stat.start(filename, options.persistent, options.interval);
statWatchers.set(filename, stat);
2010-03-01 19:42:37 +01:00
}
2010-12-02 02:43:30 +01:00
stat.addListener('change', listener);
2010-03-01 19:42:37 +01:00
return stat;
};
fs.unwatchFile = function(filename, listener) {
filename = getPathFromURL(filename);
validatePath(filename);
filename = pathModule.resolve(filename);
var stat = statWatchers.get(filename);
if (stat === undefined) return;
if (typeof listener === 'function') {
stat.removeListener('change', listener);
} else {
stat.removeAllListeners('change');
}
if (stat.listenerCount('change') === 0) {
2010-03-01 19:42:37 +01:00
stat.stop();
statWatchers.delete(filename);
2010-03-01 19:42:37 +01:00
}
};
2010-09-10 03:49:28 +02:00
var splitRoot;
if (isWindows) {
// Regex to find the device root on Windows (e.g. 'c:\\'), including trailing
// slash.
const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
splitRoot = function splitRoot(str) {
return splitRootRe.exec(str)[0];
};
} else {
splitRoot = function splitRoot(str) {
for (var i = 0; i < str.length; ++i) {
if (str.charCodeAt(i) !== CHAR_FORWARD_SLASH)
return str.slice(0, i);
}
return str;
};
}
function encodeRealpathResult(result, options) {
if (!options || !options.encoding || options.encoding === 'utf8')
return result;
const asBuffer = Buffer.from(result);
if (options.encoding === 'buffer') {
return asBuffer;
} else {
return asBuffer.toString(options.encoding);
}
}
// Finds the next portion of a (partial) path, up to the next path delimiter
var nextPart;
if (isWindows) {
nextPart = function nextPart(p, i) {
for (; i < p.length; ++i) {
const ch = p.charCodeAt(i);
// Check for a separator character
if (ch === CHAR_BACKWARD_SLASH || ch === CHAR_FORWARD_SLASH)
return i;
}
return -1;
};
} else {
nextPart = function nextPart(p, i) { return p.indexOf('/', i); };
}
const emptyObj = Object.create(null);
fs.realpathSync = function realpathSync(p, options) {
if (!options)
options = emptyObj;
else
options = getOptions(options, emptyObj);
p = getPathFromURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const cache = options[realpathCacheKey];
const maybeCachedResult = cache && cache.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
const seenLinks = Object.create(null);
const knownHard = Object.create(null);
const original = p;
// current character position in p
var pos;
// the partial path so far, including a trailing slash if any
var current;
// the partial path without a trailing slash (except when pointing at a root)
var base;
// the partial path scanned in the previous round, with slash
var previous;
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
// walk down the path, swapping out linked path parts for their real
// values
// NB: p.length changes.
while (pos < p.length) {
// find the next part
var result = nextPart(p, pos);
previous = current;
if (result === -1) {
var last = p.slice(pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += p.slice(pos, result + 1);
base = previous + p.slice(pos, result);
pos = result + 1;
}
// continue if not a symlink, break if a pipe/socket
if (knownHard[base] || (cache && cache.get(base) === base)) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
break;
}
continue;
}
var resolvedLink;
var 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 ctx = { path: base };
const stats = binding.lstat(baseLong, undefined, ctx);
handleErrorFromBinding(ctx);
if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true;
if (cache) cache.set(base, base);
continue;
}
// 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;
if (!isWindows) {
var dev = stats[0].toString(32);
var ino = stats[7].toString(32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
const ctx = { path: base };
binding.stat(baseLong, undefined, ctx);
handleErrorFromBinding(ctx);
linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
handleErrorFromBinding(ctx);
}
resolvedLink = pathModule.resolve(previous, linkTarget);
if (cache) cache.set(base, resolvedLink);
if (!isWindows) seenLinks[id] = linkTarget;
}
// resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
// Skip over roots
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
}
if (cache) cache.set(original, p);
return encodeRealpathResult(p, options);
};
2010-09-10 03:49:28 +02:00
fs.realpathSync.native = function(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
const ctx = { path };
const result = binding.realpath(path, options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
fs.realpath = function realpath(p, options, callback) {
callback = maybeCallback(typeof options === 'function' ? options : callback);
if (!options)
options = emptyObj;
else
options = getOptions(options, emptyObj);
p = getPathFromURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const seenLinks = Object.create(null);
const knownHard = Object.create(null);
// current character position in p
var pos;
// the partial path so far, including a trailing slash if any
var current;
// the partial path without a trailing slash (except when pointing at a root)
var base;
// the partial path scanned in the previous round, with slash
var previous;
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
fs.lstat(base, function(err, stats) {
if (err) return callback(err);
knownHard[base] = true;
LOOP();
});
} else {
process.nextTick(LOOP);
}
// walk down the path, swapping out linked path parts for their real
// values
function LOOP() {
// stop if scanned past end of path
if (pos >= p.length) {
return callback(null, encodeRealpathResult(p, options));
}
// find the next part
var result = nextPart(p, pos);
previous = current;
if (result === -1) {
var last = p.slice(pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += p.slice(pos, result + 1);
base = previous + p.slice(pos, result);
pos = result + 1;
}
// continue if not a symlink, break if a pipe/socket
if (knownHard[base]) {
if (isFileType(statValues, S_IFIFO) ||
isFileType(statValues, S_IFSOCK)) {
return callback(null, encodeRealpathResult(p, options));
}
return process.nextTick(LOOP);
}
return fs.lstat(base, gotStat);
}
function gotStat(err, stats) {
if (err) return callback(err);
// if not a symlink, skip to the next path part
if (!stats.isSymbolicLink()) {
knownHard[base] = true;
return process.nextTick(LOOP);
}
// stat & read the link if not read before
// call gotTarget as soon as the link target is known
// 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);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
return gotTarget(null, seenLinks[id], base);
}
}
fs.stat(base, function(err) {
if (err) return callback(err);
fs.readlink(base, function(err, target) {
if (!isWindows) seenLinks[id] = target;
gotTarget(err, target);
});
});
}
function gotTarget(err, target, base) {
if (err) return callback(err);
var resolvedLink = pathModule.resolve(previous, target);
gotResolvedLink(resolvedLink);
}
function gotResolvedLink(resolvedLink) {
// resolve the link, then start over
p = pathModule.resolve(resolvedLink, p.slice(pos));
current = base = splitRoot(p);
pos = current.length;
// On windows, check that the root exists. On unix there is no need.
if (isWindows && !knownHard[base]) {
fs.lstat(base, function(err) {
if (err) return callback(err);
knownHard[base] = true;
LOOP();
});
} else {
process.nextTick(LOOP);
}
}
};
fs.realpath.native = function(path, options, callback) {
callback = makeCallback(callback || options);
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
const req = new FSReqWrap();
req.oncomplete = callback;
return binding.realpath(path, options.encoding, req);
};
fs.mkdtemp = function(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();
req.oncomplete = callback;
binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req);
};
fs.mkdtempSync = function(prefix, options) {
options = getOptions(options, {});
if (!prefix || typeof prefix !== 'string') {
throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
}
nullCheck(prefix, 'prefix');
const path = `${prefix}XXXXXX`;
const ctx = { path };
const result = binding.mkdtemp(path, options.encoding,
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) {
if (typeof flags === 'function') {
callback = flags;
flags = 0;
} else if (typeof callback !== 'function') {
throw new ERR_INVALID_CALLBACK();
}
src = getPathFromURL(src);
dest = getPathFromURL(dest);
validatePath(src, 'src');
validatePath(dest, 'dest');
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
flags = flags | 0;
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
binding.copyFile(src, dest, flags, req);
};
fs.copyFileSync = function(src, dest, flags) {
src = getPathFromURL(src);
dest = getPathFromURL(dest);
validatePath(src, 'src');
validatePath(dest, 'dest');
const ctx = { path: src, dest }; // non-prefixed
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
flags = flags | 0;
binding.copyFile(src, dest, flags, undefined, ctx);
handleErrorFromBinding(ctx);
};
var pool;
2010-12-02 02:43:30 +01:00
function allocNewPool(poolSize) {
pool = Buffer.allocUnsafe(poolSize);
pool.used = 0;
}
2010-03-02 23:12:52 +01:00
2010-04-08 19:22:55 +02:00
fs.createReadStream = function(path, options) {
return new ReadStream(path, options);
2010-03-03 12:39:41 +01:00
};
2012-10-05 02:44:48 +02:00
util.inherits(ReadStream, Readable);
fs.ReadStream = ReadStream;
2010-03-03 12:39:41 +01:00
2012-10-05 02:44:48 +02:00
function ReadStream(path, options) {
if (!(this instanceof ReadStream))
return new ReadStream(path, options);
2010-03-03 12:39:41 +01:00
2012-10-05 02:44:48 +02:00
// 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;
2012-10-05 02:44:48 +02:00
Readable.call(this, options);
2010-03-03 12:39:41 +01:00
// 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;
2012-10-05 02:44:48 +02:00
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')
2012-10-05 02:44:48 +02:00
this.open();
2010-03-03 12:39:41 +01:00
2012-10-05 02:44:48 +02:00
this.on('end', function() {
if (this.autoClose) {
this.destroy();
}
2012-10-05 02:44:48 +02:00
});
}
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();
}
2012-10-05 02:44:48 +02:00
self.emit('error', er);
2010-03-03 12:39:41 +01:00
return;
}
self.fd = fd;
self.emit('open', fd);
self.emit('ready');
2012-10-05 02:44:48 +02:00
// start the flow of data.
self.read();
});
};
ReadStream.prototype._read = function(n) {
if (typeof this.fd !== 'number') {
2012-10-05 02:44:48 +02:00
return this.once('open', function() {
this._read(n);
2012-10-05 02:44:48 +02:00
});
}
2012-10-05 02:44:48 +02:00
if (this.destroyed)
return;
2010-03-03 12:39:41 +01:00
if (!pool || pool.length - pool.used < kMinPoolSpace) {
// discard the old pool.
allocNewPool(this.readableHighWaterMark);
}
2012-10-05 02:44:48 +02:00
// 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;
2012-10-05 02:44:48 +02:00
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);
2012-10-05 02:44:48 +02:00
// already read everything we were supposed to read!
// treat as EOF.
if (toRead <= 0)
return this.push(null);
2010-03-03 12:39:41 +01:00
2012-10-05 02:44:48 +02:00
// the actual read.
fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => {
2012-10-05 02:44:48 +02:00
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;
2012-10-05 02:44:48 +02:00
}
closeFsStream(this, cb, err);
this.fd = null;
};
2010-03-03 12:39:41 +01:00
function closeFsStream(stream, cb, err) {
fs.close(stream.fd, (er) => {
er = er || err;
cb(er);
stream.closed = true;
if (!er)
stream.emit('close');
});
}
2010-03-03 12:39:41 +01:00
ReadStream.prototype.close = function(cb) {
this.destroy(null, cb);
};
2010-04-08 19:22:55 +02:00
fs.createWriteStream = function(path, options) {
return new WriteStream(path, options);
2010-03-02 23:12:52 +01:00
};
2012-10-05 02:44:48 +02:00
util.inherits(WriteStream, Writable);
fs.WriteStream = WriteStream;
function WriteStream(path, options) {
if (!(this instanceof WriteStream))
return new WriteStream(path, options);
options = copyObject(getOptions(options, {}));
2012-10-05 02:44:48 +02:00
// for backwards compat do not emit close on destroy.
options.emitClose = false;
2012-10-05 02:44:48 +02:00
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;
2012-10-05 02:44:48 +02:00
this.pos = undefined;
this.bytesWritten = 0;
this.closed = false;
2010-03-02 23:12:52 +01:00
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')
2012-10-05 02:44:48 +02:00
this.open();
}
2010-03-02 23:12:52 +01:00
fs.FileWriteStream = fs.WriteStream; // support the legacy name
2010-03-02 23:12:52 +01:00
WriteStream.prototype._final = function(callback) {
if (this.autoClose) {
this.destroy();
}
callback();
};
2012-10-05 02:44:48 +02:00
WriteStream.prototype.open = function() {
fs.open(this.path, this.flags, this.mode, (er, fd) => {
2012-10-05 02:44:48 +02:00
if (er) {
if (this.autoClose) {
this.destroy();
}
2012-10-05 02:44:48 +02:00
this.emit('error', er);
return;
}
2010-03-02 23:12:52 +01:00
2012-10-05 02:44:48 +02:00
this.fd = fd;
this.emit('open', fd);
this.emit('ready');
});
};
2010-03-02 23:12:52 +01:00
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) => {
2012-10-05 02:44:48 +02:00
if (er) {
if (this.autoClose) {
this.destroy();
}
2012-10-05 02:44:48 +02:00
return cb(er);
}
this.bytesWritten += bytes;
2012-10-05 02:44:48 +02:00
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;
binding.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);
var size = 0;
for (var i = 0; i < len; i++) {
var 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();
};
2011-01-04 20:39:12 +01:00
// There is no shutdown() for files.
WriteStream.prototype.destroySoon = WriteStream.prototype.end;