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