1'use strict'; 2 3const { 4 ArrayIsArray, 5 BigInt, 6 DateNow, 7 Error, 8 Number, 9 NumberIsFinite, 10 ObjectSetPrototypeOf, 11 ReflectOwnKeys, 12 Symbol, 13} = primordials; 14 15const { Buffer, kMaxLength } = require('buffer'); 16const { 17 codes: { 18 ERR_FS_INVALID_SYMLINK_TYPE, 19 ERR_INVALID_ARG_TYPE, 20 ERR_INVALID_ARG_VALUE, 21 ERR_INVALID_OPT_VALUE, 22 ERR_INVALID_OPT_VALUE_ENCODING, 23 ERR_OUT_OF_RANGE 24 }, 25 hideStackFrames, 26 uvException 27} = require('internal/errors'); 28const { 29 isArrayBufferView, 30 isUint8Array, 31 isDate, 32 isBigUint64Array 33} = require('internal/util/types'); 34const { once } = require('internal/util'); 35const { toPathIfFileURL } = require('internal/url'); 36const { 37 validateInt32, 38 validateUint32 39} = require('internal/validators'); 40const pathModule = require('path'); 41const kType = Symbol('type'); 42const kStats = Symbol('stats'); 43 44const { 45 O_APPEND, 46 O_CREAT, 47 O_EXCL, 48 O_RDONLY, 49 O_RDWR, 50 O_SYNC, 51 O_TRUNC, 52 O_WRONLY, 53 S_IFBLK, 54 S_IFCHR, 55 S_IFDIR, 56 S_IFIFO, 57 S_IFLNK, 58 S_IFMT, 59 S_IFREG, 60 S_IFSOCK, 61 UV_FS_SYMLINK_DIR, 62 UV_FS_SYMLINK_JUNCTION, 63 UV_DIRENT_UNKNOWN, 64 UV_DIRENT_FILE, 65 UV_DIRENT_DIR, 66 UV_DIRENT_LINK, 67 UV_DIRENT_FIFO, 68 UV_DIRENT_SOCKET, 69 UV_DIRENT_CHAR, 70 UV_DIRENT_BLOCK 71} = internalBinding('constants').fs; 72 73const isWindows = process.platform === 'win32'; 74 75let fs; 76function lazyLoadFs() { 77 if (!fs) { 78 fs = require('fs'); 79 } 80 return fs; 81} 82 83function assertEncoding(encoding) { 84 if (encoding && !Buffer.isEncoding(encoding)) { 85 throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding); 86 } 87} 88 89class Dirent { 90 constructor(name, type) { 91 this.name = name; 92 this[kType] = type; 93 } 94 95 isDirectory() { 96 return this[kType] === UV_DIRENT_DIR; 97 } 98 99 isFile() { 100 return this[kType] === UV_DIRENT_FILE; 101 } 102 103 isBlockDevice() { 104 return this[kType] === UV_DIRENT_BLOCK; 105 } 106 107 isCharacterDevice() { 108 return this[kType] === UV_DIRENT_CHAR; 109 } 110 111 isSymbolicLink() { 112 return this[kType] === UV_DIRENT_LINK; 113 } 114 115 isFIFO() { 116 return this[kType] === UV_DIRENT_FIFO; 117 } 118 119 isSocket() { 120 return this[kType] === UV_DIRENT_SOCKET; 121 } 122} 123 124class DirentFromStats extends Dirent { 125 constructor(name, stats) { 126 super(name, null); 127 this[kStats] = stats; 128 } 129} 130 131for (const name of ReflectOwnKeys(Dirent.prototype)) { 132 if (name === 'constructor') { 133 continue; 134 } 135 DirentFromStats.prototype[name] = function() { 136 return this[kStats][name](); 137 }; 138} 139 140function copyObject(source) { 141 const target = {}; 142 for (const key in source) 143 target[key] = source[key]; 144 return target; 145} 146 147const bufferSep = Buffer.from(pathModule.sep); 148 149function join(path, name) { 150 if ((typeof path === 'string' || isUint8Array(path)) && 151 name === undefined) { 152 return path; 153 } 154 155 if (typeof path === 'string' && isUint8Array(name)) { 156 const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep)); 157 return Buffer.concat([pathBuffer, name]); 158 } 159 160 if (typeof path === 'string' && typeof name === 'string') { 161 return pathModule.join(path, name); 162 } 163 164 if (isUint8Array(path) && isUint8Array(name)) { 165 return Buffer.concat([path, bufferSep, name]); 166 } 167 168 throw new ERR_INVALID_ARG_TYPE( 169 'path', ['string', 'Buffer'], path); 170} 171 172function getDirents(path, [names, types], callback) { 173 let i; 174 if (typeof callback === 'function') { 175 const len = names.length; 176 let toFinish = 0; 177 callback = once(callback); 178 for (i = 0; i < len; i++) { 179 const type = types[i]; 180 if (type === UV_DIRENT_UNKNOWN) { 181 const name = names[i]; 182 const idx = i; 183 toFinish++; 184 let filepath; 185 try { 186 filepath = join(path, name); 187 } catch (err) { 188 callback(err); 189 return; 190 } 191 lazyLoadFs().lstat(filepath, (err, stats) => { 192 if (err) { 193 callback(err); 194 return; 195 } 196 names[idx] = new DirentFromStats(name, stats); 197 if (--toFinish === 0) { 198 callback(null, names); 199 } 200 }); 201 } else { 202 names[i] = new Dirent(names[i], types[i]); 203 } 204 } 205 if (toFinish === 0) { 206 callback(null, names); 207 } 208 } else { 209 const len = names.length; 210 for (i = 0; i < len; i++) { 211 names[i] = getDirent(path, names[i], types[i]); 212 } 213 return names; 214 } 215} 216 217function getDirent(path, name, type, callback) { 218 if (typeof callback === 'function') { 219 if (type === UV_DIRENT_UNKNOWN) { 220 let filepath; 221 try { 222 filepath = join(path, name); 223 } catch (err) { 224 callback(err); 225 return; 226 } 227 lazyLoadFs().lstat(filepath, (err, stats) => { 228 if (err) { 229 callback(err); 230 return; 231 } 232 callback(null, new DirentFromStats(name, stats)); 233 }); 234 } else { 235 callback(null, new Dirent(name, type)); 236 } 237 } else if (type === UV_DIRENT_UNKNOWN) { 238 const stats = lazyLoadFs().lstatSync(join(path, name)); 239 return new DirentFromStats(name, stats); 240 } else { 241 return new Dirent(name, type); 242 } 243} 244 245function getOptions(options, defaultOptions) { 246 if (options === null || options === undefined || 247 typeof options === 'function') { 248 return defaultOptions; 249 } 250 251 if (typeof options === 'string') { 252 defaultOptions = { ...defaultOptions }; 253 defaultOptions.encoding = options; 254 options = defaultOptions; 255 } else if (typeof options !== 'object') { 256 throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options); 257 } 258 259 if (options.encoding !== 'buffer') 260 assertEncoding(options.encoding); 261 return options; 262} 263 264function handleErrorFromBinding(ctx) { 265 if (ctx.errno !== undefined) { // libuv error numbers 266 const err = uvException(ctx); 267 // eslint-disable-next-line no-restricted-syntax 268 Error.captureStackTrace(err, handleErrorFromBinding); 269 throw err; 270 } 271 if (ctx.error !== undefined) { // Errors created in C++ land. 272 // TODO(joyeecheung): currently, ctx.error are encoding errors 273 // usually caused by memory problems. We need to figure out proper error 274 // code(s) for this. 275 // eslint-disable-next-line no-restricted-syntax 276 Error.captureStackTrace(ctx.error, handleErrorFromBinding); 277 throw ctx.error; 278 } 279} 280 281// Check if the path contains null types if it is a string nor Uint8Array, 282// otherwise return silently. 283const nullCheck = hideStackFrames((path, propName, throwError = true) => { 284 const pathIsString = typeof path === 'string'; 285 const pathIsUint8Array = isUint8Array(path); 286 287 // We can only perform meaningful checks on strings and Uint8Arrays. 288 if ((!pathIsString && !pathIsUint8Array) || 289 (pathIsString && !path.includes('\u0000')) || 290 (pathIsUint8Array && !path.includes(0))) { 291 return; 292 } 293 294 const err = new ERR_INVALID_ARG_VALUE( 295 propName, 296 path, 297 'must be a string or Uint8Array without null bytes' 298 ); 299 if (throwError) { 300 throw err; 301 } 302 return err; 303}); 304 305function preprocessSymlinkDestination(path, type, linkPath) { 306 if (!isWindows) { 307 // No preprocessing is needed on Unix. 308 return path; 309 } 310 if (type === 'junction') { 311 // Junctions paths need to be absolute and \\?\-prefixed. 312 // A relative target is relative to the link's parent directory. 313 path = pathModule.resolve(linkPath, '..', path); 314 return pathModule.toNamespacedPath(path); 315 } 316 317 if (pathModule.isAbsolute(path)) { 318 // If the path is absolute, use the \\?\-prefix to enable long filenames 319 return pathModule.toNamespacedPath(path); 320 } 321 // Windows symlinks don't tolerate forward slashes. 322 return ('' + path).replace(/\//g, '\\'); 323} 324 325// Constructor for file stats. 326function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize, 327 ino, size, blocks) { 328 this.dev = dev; 329 this.mode = mode; 330 this.nlink = nlink; 331 this.uid = uid; 332 this.gid = gid; 333 this.rdev = rdev; 334 this.blksize = blksize; 335 this.ino = ino; 336 this.size = size; 337 this.blocks = blocks; 338} 339 340StatsBase.prototype.isDirectory = function() { 341 return this._checkModeProperty(S_IFDIR); 342}; 343 344StatsBase.prototype.isFile = function() { 345 return this._checkModeProperty(S_IFREG); 346}; 347 348StatsBase.prototype.isBlockDevice = function() { 349 return this._checkModeProperty(S_IFBLK); 350}; 351 352StatsBase.prototype.isCharacterDevice = function() { 353 return this._checkModeProperty(S_IFCHR); 354}; 355 356StatsBase.prototype.isSymbolicLink = function() { 357 return this._checkModeProperty(S_IFLNK); 358}; 359 360StatsBase.prototype.isFIFO = function() { 361 return this._checkModeProperty(S_IFIFO); 362}; 363 364StatsBase.prototype.isSocket = function() { 365 return this._checkModeProperty(S_IFSOCK); 366}; 367 368const kNsPerMsBigInt = 10n ** 6n; 369const kNsPerSecBigInt = 10n ** 9n; 370const kMsPerSec = 10 ** 3; 371const kNsPerMs = 10 ** 6; 372function msFromTimeSpec(sec, nsec) { 373 return sec * kMsPerSec + nsec / kNsPerMs; 374} 375 376function nsFromTimeSpecBigInt(sec, nsec) { 377 return sec * kNsPerSecBigInt + nsec; 378} 379 380// The Date constructor performs Math.floor() to the timestamp. 381// https://www.ecma-international.org/ecma-262/#sec-timeclip 382// Since there may be a precision loss when the timestamp is 383// converted to a floating point number, we manually round 384// the timestamp here before passing it to Date(). 385// Refs: https://github.com/nodejs/node/pull/12607 386function dateFromMs(ms) { 387 return new Date(Number(ms) + 0.5); 388} 389 390function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize, 391 ino, size, blocks, 392 atimeNs, mtimeNs, ctimeNs, birthtimeNs) { 393 StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize, 394 ino, size, blocks); 395 396 this.atimeMs = atimeNs / kNsPerMsBigInt; 397 this.mtimeMs = mtimeNs / kNsPerMsBigInt; 398 this.ctimeMs = ctimeNs / kNsPerMsBigInt; 399 this.birthtimeMs = birthtimeNs / kNsPerMsBigInt; 400 this.atimeNs = atimeNs; 401 this.mtimeNs = mtimeNs; 402 this.ctimeNs = ctimeNs; 403 this.birthtimeNs = birthtimeNs; 404 this.atime = dateFromMs(this.atimeMs); 405 this.mtime = dateFromMs(this.mtimeMs); 406 this.ctime = dateFromMs(this.ctimeMs); 407 this.birthtime = dateFromMs(this.birthtimeMs); 408} 409 410ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype); 411ObjectSetPrototypeOf(BigIntStats, StatsBase); 412 413BigIntStats.prototype._checkModeProperty = function(property) { 414 if (isWindows && (property === S_IFIFO || property === S_IFBLK || 415 property === S_IFSOCK)) { 416 return false; // Some types are not available on Windows 417 } 418 return (this.mode & BigInt(S_IFMT)) === BigInt(property); 419}; 420 421function Stats(dev, mode, nlink, uid, gid, rdev, blksize, 422 ino, size, blocks, 423 atimeMs, mtimeMs, ctimeMs, birthtimeMs) { 424 StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize, 425 ino, size, blocks); 426 this.atimeMs = atimeMs; 427 this.mtimeMs = mtimeMs; 428 this.ctimeMs = ctimeMs; 429 this.birthtimeMs = birthtimeMs; 430 this.atime = dateFromMs(atimeMs); 431 this.mtime = dateFromMs(mtimeMs); 432 this.ctime = dateFromMs(ctimeMs); 433 this.birthtime = dateFromMs(birthtimeMs); 434} 435 436ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype); 437ObjectSetPrototypeOf(Stats, StatsBase); 438 439// HACK: Workaround for https://github.com/standard-things/esm/issues/821. 440// TODO(ronag): Remove this as soon as `esm` publishes a fixed version. 441Stats.prototype.isFile = StatsBase.prototype.isFile; 442 443Stats.prototype._checkModeProperty = function(property) { 444 if (isWindows && (property === S_IFIFO || property === S_IFBLK || 445 property === S_IFSOCK)) { 446 return false; // Some types are not available on Windows 447 } 448 return (this.mode & S_IFMT) === property; 449}; 450 451function getStatsFromBinding(stats, offset = 0) { 452 if (isBigUint64Array(stats)) { 453 return new BigIntStats( 454 stats[0 + offset], stats[1 + offset], stats[2 + offset], 455 stats[3 + offset], stats[4 + offset], stats[5 + offset], 456 stats[6 + offset], stats[7 + offset], stats[8 + offset], 457 stats[9 + offset], 458 nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]), 459 nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]), 460 nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]), 461 nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]) 462 ); 463 } 464 return new Stats( 465 stats[0 + offset], stats[1 + offset], stats[2 + offset], 466 stats[3 + offset], stats[4 + offset], stats[5 + offset], 467 stats[6 + offset], stats[7 + offset], stats[8 + offset], 468 stats[9 + offset], 469 msFromTimeSpec(stats[10 + offset], stats[11 + offset]), 470 msFromTimeSpec(stats[12 + offset], stats[13 + offset]), 471 msFromTimeSpec(stats[14 + offset], stats[15 + offset]), 472 msFromTimeSpec(stats[16 + offset], stats[17 + offset]) 473 ); 474} 475 476function stringToFlags(flags) { 477 if (typeof flags === 'number') { 478 return flags; 479 } 480 481 switch (flags) { 482 case 'r' : return O_RDONLY; 483 case 'rs' : // Fall through. 484 case 'sr' : return O_RDONLY | O_SYNC; 485 case 'r+' : return O_RDWR; 486 case 'rs+' : // Fall through. 487 case 'sr+' : return O_RDWR | O_SYNC; 488 489 case 'w' : return O_TRUNC | O_CREAT | O_WRONLY; 490 case 'wx' : // Fall through. 491 case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL; 492 493 case 'w+' : return O_TRUNC | O_CREAT | O_RDWR; 494 case 'wx+': // Fall through. 495 case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL; 496 497 case 'a' : return O_APPEND | O_CREAT | O_WRONLY; 498 case 'ax' : // Fall through. 499 case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL; 500 case 'as' : // Fall through. 501 case 'sa' : return O_APPEND | O_CREAT | O_WRONLY | O_SYNC; 502 503 case 'a+' : return O_APPEND | O_CREAT | O_RDWR; 504 case 'ax+': // Fall through. 505 case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL; 506 case 'as+': // Fall through. 507 case 'sa+': return O_APPEND | O_CREAT | O_RDWR | O_SYNC; 508 } 509 510 throw new ERR_INVALID_OPT_VALUE('flags', flags); 511} 512 513const stringToSymlinkType = hideStackFrames((type) => { 514 let flags = 0; 515 if (typeof type === 'string') { 516 switch (type) { 517 case 'dir': 518 flags |= UV_FS_SYMLINK_DIR; 519 break; 520 case 'junction': 521 flags |= UV_FS_SYMLINK_JUNCTION; 522 break; 523 case 'file': 524 break; 525 default: 526 throw new ERR_FS_INVALID_SYMLINK_TYPE(type); 527 } 528 } 529 return flags; 530}); 531 532// converts Date or number to a fractional UNIX timestamp 533function toUnixTimestamp(time, name = 'time') { 534 // eslint-disable-next-line eqeqeq 535 if (typeof time === 'string' && +time == time) { 536 return +time; 537 } 538 if (NumberIsFinite(time)) { 539 if (time < 0) { 540 return DateNow() / 1000; 541 } 542 return time; 543 } 544 if (isDate(time)) { 545 // Convert to 123.456 UNIX timestamp 546 return time.getTime() / 1000; 547 } 548 throw new ERR_INVALID_ARG_TYPE(name, ['Date', 'Time in seconds'], time); 549} 550 551const validateOffsetLengthRead = hideStackFrames( 552 (offset, length, bufferLength) => { 553 if (offset < 0) { 554 throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset); 555 } 556 if (length < 0) { 557 throw new ERR_OUT_OF_RANGE('length', '>= 0', length); 558 } 559 if (offset + length > bufferLength) { 560 throw new ERR_OUT_OF_RANGE('length', 561 `<= ${bufferLength - offset}`, length); 562 } 563 } 564); 565 566const validateOffsetLengthWrite = hideStackFrames( 567 (offset, length, byteLength) => { 568 if (offset > byteLength) { 569 throw new ERR_OUT_OF_RANGE('offset', `<= ${byteLength}`, offset); 570 } 571 572 const max = byteLength > kMaxLength ? kMaxLength : byteLength; 573 if (length > max - offset) { 574 throw new ERR_OUT_OF_RANGE('length', `<= ${max - offset}`, length); 575 } 576 } 577); 578 579const validatePath = hideStackFrames((path, propName = 'path') => { 580 if (typeof path !== 'string' && !isUint8Array(path)) { 581 throw new ERR_INVALID_ARG_TYPE(propName, ['string', 'Buffer', 'URL'], path); 582 } 583 584 const err = nullCheck(path, propName, false); 585 586 if (err !== undefined) { 587 throw err; 588 } 589}); 590 591const getValidatedPath = hideStackFrames((fileURLOrPath, propName = 'path') => { 592 const path = toPathIfFileURL(fileURLOrPath); 593 validatePath(path, propName); 594 return path; 595}); 596 597const validateBufferArray = hideStackFrames((buffers, propName = 'buffers') => { 598 if (!ArrayIsArray(buffers)) 599 throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); 600 601 for (let i = 0; i < buffers.length; i++) { 602 if (!isArrayBufferView(buffers[i])) 603 throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers); 604 } 605 606 return buffers; 607}); 608 609let nonPortableTemplateWarn = true; 610 611function warnOnNonPortableTemplate(template) { 612 // Template strings passed to the mkdtemp() family of functions should not 613 // end with 'X' because they are handled inconsistently across platforms. 614 if (nonPortableTemplateWarn && template.endsWith('X')) { 615 process.emitWarning('mkdtemp() templates ending with X are not portable. ' + 616 'For details see: https://nodejs.org/api/fs.html'); 617 nonPortableTemplateWarn = false; 618 } 619} 620 621const defaultRmdirOptions = { 622 retryDelay: 100, 623 maxRetries: 0, 624 recursive: false, 625}; 626 627const validateRmdirOptions = hideStackFrames((options) => { 628 if (options === undefined) 629 return defaultRmdirOptions; 630 if (options === null || typeof options !== 'object') 631 throw new ERR_INVALID_ARG_TYPE('options', 'object', options); 632 633 options = { ...defaultRmdirOptions, ...options }; 634 635 if (typeof options.recursive !== 'boolean') 636 throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', options.recursive); 637 638 validateInt32(options.retryDelay, 'retryDelay', 0); 639 validateUint32(options.maxRetries, 'maxRetries'); 640 641 return options; 642}); 643 644 645module.exports = { 646 assertEncoding, 647 BigIntStats, // for testing 648 copyObject, 649 Dirent, 650 getDirent, 651 getDirents, 652 getOptions, 653 getValidatedPath, 654 handleErrorFromBinding, 655 nullCheck, 656 preprocessSymlinkDestination, 657 realpathCacheKey: Symbol('realpathCacheKey'), 658 getStatsFromBinding, 659 stringToFlags, 660 stringToSymlinkType, 661 Stats, 662 toUnixTimestamp, 663 validateBufferArray, 664 validateOffsetLengthRead, 665 validateOffsetLengthWrite, 666 validatePath, 667 validateRmdirOptions, 668 warnOnNonPortableTemplate 669}; 670