• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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