From 2620358624b6c0f6c7d02dc2e4333eae9e73b3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 14 Feb 2018 09:57:42 +0100 Subject: [PATCH] fs: move utility functions to internal/fs PR-URL: https://github.com/nodejs/node/pull/18777 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Gus Caplan Reviewed-By: James M Snell Reviewed-By: Joyee Cheung --- lib/fs.js | 309 +++--------------------------------------- lib/internal/fs.js | 325 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 340 insertions(+), 294 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index e762f90d96a284..f78703762975b9 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -37,14 +37,29 @@ const { Buffer } = require('buffer'); const errors = require('internal/errors'); const { Readable, Writable } = require('stream'); const EventEmitter = require('events'); -const { FSReqWrap } = binding; +const { FSReqWrap, statValues } = binding; const { FSEvent } = process.binding('fs_event_wrap'); const internalFS = require('internal/fs'); const { getPathFromURL } = require('internal/url'); const internalUtil = require('internal/util'); const { - assertEncoding, - stringToFlags + copyObject, + getOptions, + isUint32, + modeNum, + nullCheck, + preprocessSymlinkDestination, + Stats, + statsFromValues, + stringToFlags, + stringToSymlinkType, + toUnixTimestamp, + validateBuffer, + validateLen, + validateOffsetLengthRead, + validateOffsetLengthWrite, + validatePath, + validateUint32 } = internalFS; const { CHAR_FORWARD_SLASH, @@ -70,9 +85,6 @@ const errnoException = errors.errnoException; let truncateWarn = true; -function isInt32(n) { return n === (n | 0); } -function isUint32(n) { return n === (n >>> 0); } - function showTruncateDeprecation() { if (truncateWarn) { process.emitWarning( @@ -83,35 +95,6 @@ function showTruncateDeprecation() { } } -function getOptions(options, defaultOptions) { - if (options === null || options === undefined || - typeof options === 'function') { - return defaultOptions; - } - - if (typeof options === 'string') { - defaultOptions = util._extend({}, defaultOptions); - defaultOptions.encoding = options; - options = defaultOptions; - } else if (typeof options !== 'object') { - throw new errors.TypeError('ERR_INVALID_ARG_TYPE', - 'options', - ['string', 'Object'], - options); - } - - if (options.encoding !== 'buffer') - assertEncoding(options.encoding); - return options; -} - -function copyObject(source) { - var target = {}; - for (var key in source) - target[key] = source[key]; - return target; -} - function handleErrorFromBinding(ctx) { if (ctx.errno !== undefined) { // libuv error numbers const err = errors.uvException(ctx); @@ -175,117 +158,6 @@ function makeCallback(cb) { }; } -function validateBuffer(buffer) { - if (!isUint8Array(buffer)) { - const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer', - ['Buffer', 'Uint8Array']); - Error.captureStackTrace(err, validateBuffer); - throw err; - } -} - -function validateLen(len) { - let err; - - if (!isInt32(len)) - err = new errors.TypeError('ERR_INVALID_ARG_TYPE', 'len', 'integer'); - - if (err !== undefined) { - Error.captureStackTrace(err, validateLen); - throw err; - } -} - -function validateOffsetLengthRead(offset, length, bufferLength) { - let err; - - if (offset < 0 || offset >= bufferLength) { - err = new errors.RangeError('ERR_OUT_OF_RANGE', 'offset'); - } else if (length < 0 || offset + length > bufferLength) { - err = new errors.RangeError('ERR_OUT_OF_RANGE', 'length'); - } - - if (err !== undefined) { - Error.captureStackTrace(err, validateOffsetLengthRead); - throw err; - } -} - -function validateOffsetLengthWrite(offset, length, byteLength) { - let err; - - if (offset > byteLength) { - err = new errors.RangeError('ERR_OUT_OF_RANGE', 'offset'); - } else if (offset + length > byteLength || offset + length > kMaxLength) { - err = new errors.RangeError('ERR_OUT_OF_RANGE', 'length'); - } - - if (err !== undefined) { - Error.captureStackTrace(err, validateOffsetLengthWrite); - throw err; - } -} - -// Check if the path contains null types if it is a string nor Uint8Array, -// otherwise return silently. -function nullCheck(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) { - return; - } - - if (pathIsString && path.indexOf('\u0000') === -1) { - return; - } else if (pathIsUint8Array && path.indexOf(0) === -1) { - return; - } - - const err = new errors.Error( - 'ERR_INVALID_ARG_VALUE', propName, path, - 'must be a string or Uint8Array without null bytes'); - - if (throwError) { - Error.captureStackTrace(err, nullCheck); - throw err; - } - return err; -} - -function validatePath(path, propName) { - let err; - - if (propName === undefined) { - propName = 'path'; - } - - if (typeof path !== 'string' && !isUint8Array(path)) { - err = new errors.TypeError('ERR_INVALID_ARG_TYPE', propName, - ['string', 'Buffer', 'URL']); - } else { - err = nullCheck(path, propName, false); - } - - if (err !== undefined) { - Error.captureStackTrace(err, validatePath); - throw err; - } -} - -function validateUint32(value, propName) { - let err; - - if (!isUint32(value)) - err = new errors.TypeError('ERR_INVALID_ARG_TYPE', propName, 'integer'); - - if (err !== undefined) { - Error.captureStackTrace(err, validateUint32); - throw err; - } -} - // 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. @@ -308,85 +180,8 @@ function isFd(path) { return (path >>> 0) === path; } -// Constructor for file stats. -function Stats( - dev, - mode, - nlink, - uid, - gid, - rdev, - blksize, - ino, - size, - blocks, - atim_msec, - mtim_msec, - ctim_msec, - birthtim_msec -) { - 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; - this.atimeMs = atim_msec; - this.mtimeMs = mtim_msec; - this.ctimeMs = ctim_msec; - this.birthtimeMs = birthtim_msec; - this.atime = new Date(atim_msec + 0.5); - this.mtime = new Date(mtim_msec + 0.5); - this.ctime = new Date(ctim_msec + 0.5); - this.birthtime = new Date(birthtim_msec + 0.5); -} fs.Stats = Stats; -Stats.prototype._checkModeProperty = function(property) { - return ((this.mode & S_IFMT) === property); -}; - -Stats.prototype.isDirectory = function() { - return this._checkModeProperty(constants.S_IFDIR); -}; - -Stats.prototype.isFile = function() { - return this._checkModeProperty(S_IFREG); -}; - -Stats.prototype.isBlockDevice = function() { - return this._checkModeProperty(constants.S_IFBLK); -}; - -Stats.prototype.isCharacterDevice = function() { - return this._checkModeProperty(constants.S_IFCHR); -}; - -Stats.prototype.isSymbolicLink = function() { - return this._checkModeProperty(S_IFLNK); -}; - -Stats.prototype.isFIFO = function() { - return this._checkModeProperty(S_IFIFO); -}; - -Stats.prototype.isSocket = function() { - return this._checkModeProperty(S_IFSOCK); -}; - -const statValues = binding.statValues; - -function statsFromValues(stats = statValues) { - return new Stats(stats[0], stats[1], stats[2], stats[3], stats[4], stats[5], - stats[6] < 0 ? undefined : stats[6], stats[7], stats[8], - stats[9] < 0 ? undefined : stats[9], stats[10], stats[11], - stats[12], stats[13]); -} - // Don't allow mode to accidentally be overwritten. Object.defineProperties(fs, { F_OK: { enumerable: true, value: constants.F_OK || 0 }, @@ -760,16 +555,6 @@ fs.closeSync = function(fd) { handleErrorFromBinding(ctx); }; -function modeNum(m, def) { - if (typeof m === 'number') - return m; - if (typeof m === 'string') - return parseInt(m, 8); - if (def) - return modeNum(def); - return undefined; -} - fs.open = function(path, flags, mode, callback_) { var callback = makeCallback(arguments[arguments.length - 1]); mode = modeNum(mode, 0o666); @@ -1162,42 +947,6 @@ fs.readlinkSync = function(path, options) { return result; }; -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, '\\'); - } -} - -function stringToSymlinkType(type) { - let flags = 0; - if (typeof type === 'string') { - switch (type) { - case 'dir': - flags |= constants.UV_FS_SYMLINK_DIR; - break; - case 'junction': - flags |= constants.UV_FS_SYMLINK_JUNCTION; - break; - case 'file': - break; - default: - const err = new errors.Error('ERR_FS_INVALID_SYMLINK_TYPE', type); - Error.captureStackTrace(err, stringToSymlinkType); - throw err; - } - } - return flags; -} - fs.symlink = function(target, path, type_, callback_) { var type = (typeof type_ === 'string' ? type_ : null); var callback = makeCallback(arguments[arguments.length - 1]); @@ -1421,28 +1170,6 @@ fs.chownSync = function(path, uid, gid) { return binding.chown(pathModule.toNamespacedPath(path), uid, gid); }; -// 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 (Number.isFinite(time)) { - if (time < 0) { - return Date.now() / 1000; - } - return time; - } - if (util.isDate(time)) { - // convert to 123.456 UNIX timestamp - return time.getTime() / 1000; - } - throw new errors.TypeError('ERR_INVALID_ARG_TYPE', - name, - ['Date', 'Time in seconds'], - time); -} - // exported for unit tests, not for public consumption fs._toUnixTimestamp = toUnixTimestamp; diff --git a/lib/internal/fs.js b/lib/internal/fs.js index 01c5ff4bfc2e4d..9c5c4d8ad7936f 100644 --- a/lib/internal/fs.js +++ b/lib/internal/fs.js @@ -1,9 +1,11 @@ 'use strict'; -const { Buffer } = require('buffer'); +const { Buffer, kMaxLength } = require('buffer'); const { Writable } = require('stream'); const errors = require('internal/errors'); +const { isUint8Array } = require('internal/util/types'); const fs = require('fs'); +const pathModule = require('path'); const util = require('util'); const { @@ -14,8 +16,21 @@ const { O_RDWR, O_SYNC, O_TRUNC, - O_WRONLY + 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 } = process.binding('constants').fs; +const { statValues } = process.binding('fs'); + +const isWindows = process.platform === 'win32'; function assertEncoding(encoding) { if (encoding && !Buffer.isEncoding(encoding)) { @@ -23,6 +38,167 @@ function assertEncoding(encoding) { } } +function copyObject(source) { + var target = {}; + for (var key in source) + target[key] = source[key]; + return target; +} + +function getOptions(options, defaultOptions) { + if (options === null || options === undefined || + typeof options === 'function') { + return defaultOptions; + } + + if (typeof options === 'string') { + defaultOptions = util._extend({}, defaultOptions); + defaultOptions.encoding = options; + options = defaultOptions; + } else if (typeof options !== 'object') { + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + 'options', + ['string', 'Object'], + options); + } + + if (options.encoding !== 'buffer') + assertEncoding(options.encoding); + return options; +} + +function isInt32(n) { return n === (n | 0); } +function isUint32(n) { return n === (n >>> 0); } + +function modeNum(m, def) { + if (typeof m === 'number') + return m; + if (typeof m === 'string') + return parseInt(m, 8); + if (def) + return modeNum(def); + return undefined; +} + +// Check if the path contains null types if it is a string nor Uint8Array, +// otherwise return silently. +function nullCheck(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) { + return; + } + + if (pathIsString && path.indexOf('\u0000') === -1) { + return; + } else if (pathIsUint8Array && path.indexOf(0) === -1) { + return; + } + + const err = new errors.Error( + 'ERR_INVALID_ARG_VALUE', propName, path, + 'must be a string or Uint8Array without null bytes'); + + if (throwError) { + Error.captureStackTrace(err, nullCheck); + 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 Stats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atim_msec, + mtim_msec, + ctim_msec, + birthtim_msec +) { + 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; + this.atimeMs = atim_msec; + this.mtimeMs = mtim_msec; + this.ctimeMs = ctim_msec; + this.birthtimeMs = birthtim_msec; + this.atime = new Date(atim_msec + 0.5); + this.mtime = new Date(mtim_msec + 0.5); + this.ctime = new Date(ctim_msec + 0.5); + this.birthtime = new Date(birthtim_msec + 0.5); +} + +Stats.prototype._checkModeProperty = function(property) { + return ((this.mode & S_IFMT) === property); +}; + +Stats.prototype.isDirectory = function() { + return this._checkModeProperty(S_IFDIR); +}; + +Stats.prototype.isFile = function() { + return this._checkModeProperty(S_IFREG); +}; + +Stats.prototype.isBlockDevice = function() { + return this._checkModeProperty(S_IFBLK); +}; + +Stats.prototype.isCharacterDevice = function() { + return this._checkModeProperty(S_IFCHR); +}; + +Stats.prototype.isSymbolicLink = function() { + return this._checkModeProperty(S_IFLNK); +}; + +Stats.prototype.isFIFO = function() { + return this._checkModeProperty(S_IFIFO); +}; + +Stats.prototype.isSocket = function() { + return this._checkModeProperty(S_IFSOCK); +}; + +function statsFromValues(stats = statValues) { + return new Stats(stats[0], stats[1], stats[2], stats[3], stats[4], stats[5], + stats[6] < 0 ? undefined : stats[6], stats[7], stats[8], + stats[9] < 0 ? undefined : stats[9], stats[10], stats[11], + stats[12], stats[13]); +} + function stringToFlags(flags) { if (typeof flags === 'number') { return flags; @@ -56,6 +232,27 @@ function stringToFlags(flags) { throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); } +function stringToSymlinkType(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: + const err = new errors.Error('ERR_FS_INVALID_SYMLINK_TYPE', type); + Error.captureStackTrace(err, stringToSymlinkType); + throw err; + } + } + return flags; +} + // Temporary hack for process.stdout and process.stderr when piped to files. function SyncWriteStream(fd, options) { Writable.call(this); @@ -95,9 +292,131 @@ SyncWriteStream.prototype.destroy = function() { return true; }; +// 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 (Number.isFinite(time)) { + if (time < 0) { + return Date.now() / 1000; + } + return time; + } + if (util.isDate(time)) { + // convert to 123.456 UNIX timestamp + return time.getTime() / 1000; + } + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', + name, + ['Date', 'Time in seconds'], + time); +} + +function validateBuffer(buffer) { + if (!isUint8Array(buffer)) { + const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', 'buffer', + ['Buffer', 'Uint8Array']); + Error.captureStackTrace(err, validateBuffer); + throw err; + } +} + +function validateLen(len) { + let err; + + if (!isInt32(len)) + err = new errors.TypeError('ERR_INVALID_ARG_TYPE', 'len', 'integer'); + + if (err !== undefined) { + Error.captureStackTrace(err, validateLen); + throw err; + } +} + +function validateOffsetLengthRead(offset, length, bufferLength) { + let err; + + if (offset < 0 || offset >= bufferLength) { + err = new errors.RangeError('ERR_OUT_OF_RANGE', 'offset'); + } else if (length < 0 || offset + length > bufferLength) { + err = new errors.RangeError('ERR_OUT_OF_RANGE', 'length'); + } + + if (err !== undefined) { + Error.captureStackTrace(err, validateOffsetLengthRead); + throw err; + } +} + +function validateOffsetLengthWrite(offset, length, byteLength) { + let err; + + if (offset > byteLength) { + err = new errors.RangeError('ERR_OUT_OF_RANGE', 'offset'); + } else if (offset + length > byteLength || offset + length > kMaxLength) { + err = new errors.RangeError('ERR_OUT_OF_RANGE', 'length'); + } + + if (err !== undefined) { + Error.captureStackTrace(err, validateOffsetLengthWrite); + throw err; + } +} + +function validatePath(path, propName) { + let err; + + if (propName === undefined) { + propName = 'path'; + } + + if (typeof path !== 'string' && !isUint8Array(path)) { + err = new errors.TypeError('ERR_INVALID_ARG_TYPE', propName, + ['string', 'Buffer', 'URL']); + } else { + err = nullCheck(path, propName, false); + } + + if (err !== undefined) { + Error.captureStackTrace(err, validatePath); + throw err; + } +} + +function validateUint32(value, propName) { + let err; + + if (!isUint32(value)) + err = new errors.TypeError('ERR_INVALID_ARG_TYPE', propName, 'integer'); + + if (err !== undefined) { + Error.captureStackTrace(err, validateUint32); + throw err; + } +} + module.exports = { assertEncoding, + copyObject, + getOptions, + isInt32, + isUint32, + modeNum, + nullCheck, + preprocessSymlinkDestination, + realpathCacheKey: Symbol('realpathCacheKey'), + statsFromValues, stringToFlags, + stringToSymlinkType, + Stats, SyncWriteStream, - realpathCacheKey: Symbol('realpathCacheKey') + toUnixTimestamp, + validateBuffer, + validateLen, + validateOffsetLengthRead, + validateOffsetLengthWrite, + validatePath, + validateUint32 };