xref: /third_party/node/lib/internal/fs/utils.js (revision 1cb0ef41)
1'use strict';
2
3const {
4  ArrayIsArray,
5  BigInt,
6  Date,
7  DateNow,
8  DatePrototypeGetTime,
9  ErrorCaptureStackTrace,
10  FunctionPrototypeCall,
11  Number,
12  NumberIsFinite,
13  MathMin,
14  MathRound,
15  ObjectIs,
16  ObjectPrototypeHasOwnProperty,
17  ObjectSetPrototypeOf,
18  ReflectApply,
19  ReflectOwnKeys,
20  RegExpPrototypeSymbolReplace,
21  StringPrototypeEndsWith,
22  StringPrototypeIncludes,
23  Symbol,
24  TypedArrayPrototypeAt,
25  TypedArrayPrototypeIncludes,
26} = primordials;
27
28const { Buffer } = require('buffer');
29const {
30  codes: {
31    ERR_FS_EISDIR,
32    ERR_FS_INVALID_SYMLINK_TYPE,
33    ERR_INCOMPATIBLE_OPTION_PAIR,
34    ERR_INVALID_ARG_TYPE,
35    ERR_INVALID_ARG_VALUE,
36    ERR_OUT_OF_RANGE,
37  },
38  hideStackFrames,
39  uvException,
40} = require('internal/errors');
41const {
42  isArrayBufferView,
43  isBigInt64Array,
44  isDate,
45  isUint8Array,
46} = require('internal/util/types');
47const {
48  kEmptyObject,
49  once,
50} = require('internal/util');
51const { toPathIfFileURL } = require('internal/url');
52const {
53  validateAbortSignal,
54  validateBoolean,
55  validateFunction,
56  validateInt32,
57  validateInteger,
58  validateObject,
59  validateUint32,
60} = require('internal/validators');
61const pathModule = require('path');
62const kType = Symbol('type');
63const kStats = Symbol('stats');
64const assert = require('internal/assert');
65
66const {
67  fs: {
68    F_OK = 0,
69    W_OK = 0,
70    R_OK = 0,
71    X_OK = 0,
72    COPYFILE_EXCL,
73    COPYFILE_FICLONE,
74    COPYFILE_FICLONE_FORCE,
75    O_APPEND,
76    O_CREAT,
77    O_EXCL,
78    O_RDONLY,
79    O_RDWR,
80    O_SYNC,
81    O_TRUNC,
82    O_WRONLY,
83    S_IFBLK,
84    S_IFCHR,
85    S_IFDIR,
86    S_IFIFO,
87    S_IFLNK,
88    S_IFMT,
89    S_IFREG,
90    S_IFSOCK,
91    UV_FS_SYMLINK_DIR,
92    UV_FS_SYMLINK_JUNCTION,
93    UV_DIRENT_UNKNOWN,
94    UV_DIRENT_FILE,
95    UV_DIRENT_DIR,
96    UV_DIRENT_LINK,
97    UV_DIRENT_FIFO,
98    UV_DIRENT_SOCKET,
99    UV_DIRENT_CHAR,
100    UV_DIRENT_BLOCK,
101  },
102  os: {
103    errno: {
104      EISDIR,
105    },
106  },
107} = internalBinding('constants');
108
109// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
110// available on specific systems. They can be used in combination as well
111// (F_OK | R_OK | W_OK | X_OK).
112const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK);
113const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
114
115const kDefaultCopyMode = 0;
116// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
117// COPYFILE_FICLONE_FORCE. They can be used in combination as well
118// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
119const kMinimumCopyMode = MathMin(
120  kDefaultCopyMode,
121  COPYFILE_EXCL,
122  COPYFILE_FICLONE,
123  COPYFILE_FICLONE_FORCE,
124);
125const kMaximumCopyMode = COPYFILE_EXCL |
126                         COPYFILE_FICLONE |
127                         COPYFILE_FICLONE_FORCE;
128
129// Most platforms don't allow reads or writes >= 2 GiB.
130// See https://github.com/libuv/libuv/pull/1501.
131const kIoMaxLength = 2 ** 31 - 1;
132
133// Use 64kb in case the file type is not a regular file and thus do not know the
134// actual file size. Increasing the value further results in more frequent over
135// allocation for small files and consumes CPU time and memory that should be
136// used else wise.
137// Use up to 512kb per read otherwise to partition reading big files to prevent
138// blocking other threads in case the available threads are all in use.
139const kReadFileUnknownBufferLength = 64 * 1024;
140const kReadFileBufferLength = 512 * 1024;
141
142const kWriteFileMaxChunkSize = 512 * 1024;
143
144const kMaxUserId = 2 ** 32 - 1;
145
146const isWindows = process.platform === 'win32';
147
148let fs;
149function lazyLoadFs() {
150  if (!fs) {
151    fs = require('fs');
152  }
153  return fs;
154}
155
156function assertEncoding(encoding) {
157  if (encoding && !Buffer.isEncoding(encoding)) {
158    const reason = 'is invalid encoding';
159    throw new ERR_INVALID_ARG_VALUE(encoding, 'encoding', reason);
160  }
161}
162
163class Dirent {
164  constructor(name, type, path, filepath = path && join(path, name)) {
165    this.name = name;
166    this.parentPath = path;
167    this.path = filepath;
168    this[kType] = type;
169  }
170
171  isDirectory() {
172    return this[kType] === UV_DIRENT_DIR;
173  }
174
175  isFile() {
176    return this[kType] === UV_DIRENT_FILE;
177  }
178
179  isBlockDevice() {
180    return this[kType] === UV_DIRENT_BLOCK;
181  }
182
183  isCharacterDevice() {
184    return this[kType] === UV_DIRENT_CHAR;
185  }
186
187  isSymbolicLink() {
188    return this[kType] === UV_DIRENT_LINK;
189  }
190
191  isFIFO() {
192    return this[kType] === UV_DIRENT_FIFO;
193  }
194
195  isSocket() {
196    return this[kType] === UV_DIRENT_SOCKET;
197  }
198}
199
200class DirentFromStats extends Dirent {
201  constructor(name, stats, path, filepath) {
202    super(name, null, path, filepath);
203    this[kStats] = stats;
204  }
205}
206
207for (const name of ReflectOwnKeys(Dirent.prototype)) {
208  if (name === 'constructor') {
209    continue;
210  }
211  DirentFromStats.prototype[name] = function() {
212    return this[kStats][name]();
213  };
214}
215
216function copyObject(source) {
217  const target = {};
218  for (const key in source)
219    target[key] = source[key];
220  return target;
221}
222
223const bufferSep = Buffer.from(pathModule.sep);
224
225function join(path, name) {
226  if ((typeof path === 'string' || isUint8Array(path)) &&
227      name === undefined) {
228    return path;
229  }
230
231  if (typeof path === 'string' && isUint8Array(name)) {
232    const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep));
233    return Buffer.concat([pathBuffer, name]);
234  }
235
236  if (typeof path === 'string' && typeof name === 'string') {
237    return pathModule.join(path, name);
238  }
239
240  if (isUint8Array(path) && isUint8Array(name)) {
241    return Buffer.concat([path, bufferSep, name]);
242  }
243
244  throw new ERR_INVALID_ARG_TYPE(
245    'path', ['string', 'Buffer'], path);
246}
247
248function getDirents(path, { 0: names, 1: types }, callback) {
249  let i;
250  if (typeof callback === 'function') {
251    const len = names.length;
252    let toFinish = 0;
253    callback = once(callback);
254    for (i = 0; i < len; i++) {
255      const type = types[i];
256      if (type === UV_DIRENT_UNKNOWN) {
257        const name = names[i];
258        const idx = i;
259        toFinish++;
260        let filepath;
261        try {
262          filepath = join(path, name);
263        } catch (err) {
264          callback(err);
265          return;
266        }
267        lazyLoadFs().lstat(filepath, (err, stats) => {
268          if (err) {
269            callback(err);
270            return;
271          }
272          names[idx] = new DirentFromStats(name, stats, path, filepath);
273          if (--toFinish === 0) {
274            callback(null, names);
275          }
276        });
277      } else {
278        names[i] = new Dirent(names[i], types[i], path);
279      }
280    }
281    if (toFinish === 0) {
282      callback(null, names);
283    }
284  } else {
285    const len = names.length;
286    for (i = 0; i < len; i++) {
287      names[i] = getDirent(path, names[i], types[i]);
288    }
289    return names;
290  }
291}
292
293function getDirent(path, name, type, callback) {
294  if (typeof callback === 'function') {
295    if (type === UV_DIRENT_UNKNOWN) {
296      let filepath;
297      try {
298        filepath = join(path, name);
299      } catch (err) {
300        callback(err);
301        return;
302      }
303      lazyLoadFs().lstat(filepath, (err, stats) => {
304        if (err) {
305          callback(err);
306          return;
307        }
308        callback(null, new DirentFromStats(name, stats, path, filepath));
309      });
310    } else {
311      callback(null, new Dirent(name, type, path));
312    }
313  } else if (type === UV_DIRENT_UNKNOWN) {
314    const filepath = join(path, name);
315    const stats = lazyLoadFs().lstatSync(filepath);
316    // callback === true: Quirk to not introduce a breaking change.
317    return new DirentFromStats(name, stats, path, callback === true ? filepath : path);
318  } else if (callback === true) {
319    // callback === true: Quirk to not introduce a breaking change.
320    return new Dirent(name, type, path);
321  } else {
322    return new Dirent(name, type, path, path);
323  }
324}
325
326function getOptions(options, defaultOptions = kEmptyObject) {
327  if (options == null || typeof options === 'function') {
328    return defaultOptions;
329  }
330
331  if (typeof options === 'string') {
332    defaultOptions = { ...defaultOptions };
333    defaultOptions.encoding = options;
334    options = defaultOptions;
335  } else if (typeof options !== 'object') {
336    throw new ERR_INVALID_ARG_TYPE('options', ['string', 'Object'], options);
337  }
338
339  if (options.encoding !== 'buffer')
340    assertEncoding(options.encoding);
341
342  if (options.signal !== undefined) {
343    validateAbortSignal(options.signal, 'options.signal');
344  }
345
346  return options;
347}
348
349/**
350 * @param {InternalFSBinding.FSSyncContext} ctx
351 */
352function handleErrorFromBinding(ctx) {
353  if (ctx.errno !== undefined) {  // libuv error numbers
354    const err = uvException(ctx);
355    ErrorCaptureStackTrace(err, handleErrorFromBinding);
356    throw err;
357  }
358  if (ctx.error !== undefined) {  // Errors created in C++ land.
359    // TODO(joyeecheung): currently, ctx.error are encoding errors
360    // usually caused by memory problems. We need to figure out proper error
361    // code(s) for this.
362    ErrorCaptureStackTrace(ctx.error, handleErrorFromBinding);
363    throw ctx.error;
364  }
365}
366
367// Check if the path contains null types if it is a string nor Uint8Array,
368// otherwise return silently.
369const nullCheck = hideStackFrames((path, propName, throwError = true) => {
370  const pathIsString = typeof path === 'string';
371  const pathIsUint8Array = isUint8Array(path);
372
373  // We can only perform meaningful checks on strings and Uint8Arrays.
374  if ((!pathIsString && !pathIsUint8Array) ||
375      (pathIsString && !StringPrototypeIncludes(path, '\u0000')) ||
376      (pathIsUint8Array && !TypedArrayPrototypeIncludes(path, 0))) {
377    return;
378  }
379
380  const err = new ERR_INVALID_ARG_VALUE(
381    propName,
382    path,
383    'must be a string or Uint8Array without null bytes',
384  );
385  if (throwError) {
386    throw err;
387  }
388  return err;
389});
390
391function preprocessSymlinkDestination(path, type, linkPath) {
392  if (!isWindows) {
393    // No preprocessing is needed on Unix.
394    return path;
395  }
396  path = '' + path;
397  if (type === 'junction') {
398    // Junctions paths need to be absolute and \\?\-prefixed.
399    // A relative target is relative to the link's parent directory.
400    path = pathModule.resolve(linkPath, '..', path);
401    return pathModule.toNamespacedPath(path);
402  }
403  if (pathModule.isAbsolute(path)) {
404    // If the path is absolute, use the \\?\-prefix to enable long filenames
405    return pathModule.toNamespacedPath(path);
406  }
407  // Windows symlinks don't tolerate forward slashes.
408  return RegExpPrototypeSymbolReplace(/\//g, path, '\\');
409}
410
411// Constructor for file stats.
412function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize,
413                   ino, size, blocks) {
414  this.dev = dev;
415  this.mode = mode;
416  this.nlink = nlink;
417  this.uid = uid;
418  this.gid = gid;
419  this.rdev = rdev;
420  this.blksize = blksize;
421  this.ino = ino;
422  this.size = size;
423  this.blocks = blocks;
424}
425
426StatsBase.prototype.isDirectory = function() {
427  return this._checkModeProperty(S_IFDIR);
428};
429
430StatsBase.prototype.isFile = function() {
431  return this._checkModeProperty(S_IFREG);
432};
433
434StatsBase.prototype.isBlockDevice = function() {
435  return this._checkModeProperty(S_IFBLK);
436};
437
438StatsBase.prototype.isCharacterDevice = function() {
439  return this._checkModeProperty(S_IFCHR);
440};
441
442StatsBase.prototype.isSymbolicLink = function() {
443  return this._checkModeProperty(S_IFLNK);
444};
445
446StatsBase.prototype.isFIFO = function() {
447  return this._checkModeProperty(S_IFIFO);
448};
449
450StatsBase.prototype.isSocket = function() {
451  return this._checkModeProperty(S_IFSOCK);
452};
453
454const kNsPerMsBigInt = 10n ** 6n;
455const kNsPerSecBigInt = 10n ** 9n;
456const kMsPerSec = 10 ** 3;
457const kNsPerMs = 10 ** 6;
458function msFromTimeSpec(sec, nsec) {
459  return sec * kMsPerSec + nsec / kNsPerMs;
460}
461
462function nsFromTimeSpecBigInt(sec, nsec) {
463  return sec * kNsPerSecBigInt + nsec;
464}
465
466// The Date constructor performs Math.floor() on the absolute value
467// of the timestamp: https://tc39.es/ecma262/#sec-timeclip
468// Since there may be a precision loss when the timestamp is
469// converted to a floating point number, we manually round
470// the timestamp here before passing it to Date().
471// Refs: https://github.com/nodejs/node/pull/12607
472// Refs: https://github.com/nodejs/node/pull/43714
473function dateFromMs(ms) {
474  // Coercing to number, ms can be bigint
475  return new Date(MathRound(Number(ms)));
476}
477
478function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
479                     ino, size, blocks,
480                     atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
481  ReflectApply(StatsBase, this, [dev, mode, nlink, uid, gid, rdev, blksize,
482                                 ino, size, blocks]);
483
484  this.atimeMs = atimeNs / kNsPerMsBigInt;
485  this.mtimeMs = mtimeNs / kNsPerMsBigInt;
486  this.ctimeMs = ctimeNs / kNsPerMsBigInt;
487  this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
488  this.atimeNs = atimeNs;
489  this.mtimeNs = mtimeNs;
490  this.ctimeNs = ctimeNs;
491  this.birthtimeNs = birthtimeNs;
492  this.atime = dateFromMs(this.atimeMs);
493  this.mtime = dateFromMs(this.mtimeMs);
494  this.ctime = dateFromMs(this.ctimeMs);
495  this.birthtime = dateFromMs(this.birthtimeMs);
496}
497
498ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
499ObjectSetPrototypeOf(BigIntStats, StatsBase);
500
501BigIntStats.prototype._checkModeProperty = function(property) {
502  if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
503    property === S_IFSOCK)) {
504    return false;  // Some types are not available on Windows
505  }
506  return (this.mode & BigInt(S_IFMT)) === BigInt(property);
507};
508
509function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
510               ino, size, blocks,
511               atimeMs, mtimeMs, ctimeMs, birthtimeMs) {
512  FunctionPrototypeCall(StatsBase, this, dev, mode, nlink, uid, gid, rdev,
513                        blksize, ino, size, blocks);
514  this.atimeMs = atimeMs;
515  this.mtimeMs = mtimeMs;
516  this.ctimeMs = ctimeMs;
517  this.birthtimeMs = birthtimeMs;
518  this.atime = dateFromMs(atimeMs);
519  this.mtime = dateFromMs(mtimeMs);
520  this.ctime = dateFromMs(ctimeMs);
521  this.birthtime = dateFromMs(birthtimeMs);
522}
523
524ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype);
525ObjectSetPrototypeOf(Stats, StatsBase);
526
527// HACK: Workaround for https://github.com/standard-things/esm/issues/821.
528// TODO(ronag): Remove this as soon as `esm` publishes a fixed version.
529Stats.prototype.isFile = StatsBase.prototype.isFile;
530
531Stats.prototype._checkModeProperty = function(property) {
532  if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
533    property === S_IFSOCK)) {
534    return false;  // Some types are not available on Windows
535  }
536  return (this.mode & S_IFMT) === property;
537};
538
539/**
540 * @param {Float64Array | BigInt64Array} stats
541 * @param {number} offset
542 * @returns {BigIntStats | Stats}
543 */
544function getStatsFromBinding(stats, offset = 0) {
545  if (isBigInt64Array(stats)) {
546    return new BigIntStats(
547      stats[0 + offset], stats[1 + offset], stats[2 + offset],
548      stats[3 + offset], stats[4 + offset], stats[5 + offset],
549      stats[6 + offset], stats[7 + offset], stats[8 + offset],
550      stats[9 + offset],
551      nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
552      nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
553      nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
554      nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]),
555    );
556  }
557  return new Stats(
558    stats[0 + offset], stats[1 + offset], stats[2 + offset],
559    stats[3 + offset], stats[4 + offset], stats[5 + offset],
560    stats[6 + offset], stats[7 + offset], stats[8 + offset],
561    stats[9 + offset],
562    msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
563    msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
564    msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
565    msFromTimeSpec(stats[16 + offset], stats[17 + offset]),
566  );
567}
568
569class StatFs {
570  constructor(type, bsize, blocks, bfree, bavail, files, ffree) {
571    this.type = type;
572    this.bsize = bsize;
573    this.blocks = blocks;
574    this.bfree = bfree;
575    this.bavail = bavail;
576    this.files = files;
577    this.ffree = ffree;
578  }
579}
580
581function getStatFsFromBinding(stats) {
582  return new StatFs(
583    stats[0], stats[1], stats[2], stats[3], stats[4], stats[5], stats[6],
584  );
585}
586
587function stringToFlags(flags, name = 'flags') {
588  if (typeof flags === 'number') {
589    validateInt32(flags, name);
590    return flags;
591  }
592
593  if (flags == null) {
594    return O_RDONLY;
595  }
596
597  switch (flags) {
598    case 'r' : return O_RDONLY;
599    case 'rs' : // Fall through.
600    case 'sr' : return O_RDONLY | O_SYNC;
601    case 'r+' : return O_RDWR;
602    case 'rs+' : // Fall through.
603    case 'sr+' : return O_RDWR | O_SYNC;
604
605    case 'w' : return O_TRUNC | O_CREAT | O_WRONLY;
606    case 'wx' : // Fall through.
607    case 'xw' : return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
608
609    case 'w+' : return O_TRUNC | O_CREAT | O_RDWR;
610    case 'wx+': // Fall through.
611    case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
612
613    case 'a' : return O_APPEND | O_CREAT | O_WRONLY;
614    case 'ax' : // Fall through.
615    case 'xa' : return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
616    case 'as' : // Fall through.
617    case 'sa' : return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
618
619    case 'a+' : return O_APPEND | O_CREAT | O_RDWR;
620    case 'ax+': // Fall through.
621    case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
622    case 'as+': // Fall through.
623    case 'sa+': return O_APPEND | O_CREAT | O_RDWR | O_SYNC;
624  }
625
626  throw new ERR_INVALID_ARG_VALUE('flags', flags);
627}
628
629const stringToSymlinkType = hideStackFrames((type) => {
630  let flags = 0;
631  if (typeof type === 'string') {
632    switch (type) {
633      case 'dir':
634        flags |= UV_FS_SYMLINK_DIR;
635        break;
636      case 'junction':
637        flags |= UV_FS_SYMLINK_JUNCTION;
638        break;
639      case 'file':
640        break;
641      default:
642        throw new ERR_FS_INVALID_SYMLINK_TYPE(type);
643    }
644  }
645  return flags;
646});
647
648// converts Date or number to a fractional UNIX timestamp
649function toUnixTimestamp(time, name = 'time') {
650  // eslint-disable-next-line eqeqeq
651  if (typeof time === 'string' && +time == time) {
652    return +time;
653  }
654  if (NumberIsFinite(time)) {
655    if (time < 0) {
656      return DateNow() / 1000;
657    }
658    return time;
659  }
660  if (isDate(time)) {
661    // Convert to 123.456 UNIX timestamp
662    return DatePrototypeGetTime(time) / 1000;
663  }
664  throw new ERR_INVALID_ARG_TYPE(name, ['Date', 'Time in seconds'], time);
665}
666
667const validateOffsetLengthRead = hideStackFrames(
668  (offset, length, bufferLength) => {
669    if (offset < 0) {
670      throw new ERR_OUT_OF_RANGE('offset', '>= 0', offset);
671    }
672    if (length < 0) {
673      throw new ERR_OUT_OF_RANGE('length', '>= 0', length);
674    }
675    if (offset + length > bufferLength) {
676      throw new ERR_OUT_OF_RANGE('length',
677                                 `<= ${bufferLength - offset}`, length);
678    }
679  },
680);
681
682const validateOffsetLengthWrite = hideStackFrames(
683  (offset, length, byteLength) => {
684    if (offset > byteLength) {
685      throw new ERR_OUT_OF_RANGE('offset', `<= ${byteLength}`, offset);
686    }
687
688    if (length > byteLength - offset) {
689      throw new ERR_OUT_OF_RANGE('length', `<= ${byteLength - offset}`, length);
690    }
691
692    if (length < 0) {
693      throw new ERR_OUT_OF_RANGE('length', '>= 0', length);
694    }
695
696    validateInt32(length, 'length', 0);
697  },
698);
699
700const validatePath = hideStackFrames((path, propName = 'path') => {
701  if (typeof path !== 'string' && !isUint8Array(path)) {
702    throw new ERR_INVALID_ARG_TYPE(propName, ['string', 'Buffer', 'URL'], path);
703  }
704
705  const err = nullCheck(path, propName, false);
706
707  if (err !== undefined) {
708    throw err;
709  }
710});
711
712const getValidatedPath = hideStackFrames((fileURLOrPath, propName = 'path') => {
713  const path = toPathIfFileURL(fileURLOrPath);
714  validatePath(path, propName);
715  return path;
716});
717
718const getValidatedFd = hideStackFrames((fd, propName = 'fd') => {
719  if (ObjectIs(fd, -0)) {
720    return 0;
721  }
722
723  validateInt32(fd, propName, 0);
724
725  return fd;
726});
727
728const validateBufferArray = hideStackFrames((buffers, propName = 'buffers') => {
729  if (!ArrayIsArray(buffers))
730    throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers);
731
732  for (let i = 0; i < buffers.length; i++) {
733    if (!isArrayBufferView(buffers[i]))
734      throw new ERR_INVALID_ARG_TYPE(propName, 'ArrayBufferView[]', buffers);
735  }
736
737  return buffers;
738});
739
740let nonPortableTemplateWarn = true;
741
742function warnOnNonPortableTemplate(template) {
743  // Template strings passed to the mkdtemp() family of functions should not
744  // end with 'X' because they are handled inconsistently across platforms.
745  if (nonPortableTemplateWarn &&
746    ((typeof template === 'string' && StringPrototypeEndsWith(template, 'X')) ||
747    (typeof template !== 'string' && TypedArrayPrototypeAt(template, -1) === 0x58))) {
748    process.emitWarning('mkdtemp() templates ending with X are not portable. ' +
749                        'For details see: https://nodejs.org/api/fs.html');
750    nonPortableTemplateWarn = false;
751  }
752}
753
754const defaultCpOptions = {
755  dereference: false,
756  errorOnExist: false,
757  filter: undefined,
758  force: true,
759  preserveTimestamps: false,
760  recursive: false,
761  verbatimSymlinks: false,
762};
763
764const defaultRmOptions = {
765  recursive: false,
766  force: false,
767  retryDelay: 100,
768  maxRetries: 0,
769};
770
771const defaultRmdirOptions = {
772  retryDelay: 100,
773  maxRetries: 0,
774  recursive: false,
775};
776
777const validateCpOptions = hideStackFrames((options) => {
778  if (options === undefined)
779    return { ...defaultCpOptions };
780  validateObject(options, 'options');
781  options = { ...defaultCpOptions, ...options };
782  validateBoolean(options.dereference, 'options.dereference');
783  validateBoolean(options.errorOnExist, 'options.errorOnExist');
784  validateBoolean(options.force, 'options.force');
785  validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
786  validateBoolean(options.recursive, 'options.recursive');
787  validateBoolean(options.verbatimSymlinks, 'options.verbatimSymlinks');
788  options.mode = getValidMode(options.mode, 'copyFile');
789  if (options.dereference === true && options.verbatimSymlinks === true) {
790    throw new ERR_INCOMPATIBLE_OPTION_PAIR('dereference', 'verbatimSymlinks');
791  }
792  if (options.filter !== undefined) {
793    validateFunction(options.filter, 'options.filter');
794  }
795  return options;
796});
797
798const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => {
799  options = validateRmdirOptions(options, defaultRmOptions);
800  validateBoolean(options.force, 'options.force');
801
802  lazyLoadFs().lstat(path, (err, stats) => {
803    if (err) {
804      if (options.force && err.code === 'ENOENT') {
805        return cb(null, options);
806      }
807      return cb(err, options);
808    }
809
810    if (expectDir && !stats.isDirectory()) {
811      return cb(false);
812    }
813
814    if (stats.isDirectory() && !options.recursive) {
815      return cb(new ERR_FS_EISDIR({
816        code: 'EISDIR',
817        message: 'is a directory',
818        path,
819        syscall: 'rm',
820        errno: EISDIR,
821      }));
822    }
823    return cb(null, options);
824  });
825});
826
827const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => {
828  options = validateRmdirOptions(options, defaultRmOptions);
829  validateBoolean(options.force, 'options.force');
830
831  if (!options.force || expectDir || !options.recursive) {
832    const isDirectory = lazyLoadFs()
833      .lstatSync(path, { throwIfNoEntry: !options.force })?.isDirectory();
834
835    if (expectDir && !isDirectory) {
836      return false;
837    }
838
839    if (isDirectory && !options.recursive) {
840      throw new ERR_FS_EISDIR({
841        code: 'EISDIR',
842        message: 'is a directory',
843        path,
844        syscall: 'rm',
845        errno: EISDIR,
846      });
847    }
848  }
849
850  return options;
851});
852
853let recursiveRmdirWarned = process.noDeprecation;
854function emitRecursiveRmdirWarning() {
855  if (!recursiveRmdirWarned) {
856    process.emitWarning(
857      'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
858      'will be removed. Use fs.rm(path, { recursive: true }) instead',
859      'DeprecationWarning',
860      'DEP0147',
861    );
862    recursiveRmdirWarned = true;
863  }
864}
865
866const validateRmdirOptions = hideStackFrames(
867  (options, defaults = defaultRmdirOptions) => {
868    if (options === undefined)
869      return defaults;
870    validateObject(options, 'options');
871
872    options = { ...defaults, ...options };
873
874    validateBoolean(options.recursive, 'options.recursive');
875    validateInt32(options.retryDelay, 'options.retryDelay', 0);
876    validateUint32(options.maxRetries, 'options.maxRetries');
877
878    return options;
879  });
880
881const getValidMode = hideStackFrames((mode, type) => {
882  let min = kMinimumAccessMode;
883  let max = kMaximumAccessMode;
884  let def = F_OK;
885  if (type === 'copyFile') {
886    min = kMinimumCopyMode;
887    max = kMaximumCopyMode;
888    def = mode || kDefaultCopyMode;
889  } else {
890    assert(type === 'access');
891  }
892  if (mode == null) {
893    return def;
894  }
895  validateInteger(mode, 'mode', min, max);
896  return mode;
897});
898
899const validateStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
900  if (typeof buffer === 'string') {
901    return;
902  }
903
904  if (
905    typeof buffer === 'object' &&
906    buffer !== null &&
907    typeof buffer.toString === 'function' &&
908    ObjectPrototypeHasOwnProperty(buffer, 'toString')
909  ) {
910    return;
911  }
912
913  throw new ERR_INVALID_ARG_TYPE(
914    name,
915    ['string', 'Buffer', 'TypedArray', 'DataView'],
916    buffer,
917  );
918});
919
920const validatePrimitiveStringAfterArrayBufferView = hideStackFrames((buffer, name) => {
921  if (typeof buffer !== 'string') {
922    throw new ERR_INVALID_ARG_TYPE(
923      name,
924      ['string', 'Buffer', 'TypedArray', 'DataView'],
925      buffer,
926    );
927  }
928});
929
930const validatePosition = hideStackFrames((position, name) => {
931  if (typeof position === 'number') {
932    validateInteger(position, name);
933  } else if (typeof position === 'bigint') {
934    if (!(position >= -(2n ** 63n) && position <= 2n ** 63n - 1n)) {
935      throw new ERR_OUT_OF_RANGE(name,
936                                 `>= ${-(2n ** 63n)} && <= ${2n ** 63n - 1n}`,
937                                 position);
938    }
939  } else {
940    throw new ERR_INVALID_ARG_TYPE(name, ['integer', 'bigint'], position);
941  }
942});
943
944module.exports = {
945  constants: {
946    kIoMaxLength,
947    kMaxUserId,
948    kReadFileBufferLength,
949    kReadFileUnknownBufferLength,
950    kWriteFileMaxChunkSize,
951  },
952  assertEncoding,
953  BigIntStats,  // for testing
954  copyObject,
955  Dirent,
956  emitRecursiveRmdirWarning,
957  getDirent,
958  getDirents,
959  getOptions,
960  getValidatedFd,
961  getValidatedPath,
962  getValidMode,
963  handleErrorFromBinding,
964  nullCheck,
965  preprocessSymlinkDestination,
966  realpathCacheKey: Symbol('realpathCacheKey'),
967  getStatFsFromBinding,
968  getStatsFromBinding,
969  stringToFlags,
970  stringToSymlinkType,
971  Stats,
972  toUnixTimestamp,
973  validateBufferArray,
974  validateCpOptions,
975  validateOffsetLengthRead,
976  validateOffsetLengthWrite,
977  validatePath,
978  validatePosition,
979  validateRmOptions,
980  validateRmOptionsSync,
981  validateRmdirOptions,
982  validateStringAfterArrayBufferView,
983  validatePrimitiveStringAfterArrayBufferView,
984  warnOnNonPortableTemplate,
985};
986