node/lib/fs.js
cjihrig 8e76397fab
fs: validate encoding to binding.writeString()
The binding layer performs some validation of the encoding and
data passed to WriteString(). This commit adds similar validation
to the JS layer for better error handling.

PR-URL: https://github.com/nodejs/node/pull/38183
Fixes: https://github.com/nodejs/node/issues/38168
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
2021-04-11 23:57:29 -04:00

2258 lines
59 KiB
JavaScript

// 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';
// When using FSReqCallback, make sure to create the object only *after* all
// parameter validation has happened, so that the objects are not kept in memory
// in case they are created but never used due to an exception.
const {
ArrayPrototypePush,
BigIntPrototypeToString,
MathMax,
Number,
ObjectCreate,
ObjectDefineProperties,
ObjectDefineProperty,
Promise,
ReflectApply,
RegExpPrototypeExec,
SafeMap,
String,
StringPrototypeCharCodeAt,
StringPrototypeIndexOf,
StringPrototypeSlice,
} = primordials;
const { fs: constants } = internalBinding('constants');
const {
S_IFIFO,
S_IFLNK,
S_IFMT,
S_IFREG,
S_IFSOCK,
F_OK,
R_OK,
W_OK,
X_OK,
O_WRONLY,
O_SYMLINK
} = constants;
const pathModule = require('path');
const { isArrayBufferView } = require('internal/util/types');
// We need to get the statValues from the binding at the callsite since
// it's re-initialized after deserialization.
const binding = internalBinding('fs');
const { Buffer } = require('buffer');
const {
aggregateTwoErrors,
codes: {
ERR_FS_FILE_TOO_LARGE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE,
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
},
AbortError,
uvErrmapGet,
uvException
} = require('internal/errors');
const { FSReqCallback } = binding;
const { toPathIfFileURL } = require('internal/url');
const internalUtil = require('internal/util');
const {
constants: {
kIoMaxLength,
kMaxUserId,
},
copyObject,
Dirent,
emitRecursiveRmdirWarning,
getDirents,
getOptions,
getValidatedFd,
getValidatedPath,
getValidMode,
handleErrorFromBinding,
nullCheck,
preprocessSymlinkDestination,
Stats,
getStatsFromBinding,
realpathCacheKey,
stringToFlags,
stringToSymlinkType,
toUnixTimestamp,
validateBufferArray,
validateOffsetLengthRead,
validateOffsetLengthWrite,
validatePath,
validatePosition,
validateRmOptions,
validateRmOptionsSync,
validateRmdirOptions,
validateStringAfterArrayBufferView,
warnOnNonPortableTemplate
} = require('internal/fs/utils');
const {
Dir,
opendir,
opendirSync
} = require('internal/fs/dir');
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
} = require('internal/constants');
const {
isUint32,
parseFileMode,
validateBoolean,
validateBuffer,
validateCallback,
validateEncoding,
validateFunction,
validateInteger,
} = require('internal/validators');
let truncateWarn = true;
let fs;
// Lazy loaded
let promises = null;
let watchers;
let ReadFileContext;
let ReadStream;
let WriteStream;
let rimraf;
let rimrafSync;
// 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';
const isOSX = process.platform === 'darwin';
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 maybeCallback(cb) {
validateCallback(cb);
return cb;
}
// 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) {
validateCallback(cb);
return (...args) => ReflectApply(cb, this, 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) {
validateCallback(cb);
return (err, stats) => {
if (err) return cb(err);
cb(err, getStatsFromBinding(stats));
};
}
const isFd = isUint32;
function isFileType(stats, fileType) {
// Use stats array directly to avoid creating an fs.Stats instance just for
// our internal use.
let mode = stats[1];
if (typeof mode === 'bigint')
mode = Number(mode);
return (mode & S_IFMT) === fileType;
}
function access(path, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = F_OK;
}
path = getValidatedPath(path);
mode = getValidMode(mode, 'access');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.access(pathModule.toNamespacedPath(path), mode, req);
}
function accessSync(path, mode) {
path = getValidatedPath(path);
mode = getValidMode(mode, 'access');
const ctx = { path };
binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
function exists(path, callback) {
maybeCallback(callback);
function suppressedCallback(err) {
callback(err ? false : true);
}
try {
fs.access(path, F_OK, suppressedCallback);
} catch {
return callback(false);
}
}
ObjectDefineProperty(exists, internalUtil.promisify.custom, {
value: (path) => {
return new Promise((resolve) => fs.exists(path, resolve));
}
});
// 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
function existsSync(path) {
try {
path = getValidatedPath(path);
} catch {
return false;
}
const ctx = { path };
const nPath = pathModule.toNamespacedPath(path);
binding.access(nPath, F_OK, undefined, ctx);
// In case of an invalid symlink, `binding.access()` on win32
// will **not** return an error and is therefore not enough.
// Double check with `binding.stat()`.
if (isWindows && ctx.errno === undefined) {
binding.stat(nPath, false, undefined, ctx);
}
return ctx.errno === undefined;
}
function readFileAfterOpen(err, fd) {
const context = this.context;
if (err) {
context.callback(err);
return;
}
context.fd = fd;
const req = new FSReqCallback();
req.oncomplete = readFileAfterStat;
req.context = context;
binding.fstat(fd, false, req);
}
function readFileAfterStat(err, stats) {
const context = this.context;
if (err)
return context.close(err);
const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0;
if (size > kIoMaxLength) {
err = new ERR_FS_FILE_TOO_LARGE(size);
return context.close(err);
}
try {
if (size === 0) {
context.buffers = [];
} else {
context.buffer = Buffer.allocUnsafeSlow(size);
}
} catch (err) {
return context.close(err);
}
context.read();
}
function checkAborted(signal, callback) {
if (signal?.aborted) {
callback(new AbortError());
return true;
}
return false;
}
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 (options.signal) {
context.signal = options.signal;
}
if (context.isUserFd) {
process.nextTick(function tick(context) {
ReflectApply(readFileAfterOpen, { context }, [null, path]);
}, context);
return;
}
if (checkAborted(options.signal, callback))
return;
const flagsNumber = stringToFlags(options.flag, 'options.flag');
path = getValidatedPath(path);
const req = new FSReqCallback();
req.context = context;
req.oncomplete = readFileAfterOpen;
binding.open(pathModule.toNamespacedPath(path),
flagsNumber,
0o666,
req);
}
function tryStatSync(fd, isUserFd) {
const ctx = {};
const stats = binding.fstat(fd, false, undefined, ctx);
if (ctx.errno !== undefined && !isUserFd) {
fs.closeSync(fd);
throw uvException(ctx);
}
return stats;
}
function tryCreateBuffer(size, fd, isUserFd) {
let threw = true;
let buffer;
try {
if (size > kIoMaxLength) {
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) {
let threw = true;
let bytesRead;
try {
bytesRead = fs.readSync(fd, buffer, pos, len);
threw = false;
} finally {
if (threw && !isUserFd) fs.closeSync(fd);
}
return bytesRead;
}
function readFileSync(path, options) {
options = getOptions(options, { flag: 'r' });
const isUserFd = isFd(path); // File descriptor ownership
const fd = isUserFd ? path : fs.openSync(path, options.flag, 0o666);
const stats = tryStatSync(fd, isUserFd);
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 = [];
} else {
buffer = tryCreateBuffer(size, fd, isUserFd);
}
let 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) {
ArrayPrototypePush(buffers, buffer.slice(0, bytesRead));
}
pos += bytesRead;
} while (bytesRead !== 0);
}
if (!isUserFd)
fs.closeSync(fd);
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;
}
function defaultCloseCallback(err) {
if (err != null) throw err;
}
function close(fd, callback = defaultCloseCallback) {
fd = getValidatedFd(fd);
if (callback !== defaultCloseCallback)
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.close(fd, req);
}
function closeSync(fd) {
fd = getValidatedFd(fd);
const ctx = {};
binding.close(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
function open(path, flags, mode, callback) {
path = getValidatedPath(path);
if (arguments.length < 3) {
callback = flags;
flags = 'r';
mode = 0o666;
} else if (typeof mode === 'function') {
callback = mode;
mode = 0o666;
} else {
mode = parseFileMode(mode, 'mode', 0o666);
}
const flagsNumber = stringToFlags(flags);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.open(pathModule.toNamespacedPath(path),
flagsNumber,
mode,
req);
}
function openSync(path, flags, mode) {
path = getValidatedPath(path);
const flagsNumber = stringToFlags(flags);
mode = parseFileMode(mode, 'mode', 0o666);
const ctx = { path };
const result = binding.open(pathModule.toNamespacedPath(path),
flagsNumber, mode,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
// usage:
// fs.read(fd, buffer, offset, length, position, callback);
// OR
// fs.read(fd, {}, callback)
function read(fd, buffer, offset, length, position, callback) {
fd = getValidatedFd(fd);
if (arguments.length <= 3) {
// Assume fs.read(fd, options, callback)
let options = {};
if (arguments.length < 3) {
// This is fs.read(fd, callback)
// buffer will be the callback
callback = buffer;
} else {
// This is fs.read(fd, {}, callback)
// buffer will be the options object
// offset is the callback
options = buffer;
callback = offset;
}
({
buffer = Buffer.alloc(16384),
offset = 0,
length = buffer.length,
position
} = options);
}
validateBuffer(buffer);
callback = maybeCallback(callback);
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset');
}
length |= 0;
if (length === 0) {
return process.nextTick(function tick() {
callback(null, 0, buffer);
});
}
if (buffer.byteLength === 0) {
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
'is empty and cannot be written');
}
validateOffsetLengthRead(offset, length, buffer.byteLength);
if (position == null)
position = -1;
validatePosition(position, 'position');
function wrapper(err, bytesRead) {
// Retain a reference to buffer so that it can't be GC'ed too soon.
callback(err, bytesRead || 0, buffer);
}
const req = new FSReqCallback();
req.oncomplete = wrapper;
binding.read(fd, buffer, offset, length, position, req);
}
ObjectDefineProperty(read, internalUtil.customPromisifyArgs,
{ value: ['bytesRead', 'buffer'], enumerable: false });
// usage:
// fs.readSync(fd, buffer, offset, length, position);
// OR
// fs.readSync(fd, buffer, {}) or fs.readSync(fd, buffer)
function readSync(fd, buffer, offset, length, position) {
fd = getValidatedFd(fd);
if (arguments.length <= 3) {
// Assume fs.read(fd, buffer, options)
const options = offset || {};
({ offset = 0, length = buffer.length, position } = options);
}
validateBuffer(buffer);
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset');
}
length |= 0;
if (length === 0) {
return 0;
}
if (buffer.byteLength === 0) {
throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
'is empty and cannot be written');
}
validateOffsetLengthRead(offset, length, buffer.byteLength);
if (position == null)
position = -1;
validatePosition(position, 'position');
const ctx = {};
const result = binding.read(fd, buffer, offset, length, position,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function readv(fd, buffers, position, callback) {
function wrapper(err, read) {
callback(err, read || 0, buffers);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
callback = maybeCallback(callback || position);
const req = new FSReqCallback();
req.oncomplete = wrapper;
if (typeof position !== 'number')
position = null;
return binding.readBuffers(fd, buffers, position, req);
}
ObjectDefineProperty(readv, internalUtil.customPromisifyArgs,
{ value: ['bytesRead', 'buffers'], enumerable: false });
function readvSync(fd, buffers, position) {
fd = getValidatedFd(fd);
validateBufferArray(buffers);
const ctx = {};
if (typeof position !== 'number')
position = null;
const result = binding.readBuffers(fd, buffers, position, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
// usage:
// fs.write(fd, buffer[, offset[, length[, position]]], callback);
// OR
// fs.write(fd, string[, position[, encoding]], 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);
}
fd = getValidatedFd(fd);
if (isArrayBufferView(buffer)) {
callback = maybeCallback(callback || position || length || offset);
if (offset == null || typeof offset === 'function') {
offset = 0;
} else {
validateInteger(offset, 'offset');
}
if (typeof length !== 'number')
length = buffer.length - offset;
if (typeof position !== 'number')
position = null;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
const req = new FSReqCallback();
req.oncomplete = wrapper;
return binding.writeBuffer(fd, buffer, offset, length, position, req);
}
validateStringAfterArrayBufferView(buffer, 'buffer');
if (typeof position !== 'function') {
if (typeof offset === 'function') {
position = offset;
offset = null;
} else {
position = length;
}
length = 'utf8';
}
const str = String(buffer);
validateEncoding(str, length);
callback = maybeCallback(position);
const req = new FSReqCallback();
req.oncomplete = wrapper;
return binding.writeString(fd, str, offset, length, req);
}
ObjectDefineProperty(write, internalUtil.customPromisifyArgs,
{ value: ['bytesWritten', 'buffer'], enumerable: false });
// Usage:
// fs.writeSync(fd, buffer[, offset[, length[, position]]]);
// OR
// fs.writeSync(fd, string[, position[, encoding]]);
function writeSync(fd, buffer, offset, length, position) {
fd = getValidatedFd(fd);
const ctx = {};
let result;
if (isArrayBufferView(buffer)) {
if (position === undefined)
position = null;
if (offset == null) {
offset = 0;
} else {
validateInteger(offset, 'offset');
}
if (typeof length !== 'number')
length = buffer.byteLength - offset;
validateOffsetLengthWrite(offset, length, buffer.byteLength);
result = binding.writeBuffer(fd, buffer, offset, length, position,
undefined, ctx);
} else {
validateStringAfterArrayBufferView(buffer, 'buffer');
validateEncoding(buffer, length);
if (offset === undefined)
offset = null;
result = binding.writeString(fd, buffer, offset, length,
undefined, ctx);
}
handleErrorFromBinding(ctx);
return result;
}
// usage:
// fs.writev(fd, buffers[, position], callback);
function writev(fd, buffers, position, callback) {
function wrapper(err, written) {
callback(err, written || 0, buffers);
}
fd = getValidatedFd(fd);
validateBufferArray(buffers);
callback = maybeCallback(callback || position);
const req = new FSReqCallback();
req.oncomplete = wrapper;
if (typeof position !== 'number')
position = null;
return binding.writeBuffers(fd, buffers, position, req);
}
ObjectDefineProperty(writev, internalUtil.customPromisifyArgs, {
value: ['bytesWritten', 'buffer'],
enumerable: false
});
function writevSync(fd, buffers, position) {
fd = getValidatedFd(fd);
validateBufferArray(buffers);
const ctx = {};
if (typeof position !== 'number')
position = null;
const result = binding.writeBuffers(fd, buffers, position, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function rename(oldPath, newPath, callback) {
callback = makeCallback(callback);
oldPath = getValidatedPath(oldPath, 'oldPath');
newPath = getValidatedPath(newPath, 'newPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath),
req);
}
function renameSync(oldPath, newPath) {
oldPath = getValidatedPath(oldPath, 'oldPath');
newPath = getValidatedPath(newPath, 'newPath');
const ctx = { path: oldPath, dest: newPath };
binding.rename(pathModule.toNamespacedPath(oldPath),
pathModule.toNamespacedPath(newPath), undefined, ctx);
handleErrorFromBinding(ctx);
}
function truncate(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;
}
validateInteger(len, 'len');
len = MathMax(0, len);
callback = maybeCallback(callback);
fs.open(path, 'r+', (er, fd) => {
if (er) return callback(er);
const req = new FSReqCallback();
req.oncomplete = function oncomplete(er) {
fs.close(fd, (er2) => {
callback(aggregateTwoErrors(er2, er));
});
};
binding.ftruncate(fd, len, req);
});
}
function truncateSync(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.
const fd = fs.openSync(path, 'r+');
let ret;
try {
ret = fs.ftruncateSync(fd, len);
} finally {
fs.closeSync(fd);
}
return ret;
}
function ftruncate(fd, len = 0, callback) {
if (typeof len === 'function') {
callback = len;
len = 0;
}
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.ftruncate(fd, len, req);
}
function ftruncateSync(fd, len = 0) {
fd = getValidatedFd(fd);
validateInteger(len, 'len');
len = MathMax(0, len);
const ctx = {};
binding.ftruncate(fd, len, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lazyLoadRimraf() {
if (rimraf === undefined)
({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
}
function rmdir(path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
callback = makeCallback(callback);
path = pathModule.toNamespacedPath(getValidatedPath(path));
if (options?.recursive) {
emitRecursiveRmdirWarning();
validateRmOptions(
path,
{ ...options, force: false },
true,
(err, options) => {
if (err === false) {
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.rmdir(path, req);
}
if (err) {
return callback(err);
}
lazyLoadRimraf();
rimraf(path, options, callback);
});
} else {
validateRmdirOptions(options);
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.rmdir(path, req);
}
}
function rmdirSync(path, options) {
path = getValidatedPath(path);
if (options?.recursive) {
emitRecursiveRmdirWarning();
options = validateRmOptionsSync(path, { ...options, force: false }, true);
if (options !== false) {
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
}
} else {
validateRmdirOptions(options);
}
const ctx = { path };
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
return handleErrorFromBinding(ctx);
}
function rm(path, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
validateRmOptions(path, options, false, (err, options) => {
if (err) {
return callback(err);
}
lazyLoadRimraf();
return rimraf(pathModule.toNamespacedPath(path), options, callback);
});
}
function rmSync(path, options) {
options = validateRmOptionsSync(path, options, false);
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
}
function fdatasync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fdatasync(fd, req);
}
function fdatasyncSync(fd) {
fd = getValidatedFd(fd);
const ctx = {};
binding.fdatasync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
function fsync(fd, callback) {
fd = getValidatedFd(fd);
const req = new FSReqCallback();
req.oncomplete = makeCallback(callback);
binding.fsync(fd, req);
}
function fsyncSync(fd) {
fd = getValidatedFd(fd);
const ctx = {};
binding.fsync(fd, undefined, ctx);
handleErrorFromBinding(ctx);
}
function mkdir(path, options, callback) {
let mode = 0o777;
let recursive = false;
if (typeof options === 'function') {
callback = options;
} else if (typeof options === 'number' || typeof options === 'string') {
mode = options;
} else if (options) {
if (options.recursive !== undefined)
recursive = options.recursive;
if (options.mode !== undefined)
mode = options.mode;
}
callback = makeCallback(callback);
path = getValidatedPath(path);
validateBoolean(recursive, 'options.recursive');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.mkdir(pathModule.toNamespacedPath(path),
parseFileMode(mode, 'mode'), recursive, req);
}
function mkdirSync(path, options) {
let mode = 0o777;
let recursive = false;
if (typeof options === 'number' || typeof options === 'string') {
mode = options;
} else if (options) {
if (options.recursive !== undefined)
recursive = options.recursive;
if (options.mode !== undefined)
mode = options.mode;
}
path = getValidatedPath(path);
validateBoolean(recursive, 'options.recursive');
const ctx = { path };
const result = binding.mkdir(pathModule.toNamespacedPath(path),
parseFileMode(mode, 'mode'), recursive,
undefined, ctx);
handleErrorFromBinding(ctx);
if (recursive) {
return result;
}
}
function readdir(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options, {});
path = getValidatedPath(path);
const req = new FSReqCallback();
if (!options.withFileTypes) {
req.oncomplete = callback;
} else {
req.oncomplete = (err, result) => {
if (err) {
callback(err);
return;
}
getDirents(path, result, callback);
};
}
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
!!options.withFileTypes, req);
}
function readdirSync(path, options) {
options = getOptions(options, {});
path = getValidatedPath(path);
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, !!options.withFileTypes,
undefined, ctx);
handleErrorFromBinding(ctx);
return options.withFileTypes ? getDirents(path, result) : result;
}
function fstat(fd, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
fd = getValidatedFd(fd);
callback = makeStatsCallback(callback);
const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.fstat(fd, options.bigint, req);
}
function lstat(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
callback = makeStatsCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.lstat(pathModule.toNamespacedPath(path), options.bigint, req);
}
function stat(path, options = { bigint: false }, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
callback = makeStatsCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback(options.bigint);
req.oncomplete = callback;
binding.stat(pathModule.toNamespacedPath(path), options.bigint, req);
}
function hasNoEntryError(ctx) {
if (ctx.errno) {
const uvErr = uvErrmapGet(ctx.errno);
return uvErr?.[0] === 'ENOENT';
}
if (ctx.error) {
return ctx.error.code === 'ENOENT';
}
return false;
}
function fstatSync(fd, options = { bigint: false, throwIfNoEntry: true }) {
fd = getValidatedFd(fd);
const ctx = { fd };
const stats = binding.fstat(fd, options.bigint, undefined, ctx);
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
function lstatSync(path, options = { bigint: false, throwIfNoEntry: true }) {
path = getValidatedPath(path);
const ctx = { path };
const stats = binding.lstat(pathModule.toNamespacedPath(path),
options.bigint, undefined, ctx);
if (options.throwIfNoEntry === false && hasNoEntryError(ctx)) {
return undefined;
}
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
function statSync(path, options = { bigint: false, throwIfNoEntry: true }) {
path = getValidatedPath(path);
const ctx = { path };
const stats = binding.stat(pathModule.toNamespacedPath(path),
options.bigint, undefined, ctx);
if (options.throwIfNoEntry === false && hasNoEntryError(ctx)) {
return undefined;
}
handleErrorFromBinding(ctx);
return getStatsFromBinding(stats);
}
function readlink(path, options, callback) {
callback = makeCallback(typeof options === 'function' ? options : callback);
options = getOptions(options, {});
path = getValidatedPath(path, 'oldPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req);
}
function readlinkSync(path, options) {
options = getOptions(options, {});
path = getValidatedPath(path, 'oldPath');
const ctx = { path };
const result = binding.readlink(pathModule.toNamespacedPath(path),
options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function symlink(target, path, type_, callback_) {
const type = (typeof type_ === 'string' ? type_ : null);
const callback = makeCallback(arguments[arguments.length - 1]);
target = getValidatedPath(target, 'target');
path = getValidatedPath(path);
if (isWindows && type === null) {
let absoluteTarget;
try {
// Symlinks targets can be relative to the newly created path.
// Calculate absolute file name of the symlink target, and check
// if it is a directory. Ignore resolve error to keep symlink
// errors consistent between platforms if invalid path is
// provided.
absoluteTarget = pathModule.resolve(path, '..', target);
} catch { }
if (absoluteTarget !== undefined) {
stat(absoluteTarget, (err, stat) => {
const resolvedType = !err && stat.isDirectory() ? 'dir' : 'file';
const resolvedFlags = stringToSymlinkType(resolvedType);
const destination = preprocessSymlinkDestination(target,
resolvedType,
path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.symlink(destination,
pathModule.toNamespacedPath(path), resolvedFlags, req);
});
return;
}
}
const destination = preprocessSymlinkDestination(target, type, path);
const flags = stringToSymlinkType(type);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.symlink(destination, pathModule.toNamespacedPath(path), flags, req);
}
function symlinkSync(target, path, type) {
type = (typeof type === 'string' ? type : null);
if (isWindows && type === null) {
const absoluteTarget = pathModule.resolve(`${path}`, '..', `${target}`);
if (statSync(absoluteTarget, { throwIfNoEntry: false })?.isDirectory()) {
type = 'dir';
}
}
target = getValidatedPath(target, 'target');
path = getValidatedPath(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);
}
function link(existingPath, newPath, callback) {
callback = makeCallback(callback);
existingPath = getValidatedPath(existingPath, 'existingPath');
newPath = getValidatedPath(newPath, 'newPath');
const req = new FSReqCallback();
req.oncomplete = callback;
binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
req);
}
function linkSync(existingPath, newPath) {
existingPath = getValidatedPath(existingPath, 'existingPath');
newPath = getValidatedPath(newPath, 'newPath');
const ctx = { path: existingPath, dest: newPath };
const result = binding.link(pathModule.toNamespacedPath(existingPath),
pathModule.toNamespacedPath(newPath),
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function unlink(path, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.unlink(pathModule.toNamespacedPath(path), req);
}
function unlinkSync(path) {
path = getValidatedPath(path);
const ctx = { path };
binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx);
handleErrorFromBinding(ctx);
}
function fchmod(fd, mode, callback) {
fd = getValidatedFd(fd);
mode = parseFileMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.fchmod(fd, mode, req);
}
function fchmodSync(fd, mode) {
fd = getValidatedFd(fd);
mode = parseFileMode(mode, 'mode');
const ctx = {};
binding.fchmod(fd, mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lchmod(path, mode, callback) {
callback = maybeCallback(callback);
mode = parseFileMode(mode, 'mode');
fs.open(path, O_WRONLY | O_SYMLINK, (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, (err) => {
fs.close(fd, (err2) => {
callback(aggregateTwoErrors(err2, err));
});
});
});
}
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;
}
function chmod(path, mode, callback) {
path = getValidatedPath(path);
mode = parseFileMode(mode, 'mode');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.chmod(pathModule.toNamespacedPath(path), mode, req);
}
function chmodSync(path, mode) {
path = getValidatedPath(path);
mode = parseFileMode(mode, 'mode');
const ctx = { path };
binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lchown(path, uid, gid, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.lchown(pathModule.toNamespacedPath(path), uid, gid, req);
}
function lchownSync(path, uid, gid) {
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = { path };
binding.lchown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
function fchown(fd, uid, gid, callback) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.fchown(fd, uid, gid, req);
}
function fchownSync(fd, uid, gid) {
fd = getValidatedFd(fd);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = {};
binding.fchown(fd, uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
function chown(path, uid, gid, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
}
function chownSync(path, uid, gid) {
path = getValidatedPath(path);
validateInteger(uid, 'uid', -1, kMaxUserId);
validateInteger(gid, 'gid', -1, kMaxUserId);
const ctx = { path };
binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
handleErrorFromBinding(ctx);
}
function utimes(path, atime, mtime, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
req);
}
function utimesSync(path, atime, mtime) {
path = getValidatedPath(path);
const ctx = { path };
binding.utimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime), toUnixTimestamp(mtime),
undefined, ctx);
handleErrorFromBinding(ctx);
}
function futimes(fd, atime, mtime, callback) {
fd = getValidatedFd(fd);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.futimes(fd, atime, mtime, req);
}
function futimesSync(fd, atime, mtime) {
fd = getValidatedFd(fd);
atime = toUnixTimestamp(atime, 'atime');
mtime = toUnixTimestamp(mtime, 'mtime');
const ctx = {};
binding.futimes(fd, atime, mtime, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lutimes(path, atime, mtime, callback) {
callback = makeCallback(callback);
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.lutimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
req);
}
function lutimesSync(path, atime, mtime) {
path = getValidatedPath(path);
const ctx = { path };
binding.lutimes(pathModule.toNamespacedPath(path),
toUnixTimestamp(atime),
toUnixTimestamp(mtime),
undefined, ctx);
handleErrorFromBinding(ctx);
}
function writeAll(fd, isUserFd, buffer, offset, length, signal, callback) {
if (signal?.aborted) {
const abortError = new AbortError();
if (isUserFd) {
callback(abortError);
} else {
fs.close(fd, (err) => {
callback(aggregateTwoErrors(err, abortError));
});
}
return;
}
// write(fd, buffer, offset, length, position, callback)
fs.write(fd, buffer, offset, length, null, (writeErr, written) => {
if (writeErr) {
if (isUserFd) {
callback(writeErr);
} else {
fs.close(fd, (err) => {
callback(aggregateTwoErrors(err, writeErr));
});
}
} else if (written === length) {
if (isUserFd) {
callback(null);
} else {
fs.close(fd, callback);
}
} else {
offset += written;
length -= written;
writeAll(fd, isUserFd, buffer, offset, length, signal, 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';
if (!isArrayBufferView(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(String(data), options.encoding || 'utf8');
}
if (isFd(path)) {
const isUserFd = true;
const signal = options.signal;
writeAll(path, isUserFd, data, 0, data.byteLength, signal, callback);
return;
}
if (checkAborted(options.signal, callback))
return;
fs.open(path, flag, options.mode, (openErr, fd) => {
if (openErr) {
callback(openErr);
} else {
const isUserFd = false;
const signal = options.signal;
writeAll(fd, isUserFd, data, 0, data.byteLength, signal, callback);
}
});
}
function writeFileSync(path, data, options) {
options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
if (!isArrayBufferView(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(String(data), options.encoding || 'utf8');
}
const flag = options.flag || 'w';
const isUserFd = isFd(path); // File descriptor ownership
const fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
let offset = 0;
let length = data.byteLength;
try {
while (length > 0) {
const written = fs.writeSync(fd, data, offset, length);
offset += written;
length -= written;
}
} finally {
if (!isUserFd) fs.closeSync(fd);
}
}
function appendFile(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);
}
function appendFileSync(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);
}
function watch(filename, options, listener) {
if (typeof options === 'function') {
listener = options;
}
options = getOptions(options, {});
// 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;
if (options.recursive && !(isOSX || isWindows))
throw new ERR_FEATURE_UNAVAILABLE_ON_PLATFORM('watch recursively');
if (!watchers)
watchers = require('internal/fs/watchers');
const watcher = new watchers.FSWatcher();
watcher[watchers.kFSWatchStart](filename,
options.persistent,
options.recursive,
options.encoding);
if (listener) {
watcher.addListener('change', listener);
}
if (options.signal) {
if (options.signal.aborted) {
process.nextTick(() => watcher.close());
} else {
const listener = () => watcher.close();
options.signal.addEventListener('abort', listener);
watcher.once('close', () => {
options.signal.removeEventListener('abort', listener);
});
}
}
return watcher;
}
const statWatchers = new SafeMap();
function watchFile(filename, options, listener) {
filename = getValidatedPath(filename);
filename = pathModule.resolve(filename);
let stat;
if (options === null || typeof options !== 'object') {
listener = options;
options = null;
}
options = {
// 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,
...options
};
validateFunction(listener, 'listener');
stat = statWatchers.get(filename);
if (stat === undefined) {
if (!watchers)
watchers = require('internal/fs/watchers');
stat = new watchers.StatWatcher(options.bigint);
stat[watchers.kFSStatWatcherStart](filename,
options.persistent, options.interval);
statWatchers.set(filename, stat);
} else {
stat[watchers.kFSStatWatcherAddOrCleanRef]('add');
}
stat.addListener('change', listener);
return stat;
}
function unwatchFile(filename, listener) {
filename = getValidatedPath(filename);
filename = pathModule.resolve(filename);
const stat = statWatchers.get(filename);
if (stat === undefined) return;
if (typeof listener === 'function') {
const beforeListenerCount = stat.listenerCount('change');
stat.removeListener('change', listener);
if (stat.listenerCount('change') < beforeListenerCount)
stat[watchers.kFSStatWatcherAddOrCleanRef]('clean');
} else {
stat.removeAllListeners('change');
stat[watchers.kFSStatWatcherAddOrCleanRef]('cleanAll');
}
if (stat.listenerCount('change') === 0) {
stat.stop();
statWatchers.delete(filename);
}
}
let 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 RegExpPrototypeExec(splitRootRe, str)[0];
};
} else {
splitRoot = function splitRoot(str) {
for (let i = 0; i < str.length; ++i) {
if (StringPrototypeCharCodeAt(str, i) !== CHAR_FORWARD_SLASH)
return StringPrototypeSlice(str, 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;
}
return asBuffer.toString(options.encoding);
}
// Finds the next portion of a (partial) path, up to the next path delimiter
let nextPart;
if (isWindows) {
nextPart = function nextPart(p, i) {
for (; i < p.length; ++i) {
const ch = StringPrototypeCharCodeAt(p, 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 StringPrototypeIndexOf(p, '/', i);
};
}
const emptyObj = ObjectCreate(null);
function realpathSync(p, options) {
options = getOptions(options, emptyObj);
p = toPathIfFileURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const cache = options[realpathCacheKey];
const maybeCachedResult = cache?.get(p);
if (maybeCachedResult) {
return maybeCachedResult;
}
const seenLinks = ObjectCreate(null);
const knownHard = ObjectCreate(null);
const original = p;
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let 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) {
const ctx = { path: base };
binding.lstat(pathModule.toNamespacedPath(base), false, 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
const result = nextPart(p, pos);
previous = current;
if (result === -1) {
const last = StringPrototypeSlice(p, pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += StringPrototypeSlice(p, pos, result + 1);
base = previous + StringPrototypeSlice(p, pos, result);
pos = result + 1;
}
// Continue if not a symlink, break if a pipe/socket
if (knownHard[base] || cache?.get(base) === base) {
if (isFileType(binding.statValues, S_IFIFO) ||
isFileType(binding.statValues, S_IFSOCK)) {
break;
}
continue;
}
let resolvedLink;
const maybeCachedResolved = cache?.get(base);
if (maybeCachedResolved) {
resolvedLink = maybeCachedResolved;
} else {
// Use stats array directly to avoid creating an fs.Stats instance just
// for our internal use.
const baseLong = pathModule.toNamespacedPath(base);
const ctx = { path: base };
const stats = binding.lstat(baseLong, true, undefined, ctx);
handleErrorFromBinding(ctx);
if (!isFileType(stats, S_IFLNK)) {
knownHard[base] = true;
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.
let linkTarget = null;
let id;
if (!isWindows) {
const dev = BigIntPrototypeToString(stats[0], 32);
const ino = BigIntPrototypeToString(stats[7], 32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
linkTarget = seenLinks[id];
}
}
if (linkTarget === null) {
const ctx = { path: base };
binding.stat(baseLong, false, 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, StringPrototypeSlice(p, 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), false, undefined, ctx);
handleErrorFromBinding(ctx);
knownHard[base] = true;
}
}
cache?.set(original, p);
return encodeRealpathResult(p, options);
}
realpathSync.native = (path, options) => {
options = getOptions(options, {});
path = getValidatedPath(path);
const ctx = { path };
const result = binding.realpath(path, options.encoding, undefined, ctx);
handleErrorFromBinding(ctx);
return result;
};
function realpath(p, options, callback) {
callback = typeof options === 'function' ? options : maybeCallback(callback);
options = getOptions(options, {});
p = toPathIfFileURL(p);
if (typeof p !== 'string') {
p += '';
}
validatePath(p);
p = pathModule.resolve(p);
const seenLinks = ObjectCreate(null);
const knownHard = ObjectCreate(null);
// Current character position in p
let pos;
// The partial path so far, including a trailing slash if any
let current;
// The partial path without a trailing slash (except when pointing at a root)
let base;
// The partial path scanned in the previous round, with slash
let 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, (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
const result = nextPart(p, pos);
previous = current;
if (result === -1) {
const last = StringPrototypeSlice(p, pos);
current += last;
base = previous + last;
pos = p.length;
} else {
current += StringPrototypeSlice(p, pos, result + 1);
base = previous + StringPrototypeSlice(p, pos, result);
pos = result + 1;
}
// Continue if not a symlink, break if a pipe/socket
if (knownHard[base]) {
if (isFileType(binding.statValues, S_IFIFO) ||
isFileType(binding.statValues, S_IFSOCK)) {
return callback(null, encodeRealpathResult(p, options));
}
return process.nextTick(LOOP);
}
return fs.lstat(base, { bigint: true }, 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) {
const dev = BigIntPrototypeToString(stats.dev, 32);
const ino = BigIntPrototypeToString(stats.ino, 32);
id = `${dev}:${ino}`;
if (seenLinks[id]) {
return gotTarget(null, seenLinks[id]);
}
}
fs.stat(base, (err) => {
if (err) return callback(err);
fs.readlink(base, (err, target) => {
if (!isWindows) seenLinks[id] = target;
gotTarget(err, target);
});
});
}
function gotTarget(err, target) {
if (err) return callback(err);
gotResolvedLink(pathModule.resolve(previous, target));
}
function gotResolvedLink(resolvedLink) {
// Resolve the link, then start over
p = pathModule.resolve(resolvedLink, StringPrototypeSlice(p, 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, (err) => {
if (err) return callback(err);
knownHard[base] = true;
LOOP();
});
} else {
process.nextTick(LOOP);
}
}
}
realpath.native = (path, options, callback) => {
callback = makeCallback(callback || options);
options = getOptions(options, {});
path = getValidatedPath(path);
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.realpath(path, options.encoding, req);
};
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');
warnOnNonPortableTemplate(prefix);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req);
}
function mkdtempSync(prefix, options) {
options = getOptions(options, {});
if (!prefix || typeof prefix !== 'string') {
throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
}
nullCheck(prefix, 'prefix');
warnOnNonPortableTemplate(prefix);
const path = `${prefix}XXXXXX`;
const ctx = { path };
const result = binding.mkdtemp(path, options.encoding,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
}
function copyFile(src, dest, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = 0;
}
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
mode = getValidMode(mode, 'copyFile');
callback = makeCallback(callback);
const req = new FSReqCallback();
req.oncomplete = callback;
binding.copyFile(src, dest, mode, req);
}
function copyFileSync(src, dest, mode) {
src = getValidatedPath(src, 'src');
dest = getValidatedPath(dest, 'dest');
const ctx = { path: src, dest }; // non-prefixed
src = pathModule._makeLong(src);
dest = pathModule._makeLong(dest);
mode = getValidMode(mode, 'copyFile');
binding.copyFile(src, dest, mode, undefined, ctx);
handleErrorFromBinding(ctx);
}
function lazyLoadStreams() {
if (!ReadStream) {
({ ReadStream, WriteStream } = require('internal/fs/streams'));
FileReadStream = ReadStream;
FileWriteStream = WriteStream;
}
}
function createReadStream(path, options) {
lazyLoadStreams();
return new ReadStream(path, options);
}
function createWriteStream(path, options) {
lazyLoadStreams();
return new WriteStream(path, options);
}
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,
lchownSync,
lchmod: constants.O_SYMLINK !== undefined ? lchmod : undefined,
lchmodSync: constants.O_SYMLINK !== undefined ? lchmodSync : undefined,
link,
linkSync,
lstat,
lstatSync,
lutimes,
lutimesSync,
mkdir,
mkdirSync,
mkdtemp,
mkdtempSync,
open,
openSync,
opendir,
opendirSync,
readdir,
readdirSync,
read,
readSync,
readv,
readvSync,
readFile,
readFileSync,
readlink,
readlinkSync,
realpath,
realpathSync,
rename,
renameSync,
rm,
rmSync,
rmdir,
rmdirSync,
stat,
statSync,
symlink,
symlinkSync,
truncate,
truncateSync,
unwatchFile,
unlink,
unlinkSync,
utimes,
utimesSync,
watch,
watchFile,
writeFile,
writeFileSync,
write,
writeSync,
writev,
writevSync,
Dir,
Dirent,
Stats,
get ReadStream() {
lazyLoadStreams();
return ReadStream;
},
set ReadStream(val) {
ReadStream = val;
},
get WriteStream() {
lazyLoadStreams();
return WriteStream;
},
set WriteStream(val) {
WriteStream = val;
},
// 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;
},
set FileReadStream(val) {
FileReadStream = val;
},
get FileWriteStream() {
lazyLoadStreams();
return FileWriteStream;
},
set FileWriteStream(val) {
FileWriteStream = val;
},
// For tests
_toUnixTimestamp: toUnixTimestamp
};
ObjectDefineProperties(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: true,
get() {
if (promises === null)
promises = require('internal/fs/promises').exports;
return promises;
}
}
});