| 'use strict'; |
| |
| const { |
| ArrayIsArray, |
| BigInt, |
| DateNow, |
| Error, |
| Number, |
| NumberIsFinite, |
| MathMin, |
| ObjectSetPrototypeOf, |
| ReflectOwnKeys, |
| Symbol, |
| } = primordials; |
| |
| const { Buffer } = require('buffer'); |
| const { |
| codes: { |
| ERR_FS_INVALID_SYMLINK_TYPE, |
| ERR_INVALID_ARG_TYPE, |
| ERR_INVALID_ARG_VALUE, |
| ERR_INVALID_OPT_VALUE, |
| ERR_INVALID_OPT_VALUE_ENCODING, |
| ERR_OUT_OF_RANGE |
| }, |
| hideStackFrames, |
| uvException |
| } = require('internal/errors'); |
| const { |
| isArrayBufferView, |
| isUint8Array, |
| isDate, |
| isBigUint64Array |
| } = require('internal/util/types'); |
| const { once } = require('internal/util'); |
| const { toPathIfFileURL } = require('internal/url'); |
| const { |
| validateInt32, |
| validateUint32 |
| } = require('internal/validators'); |
| const pathModule = require('path'); |
| const kType = Symbol('type'); |
| const kStats = Symbol('stats'); |
| const assert = require('internal/assert'); |
| |
| const { |
| F_OK = 0, |
| W_OK = 0, |
| R_OK = 0, |
| X_OK = 0, |
| COPYFILE_EXCL, |
| COPYFILE_FICLONE, |
| COPYFILE_FICLONE_FORCE, |
| O_APPEND, |
| O_CREAT, |
| O_EXCL, |
| O_RDONLY, |
| O_RDWR, |
| O_SYNC, |
| O_TRUNC, |
| O_WRONLY, |
| S_IFBLK, |
| S_IFCHR, |
| S_IFDIR, |
| S_IFIFO, |
| S_IFLNK, |
| S_IFMT, |
| S_IFREG, |
| S_IFSOCK, |
| UV_FS_SYMLINK_DIR, |
| UV_FS_SYMLINK_JUNCTION, |
| UV_DIRENT_UNKNOWN, |
| UV_DIRENT_FILE, |
| UV_DIRENT_DIR, |
| UV_DIRENT_LINK, |
| UV_DIRENT_FIFO, |
| UV_DIRENT_SOCKET, |
| UV_DIRENT_CHAR, |
| UV_DIRENT_BLOCK |
| } = internalBinding('constants').fs; |
| |
| // The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be |
| // available on specific systems. They can be used in combination as well |
| // (F_OK | R_OK | W_OK | X_OK). |
| const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK); |
| const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK; |
| |
| const kDefaultCopyMode = 0; |
| // The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or |
| // COPYFILE_FICLONE_FORCE. They can be used in combination as well |
| // (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE). |
| const kMinimumCopyMode = MathMin( |
| kDefaultCopyMode, |
| COPYFILE_EXCL, |
| COPYFILE_FICLONE, |
| COPYFILE_FICLONE_FORCE |
| ); |
| const kMaximumCopyMode = COPYFILE_EXCL | |
| COPYFILE_FICLONE | |
| COPYFILE_FICLONE_FORCE; |
| |
| const isWindows = process.platform === 'win32'; |
| |
| let fs; |
| function lazyLoadFs() { |
| if (!fs) { |
| fs = require('fs'); |
| } |
| return fs; |
| } |
| |
| function assertEncoding(encoding) { |
| if (encoding && !Buffer.isEncoding(encoding)) { |
| throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding); |
| } |
| } |
| |
| class Dirent { |
| constructor(name, type) { |
| this.name = name; |
| this[kType] = type; |
| } |
| |
| isDirectory() { |
| return this[kType] === UV_DIRENT_DIR; |
| } |
| |
| isFile() { |
| return this[kType] === UV_DIRENT_FILE; |
| } |
| |
| isBlockDevice() { |
| return this[kType] === UV_DIRENT_BLOCK; |
| } |
| |
| isCharacterDevice() { |
| return this[kType] === UV_DIRENT_CHAR; |
| } |
| |
| isSymbolicLink() { |
| return this[kType] === UV_DIRENT_LINK; |
| } |
| |
| isFIFO() { |
| return this[kType] === UV_DIRENT_FIFO; |
| } |
| |
| isSocket() { |
| return this[kType] === UV_DIRENT_SOCKET; |
| } |
| } |
| |
| class DirentFromStats extends Dirent { |
| constructor(name, stats) { |
| super(name, null); |
| this[kStats] = stats; |
| } |
| } |
| |
| for (const name of ReflectOwnKeys(Dirent.prototype)) { |
| if (name === 'constructor') { |
| continue; |
| } |
| DirentFromStats.prototype[name] = function() { |
| return this[kStats][name](); |
| }; |
| } |
| |
| function copyObject(source) { |
| const target = {}; |
| for (const key in source) |
| target[key] = source[key]; |
| return target; |
| } |
| |
| function getDirents(path, [names, types], callback) { |
| let i; |
| if (typeof callback === 'function') { |
| const len = names.length; |
| let toFinish = 0; |
| callback = once(callback); |
| for (i = 0; i < len; i++) { |
| const type = types[i]; |
| if (type === UV_DIRENT_UNKNOWN) { |
| const name = names[i]; |
| const idx = i; |
| toFinish++; |
| lazyLoadFs().lstat(pathModule.join(path, name), (err, stats) => { |
| if (err) { |
| callback(err); |
| return; |
| } |
| names[idx] = new DirentFromStats(name, stats); |
| if (--toFinish === 0) { |
| callback(null, names); |
| } |
| }); |
| } else { |
| names[i] = new Dirent(names[i], types[i]); |
| } |
| } |
| if (toFinish === 0) { |
| callback(null, names); |
| } |
| } else { |
| const len = names.length; |
| for (i = 0; i < len; i++) { |
| names[i] = getDirent(path, names[i], types[i]); |
| } |
| return names; |
| } |
| } |
| |
| function getDirent(path, name, type, callback) { |
| if (typeof callback === 'function') { |
| if (type === UV_DIRENT_UNKNOWN) { |
| lazyLoadFs().lstat(pathModule.join(path, name), (err, stats) => { |
| if (err) { |
| callback(err); |
| return; |
| } |
| callback(null, new DirentFromStats(name, stats)); |
| }); |
| } else { |
| callback(null, new Dirent(name, type)); |
| } |
| } else if (type === UV_DIRENT_UNKNOWN) { |
| const stats = lazyLoadFs().lstatSync(pathModule.join(path, name)); |
| return new DirentFromStats(name, stats); |
| } else { |
| return new Dirent(name, type); |
| } |
| } |
| |
| function getOptions(options, defaultOptions) { |
| if (options === null || options === undefined || |
| typeof options === 'function') { |
| return defaultOptions; |
| } |
| |
| if (typeof options === 'string') { |
| defaultOptions = { ...defaultOptions }; |
| defaultOptions.encoding = options; |
| options = defaultOptions; |
| } else if (typeof options !== 'object') { |
| throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options); |
| } |
| |
| if (options.encoding !== 'buffer') |
| assertEncoding(options.encoding); |
| return options; |
| } |
| |
| function handleErrorFromBinding(ctx) { |
| if (ctx.errno !== undefined) { // libuv error numbers |
| const err = uvException(ctx); |
| // eslint-disable-next-line no-restricted-syntax |
| Error.captureStackTrace(err, handleErrorFromBinding); |
| throw err; |
| } |
| 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. |
| // eslint-disable-next-line no-restricted-syntax |
| Error.captureStackTrace(ctx.error, handleErrorFromBinding); |
| throw ctx.error; |
| } |
| } |
| |
| // Check if the path contains null types if it is a string nor Uint8Array, |
| // otherwise return silently. |
| const nullCheck = hideStackFrames((path, propName, throwError = true) => { |
| const pathIsString = typeof path === 'string'; |
| const pathIsUint8Array = isUint8Array(path); |
| |
| // We can only perform meaningful checks on strings and Uint8Arrays. |
| if ((!pathIsString && !pathIsUint8Array) || |
| (pathIsString && !path.includes('\u0000')) || |
| (pathIsUint8Array && !path.includes(0))) { |
| return; |
| } |
| |
| const err = new ERR_INVALID_ARG_VALUE( |
| propName, |
| path, |
| 'must be a string or Uint8Array without null bytes' |
| ); |
| if (throwError) { |
| throw err; |
| } |
| return err; |
| }); |
| |
| function preprocessSymlinkDestination(path, type, linkPath) { |
| if (!isWindows) { |
| // No preprocessing is needed on Unix. |
| return path; |
| } else if (type === 'junction') { |
| // Junctions paths need to be absolute and \\?\-prefixed. |
| // A relative target is relative to the link's parent directory. |
| path = pathModule.resolve(linkPath, '..', path); |
| return pathModule.toNamespacedPath(path); |
| } else { |
| // Windows symlinks don't tolerate forward slashes. |
| return ('' + path).replace(/\//g, '\\'); |
| } |
| } |
| |
| // Constructor for file stats. |
| function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize, |
| ino, size, blocks) { |
| this.dev = dev; |
| this.mode = mode; |
| this.nlink = nlink; |
| this.uid = uid; |
| this.gid = gid; |
| this.rdev = rdev; |
| this.blksize = blksize; |
| this.ino = ino; |
| this.size = size; |
| this.blocks = blocks; |
| } |
| |
| StatsBase.prototype.isDirectory = function() { |
| return this._checkModeProperty(S_IFDIR); |
| }; |
| |
| StatsBase.prototype.isFile = function() { |
| return this._checkModeProperty(S_IFREG); |
| }; |
| |
| StatsBase.prototype.isBlockDevice = function() { |
| return this._checkModeProperty(S_IFBLK); |
| }; |
| |
| StatsBase.prototype.isCharacterDevice = function() { |
| return this._checkModeProperty(S_IFCHR); |
| }; |
| |
| StatsBase.prototype.isSymbolicLink = function() { |
| return this._checkModeProperty(S_IFLNK); |
| }; |
| |
| StatsBase.prototype.isFIFO = function() { |
| return this._checkModeProperty(S_IFIFO); |
| }; |
| |
| StatsBase.prototype.isSocket = function() { |
| return this._checkModeProperty(S_IFSOCK); |
| }; |
| |
| const kNsPerMsBigInt = 10n ** 6n; |
| const kNsPerSecBigInt = 10n ** 9n; |
| const kMsPerSec = 10 ** 3; |
| const kNsPerMs = 10 ** 6; |
| function msFromTimeSpec(sec, nsec) { |
| return sec * kMsPerSec + nsec / kNsPerMs; |
| } |
| |
| function nsFromTimeSpecBigInt(sec, nsec) { |
| return sec * kNsPerSecBigInt + nsec; |
| } |
| |
| // The Date constructor performs Math.floor() to the timestamp. |
| // https://www.ecma-international.org/ecma-262/#sec-timeclip |
| // Since there may be a precision loss when the timestamp is |
| // converted to a floating point number, we manually round |
| // the timestamp here before passing it to Date(). |
| // Refs: https://github.com/nodejs/node/pull/12607 |
| function dateFromMs(ms) { |
| return new Date(Number(ms) + 0.5); |
| } |
| |
| function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize, |
| ino, size, blocks, |
| atimeNs, mtimeNs, ctimeNs, birthtimeNs) { |
| StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize, |
| ino, size, blocks); |
| |
| this.atimeMs = atimeNs / kNsPerMsBigInt; |
| this.mtimeMs = mtimeNs / kNsPerMsBigInt; |
| this.ctimeMs = ctimeNs / kNsPerMsBigInt; |
| this.birthtimeMs = birthtimeNs / kNsPerMsBigInt; |
| this.atimeNs = atimeNs; |
| this.mtimeNs = mtimeNs; |
| this.ctimeNs = ctimeNs; |
| this.birthtimeNs = birthtimeNs; |
| this.atime = dateFromMs(this.atimeMs); |
| this.mtime = dateFromMs(this.mtimeMs); |
| this.ctime = dateFromMs(this.ctimeMs); |
| this.birthtime = dateFromMs(this.birthtimeMs); |
| } |
| |
| ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype); |
| ObjectSetPrototypeOf(BigIntStats, StatsBase); |
| |
| BigIntStats.prototype._checkModeProperty = function(property) { |
| if (isWindows && (property === S_IFIFO || property === S_IFBLK || |
| property === S_IFSOCK)) { |
| return false; // Some types are not available on Windows |
| } |
| return (this.mode & BigInt(S_IFMT)) === BigInt(property); |
| }; |
| |
| function Stats(dev, mode, nlink, uid, gid, rdev, blksize, |
| ino, size, blocks, |
| atimeMs, mtimeMs, ctimeMs, birthtimeMs) { |
| StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize, |
| ino, size, blocks); |
| this.atimeMs = atimeMs; |
| this.mtimeMs = mtimeMs; |
| this.ctimeMs = ctimeMs; |
| this.birthtimeMs = birthtimeMs; |
| this.atime = dateFromMs(atimeMs); |
| this.mtime = dateFromMs(mtimeMs); |
| this.ctime = dateFromMs(ctimeMs); |
| this.birthtime = dateFromMs(birthtimeMs); |
| } |
| |
| ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype); |
| ObjectSetPrototypeOf(Stats, StatsBase); |
| |
| // HACK: Workaround for https://github.com/standard-things/esm/issues/821. |
| // TODO(ronag): Remove this as soon as `esm` publishes a fixed version. |
| Stats.prototype.isFile = StatsBase.prototype.isFile; |
| |
| Stats.prototype._checkModeProperty = function(property) { |
| if (isWindows && (property === S_IFIFO || property === S_IFBLK || |
| property === S_IFSOCK)) { |
| return false; // Some types are not available on Windows |
| } |
| return (this.mode & S_IFMT) === property; |
| }; |
| |
| function getStatsFromBinding(stats, offset = 0) { |
| if (isBigUint64Array(stats)) { |
| return new BigIntStats( |
| stats[0 + offset], stats[1 + offset], stats[2 + offset], |
| stats[3 + offset], stats[4 + offset], stats[5 + offset], |
| stats[6 + offset], stats[7 + offset], stats[8 + offset], |
| stats[9 + offset], |
| nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]), |
| nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]), |
| nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]), |
| nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]) |
| ); |
| } |
| return new Stats( |
| stats[0 + offset], stats[1 + offset], stats[2 + offset], |
| stats[3 + offset], stats[4 + offset], stats[5 + offset], |
| stats[6 + offset], stats[7 + offset], stats[8 + offset], |
| stats[9 + offset], |
| msFromTimeSpec(stats[10 + offset], stats[11 + offset]), |
| msFromTimeSpec(stats[12 + offset], stats[13 + offset]), |
| msFromTimeSpec(stats[14 + offset], stats[15 + offset]), |
| msFromTimeSpec(stats[16 + offset], stats[17 + offset]) |
| ); |
| } |
| |
| function stringToFlags(flags) { |
| if (typeof flags === 'number') { |
| return flags; |
| } |
| |
| if (flags == null) { |
| return O_RDONLY; |
| } |
| |
| switch (flags) { |
| case 'r' : return O_RDONLY; |
| case 'rs' : // Fall through. |
| case 'sr' : return O_RDONLY | O_SYNC; |
| case 'r+' : return O_RDWR; |
| case 'rs+' : // Fall through. |
| case 'sr+' : return O_RDWR | O_SYNC; |
| |
| case 'w' : return O_TRUNC | O_CREAT | O_WRONLY; |
| case 'wx' : // Fall through. |
| case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL; |
| |
| case 'w+' : return O_TRUNC | O_CREAT | O_RDWR; |
| case 'wx+': // Fall through. |
| case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL; |
| |
| case 'a' : return O_APPEND | O_CREAT | O_WRONLY; |
| case 'ax' : // Fall through. |
| case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL; |
| case 'as' : // Fall through. |
| case 'sa' : return O_APPEND | O_CREAT | O_WRONLY | O_SYNC; |
| |
| case 'a+' : return O_APPEND | O_CREAT | O_RDWR; |
| case 'ax+': // Fall through. |
| case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL; |
| case 'as+': // Fall through. |
| case 'sa+': return O_APPEND | O_CREAT | O_RDWR | O_SYNC; |
| } |
| |
| throw new ERR_INVALID_OPT_VALUE('flags', flags); |
| } |
| |
| const stringToSymlinkType = hideStackFrames((type) => { |
| let flags = 0; |
| if (typeof type === 'string') { |
| switch (type) { |
| case 'dir': |
| flags |= UV_FS_SYMLINK_DIR; |
| break; |
| case 'junction': |
| flags |= UV_FS_SYMLINK_JUNCTION; |
| break; |
| case 'file': |
| break; |
| default: |
| throw new ERR_FS_INVALID_SYMLINK_TYPE(type); |
| } |
| } |
| return flags; |
| }); |
| |
| // converts Date or number to a fractional UNIX timestamp |
| function toUnixTimestamp(time, name = 'time') { |
| // eslint-disable-next-line eqeqeq |
| if (typeof time === 'string' && +time == time) { |
| return +time; |
| } |
| if (NumberIsFinite(time)) { |
| if (time < 0) { |
| return DateNow() / 1000; |
| } |
| return time; |
| } |
| if (isDate(time)) { |
| // Convert to 123.456 UNIX timestamp |
| return time.getTime() / 1000; |
| } |
| throw new ERR_INVALID_ARG_TYPE(name, ['Date', 'Time in seconds'], time); |
| } |
| |
| const validateOffsetLengthRead = hideStackFrames( |
| (offset, length, bufferLength) => { |
| if (offset < 0 || offset >= bufferLength) { |
| throw new ERR_OUT_OF_RANGE('offset', |
| `>= 0 && <= ${bufferLength}`, offset); |
| } |
| if (length < 0 || offset + length > bufferLength) { |
| throw new ERR_OUT_OF_RANGE('length', |
| `>= 0 && <= ${bufferLength - offset}`, length); |
| } |
| } |
| ); |
| |
| const validateOffsetLengthWrite = hideStackFrames( |
| (offset, length, byteLength) => { |
| if (offset > byteLength) { |
| throw new ERR_OUT_OF_RANGE('offset', `<= ${byteLength}`, offset); |
| } |
| |
| if (length > byteLength - offset) { |
| throw new ERR_OUT_OF_RANGE('length', `<= ${byteLength - offset}`, length); |
| } |
| } |
| ); |
| |
| const validatePath = hideStackFrames((path, propName = 'path') => { |
| if (typeof path !== 'string' && !isUint8Array(path)) { |
| throw new ERR_INVALID_ARG_TYPE(propName, ['string', 'Buffer', 'URL'], path); |
| } |
| |
| const err = nullCheck(path, propName, false); |
| |
| if (err !== undefined) { |
| throw err; |
| } |
| }); |
| |
| const getValidatedPath = hideStackFrames((fileURLOrPath, propName = 'path') => { |
| const path = toPathIfFileURL(fileURLOrPath); |
| validatePath(path, propName); |
| return path; |
| }); |
| |
| const validateBufferArray = hideStackFrames((buffers, propName = 'buffers') => { |
| if (!ArrayIsArray(buffers)) |
| throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); |
| |
| for (let i = 0; i < buffers.length; i++) { |
| if (!isArrayBufferView(buffers[i])) |
| throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); |
| } |
| |
| return buffers; |
| }); |
| |
| let nonPortableTemplateWarn = true; |
| |
| function warnOnNonPortableTemplate(template) { |
| // Template strings passed to the mkdtemp() family of functions should not |
| // end with 'X' because they are handled inconsistently across platforms. |
| if (nonPortableTemplateWarn && template.endsWith('X')) { |
| process.emitWarning('mkdtemp() templates ending with X are not portable. ' + |
| 'For details see: https://nodejs.org/api/fs.html'); |
| nonPortableTemplateWarn = false; |
| } |
| } |
| |
| const defaultRmdirOptions = { |
| retryDelay: 100, |
| maxRetries: 0, |
| recursive: false, |
| }; |
| |
| const validateRmdirOptions = hideStackFrames((options) => { |
| if (options === undefined) |
| return defaultRmdirOptions; |
| if (options === null || typeof options !== 'object') |
| throw new ERR_INVALID_ARG_TYPE('options', 'object', options); |
| |
| options = { ...defaultRmdirOptions, ...options }; |
| |
| if (typeof options.recursive !== 'boolean') |
| throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive); |
| |
| validateInt32(options.retryDelay, 'retryDelay', 0); |
| validateUint32(options.maxRetries, 'maxRetries'); |
| |
| return options; |
| }); |
| |
| const getValidMode = hideStackFrames((mode, type) => { |
| let min = kMinimumAccessMode; |
| let max = kMaximumAccessMode; |
| let def = F_OK; |
| if (type === 'copyFile') { |
| min = kMinimumCopyMode; |
| max = kMaximumCopyMode; |
| def = mode || kDefaultCopyMode; |
| } else { |
| assert(type === 'access'); |
| } |
| if (mode == null) { |
| return def; |
| } |
| if (Number.isInteger(mode) && mode >= min && mode <= max) { |
| return mode; |
| } |
| if (typeof mode !== 'number') { |
| throw new ERR_INVALID_ARG_TYPE('mode', 'integer', mode); |
| } |
| throw new ERR_OUT_OF_RANGE( |
| 'mode', `an integer >= ${min} && <= ${max}`, mode); |
| }); |
| |
| const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => { |
| if (typeof buffer !== 'string') { |
| throw new ERR_INVALID_ARG_TYPE( |
| name, |
| ['string', 'Buffer', 'TypedArray', 'DataView'], |
| buffer |
| ); |
| } |
| }); |
| |
| module.exports = { |
| assertEncoding, |
| BigIntStats, // for testing |
| copyObject, |
| Dirent, |
| getDirent, |
| getDirents, |
| getOptions, |
| getValidatedPath, |
| getValidMode, |
| handleErrorFromBinding, |
| nullCheck, |
| preprocessSymlinkDestination, |
| realpathCacheKey: Symbol('realpathCacheKey'), |
| getStatsFromBinding, |
| stringToFlags, |
| stringToSymlinkType, |
| Stats, |
| toUnixTimestamp, |
| validateBufferArray, |
| validateOffsetLengthRead, |
| validateOffsetLengthWrite, |
| validatePath, |
| validateRmdirOptions, |
| validateStringAfterArrayBufferView, |
| warnOnNonPortableTemplate |
| }; |