• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypePush,
5  Error,
6  MathMax,
7  MathMin,
8  NumberIsSafeInteger,
9  Promise,
10  PromisePrototypeFinally,
11  PromiseResolve,
12  Symbol,
13  Uint8Array,
14} = primordials;
15
16const {
17  F_OK,
18  O_SYMLINK,
19  O_WRONLY,
20  S_IFMT,
21  S_IFREG
22} = internalBinding('constants').fs;
23const binding = internalBinding('fs');
24const { Buffer } = require('buffer');
25
26const { AbortError, codes, hideStackFrames } = require('internal/errors');
27const {
28  ERR_FS_FILE_TOO_LARGE,
29  ERR_INVALID_ARG_TYPE,
30  ERR_INVALID_ARG_VALUE,
31  ERR_METHOD_NOT_IMPLEMENTED,
32} = codes;
33const { isArrayBufferView } = require('internal/util/types');
34const { rimrafPromises } = require('internal/fs/rimraf');
35const {
36  constants: {
37    kIoMaxLength,
38    kMaxUserId,
39    kReadFileBufferLength,
40    kReadFileUnknownBufferLength,
41    kWriteFileMaxChunkSize,
42  },
43  copyObject,
44  getDirents,
45  getOptions,
46  getStatsFromBinding,
47  getValidatedPath,
48  getValidMode,
49  nullCheck,
50  preprocessSymlinkDestination,
51  stringToFlags,
52  stringToSymlinkType,
53  toUnixTimestamp,
54  validateBufferArray,
55  validateOffsetLengthRead,
56  validateOffsetLengthWrite,
57  validateRmOptions,
58  validateRmdirOptions,
59  validatePrimitiveStringAfterArrayBufferView,
60  warnOnNonPortableTemplate,
61} = require('internal/fs/utils');
62const { opendir } = require('internal/fs/dir');
63const {
64  parseFileMode,
65  validateAbortSignal,
66  validateBuffer,
67  validateInteger,
68  validateString,
69} = require('internal/validators');
70const pathModule = require('path');
71const { promisify } = require('internal/util');
72const { watch } = require('internal/fs/watchers');
73const { isIterable } = require('internal/streams/utils');
74
75const kHandle = Symbol('kHandle');
76const kFd = Symbol('kFd');
77const kRefs = Symbol('kRefs');
78const kClosePromise = Symbol('kClosePromise');
79const kCloseResolve = Symbol('kCloseResolve');
80const kCloseReject = Symbol('kCloseReject');
81
82const { kUsePromises } = binding;
83const {
84  JSTransferable, kDeserialize, kTransfer, kTransferList
85} = require('internal/worker/js_transferable');
86
87const getDirectoryEntriesPromise = promisify(getDirents);
88const validateRmOptionsPromise = promisify(validateRmOptions);
89
90let DOMException;
91const lazyDOMException = hideStackFrames((message, name) => {
92  if (DOMException === undefined)
93    DOMException = internalBinding('messaging').DOMException;
94  return new DOMException(message, name);
95});
96
97class FileHandle extends JSTransferable {
98  constructor(filehandle) {
99    super();
100    this[kHandle] = filehandle;
101    this[kFd] = filehandle ? filehandle.fd : -1;
102
103    this[kRefs] = 1;
104    this[kClosePromise] = null;
105  }
106
107  getAsyncId() {
108    return this[kHandle].getAsyncId();
109  }
110
111  get fd() {
112    return this[kFd];
113  }
114
115  appendFile(data, options) {
116    return fsCall(writeFile, this, data, options);
117  }
118
119  chmod(mode) {
120    return fsCall(fchmod, this, mode);
121  }
122
123  chown(uid, gid) {
124    return fsCall(fchown, this, uid, gid);
125  }
126
127  datasync() {
128    return fsCall(fdatasync, this);
129  }
130
131  sync() {
132    return fsCall(fsync, this);
133  }
134
135  read(buffer, offset, length, position) {
136    return fsCall(read, this, buffer, offset, length, position);
137  }
138
139  readv(buffers, position) {
140    return fsCall(readv, this, buffers, position);
141  }
142
143  readFile(options) {
144    return fsCall(readFile, this, options);
145  }
146
147  stat(options) {
148    return fsCall(fstat, this, options);
149  }
150
151  truncate(len = 0) {
152    return fsCall(ftruncate, this, len);
153  }
154
155  utimes(atime, mtime) {
156    return fsCall(futimes, this, atime, mtime);
157  }
158
159  write(buffer, offset, length, position) {
160    return fsCall(write, this, buffer, offset, length, position);
161  }
162
163  writev(buffers, position) {
164    return fsCall(writev, this, buffers, position);
165  }
166
167  writeFile(data, options) {
168    return fsCall(writeFile, this, data, options);
169  }
170
171  close = () => {
172    if (this[kFd] === -1) {
173      return PromiseResolve();
174    }
175
176    if (this[kClosePromise]) {
177      return this[kClosePromise];
178    }
179
180    this[kRefs]--;
181    if (this[kRefs] === 0) {
182      this[kFd] = -1;
183      this[kClosePromise] = this[kHandle].close().finally(() => {
184        this[kClosePromise] = undefined;
185      });
186    } else {
187      this[kClosePromise] = new Promise((resolve, reject) => {
188        this[kCloseResolve] = resolve;
189        this[kCloseReject] = reject;
190      }).finally(() => {
191        this[kClosePromise] = undefined;
192        this[kCloseReject] = undefined;
193        this[kCloseResolve] = undefined;
194      });
195    }
196
197    return this[kClosePromise];
198  }
199
200  [kTransfer]() {
201    if (this[kClosePromise] || this[kRefs] > 1) {
202      const DOMException = internalBinding('messaging').DOMException;
203      throw new DOMException('Cannot transfer FileHandle while in use',
204                             'DataCloneError');
205    }
206
207    const handle = this[kHandle];
208    this[kFd] = -1;
209    this[kHandle] = null;
210    this[kRefs] = 0;
211
212    return {
213      data: { handle },
214      deserializeInfo: 'internal/fs/promises:FileHandle'
215    };
216  }
217
218  [kTransferList]() {
219    return [ this[kHandle] ];
220  }
221
222  [kDeserialize]({ handle }) {
223    this[kHandle] = handle;
224    this[kFd] = handle.fd;
225  }
226}
227
228async function fsCall(fn, handle, ...args) {
229  if (handle[kRefs] === undefined) {
230    throw new ERR_INVALID_ARG_TYPE('filehandle', 'FileHandle', handle);
231  }
232
233  if (handle.fd === -1) {
234    // eslint-disable-next-line no-restricted-syntax
235    const err = new Error('file closed');
236    err.code = 'EBADF';
237    err.syscall = fn.name;
238    throw err;
239  }
240
241  try {
242    handle[kRefs]++;
243    return await fn(handle, ...args);
244  } finally {
245    handle[kRefs]--;
246    if (handle[kRefs] === 0) {
247      handle[kFd] = -1;
248      handle[kHandle]
249        .close()
250        .then(handle[kCloseResolve], handle[kCloseReject]);
251    }
252  }
253}
254
255function checkAborted(signal) {
256  if (signal && signal.aborted)
257    throw new AbortError();
258}
259
260async function writeFileHandle(filehandle, data, signal, encoding) {
261  checkAborted(signal);
262  if (isCustomIterable(data)) {
263    for await (const buf of data) {
264      checkAborted(signal);
265      await write(
266        filehandle, buf, undefined,
267        isArrayBufferView(buf) ? buf.byteLength : encoding);
268      checkAborted(signal);
269    }
270    return;
271  }
272  data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
273  let remaining = data.byteLength;
274  if (remaining === 0) return;
275  do {
276    if (signal?.aborted) {
277      throw lazyDOMException('The operation was aborted', 'AbortError');
278    }
279    const { bytesWritten } =
280      await write(filehandle, data, 0,
281                  MathMin(kWriteFileMaxChunkSize, data.byteLength));
282    remaining -= bytesWritten;
283    data = new Uint8Array(
284      data.buffer,
285      data.byteOffset + bytesWritten,
286      data.byteLength - bytesWritten
287    );
288  } while (remaining > 0);
289}
290
291async function readFileHandle(filehandle, options) {
292  const signal = options && options.signal;
293
294  if (signal && signal.aborted) {
295    throw lazyDOMException('The operation was aborted', 'AbortError');
296  }
297  const statFields = await binding.fstat(filehandle.fd, false, kUsePromises);
298
299  if (signal && signal.aborted) {
300    throw lazyDOMException('The operation was aborted', 'AbortError');
301  }
302
303  let size;
304  if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
305    size = statFields[8/* size */];
306  } else {
307    size = 0;
308  }
309
310  if (size > kIoMaxLength)
311    throw new ERR_FS_FILE_TOO_LARGE(size);
312
313  let endOfFile = false;
314  let totalRead = 0;
315  const noSize = size === 0;
316  const buffers = [];
317  const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
318  do {
319    if (signal && signal.aborted) {
320      throw lazyDOMException('The operation was aborted', 'AbortError');
321    }
322    let buffer;
323    let offset;
324    let length;
325    if (noSize) {
326      buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
327      offset = 0;
328      length = kReadFileUnknownBufferLength;
329    } else {
330      buffer = fullBuffer;
331      offset = totalRead;
332      length = MathMin(size - totalRead, kReadFileBufferLength);
333    }
334
335    const bytesRead = (await binding.read(filehandle.fd, buffer, offset,
336                                          length, -1, kUsePromises)) || 0;
337    totalRead += bytesRead;
338    endOfFile = bytesRead === 0 || totalRead === size;
339    if (noSize && bytesRead > 0) {
340      const isBufferFull = bytesRead === kReadFileUnknownBufferLength;
341      const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead);
342      ArrayPrototypePush(buffers, chunkBuffer);
343    }
344  } while (!endOfFile);
345
346  let result;
347  if (size > 0) {
348    result = totalRead === size ? fullBuffer : fullBuffer.slice(0, totalRead);
349  } else {
350    result = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers,
351                                                               totalRead);
352  }
353
354  return options.encoding ? result.toString(options.encoding) : result;
355}
356
357// All of the functions are defined as async in order to ensure that errors
358// thrown cause promise rejections rather than being thrown synchronously.
359async function access(path, mode = F_OK) {
360  path = getValidatedPath(path);
361
362  mode = getValidMode(mode, 'access');
363  return binding.access(pathModule.toNamespacedPath(path), mode,
364                        kUsePromises);
365}
366
367async function copyFile(src, dest, mode) {
368  src = getValidatedPath(src, 'src');
369  dest = getValidatedPath(dest, 'dest');
370  mode = getValidMode(mode, 'copyFile');
371  return binding.copyFile(pathModule.toNamespacedPath(src),
372                          pathModule.toNamespacedPath(dest),
373                          mode,
374                          kUsePromises);
375}
376
377// Note that unlike fs.open() which uses numeric file descriptors,
378// fsPromises.open() uses the fs.FileHandle class.
379async function open(path, flags, mode) {
380  path = getValidatedPath(path);
381  const flagsNumber = stringToFlags(flags);
382  mode = parseFileMode(mode, 'mode', 0o666);
383  return new FileHandle(
384    await binding.openFileHandle(pathModule.toNamespacedPath(path),
385                                 flagsNumber, mode, kUsePromises));
386}
387
388async function read(handle, bufferOrOptions, offset, length, position) {
389  let buffer = bufferOrOptions;
390  if (!isArrayBufferView(buffer)) {
391    if (bufferOrOptions === undefined) {
392      bufferOrOptions = {};
393    }
394    if (bufferOrOptions.buffer) {
395      buffer = bufferOrOptions.buffer;
396      validateBuffer(buffer);
397    } else {
398      buffer = Buffer.alloc(16384);
399    }
400    offset = bufferOrOptions.offset || 0;
401    length = buffer.byteLength;
402    position = bufferOrOptions.position ?? null;
403  }
404
405  if (offset == null) {
406    offset = 0;
407  } else {
408    validateInteger(offset, 'offset', 0);
409  }
410
411  length |= 0;
412
413  if (length === 0)
414    return { bytesRead: length, buffer };
415
416  if (buffer.byteLength === 0) {
417    throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
418                                    'is empty and cannot be written');
419  }
420
421  validateOffsetLengthRead(offset, length, buffer.byteLength);
422
423  if (!NumberIsSafeInteger(position))
424    position = -1;
425
426  const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
427                                        position, kUsePromises)) || 0;
428
429  return { bytesRead, buffer };
430}
431
432async function readv(handle, buffers, position) {
433  validateBufferArray(buffers);
434
435  if (typeof position !== 'number')
436    position = null;
437
438  const bytesRead = (await binding.readBuffers(handle.fd, buffers, position,
439                                               kUsePromises)) || 0;
440  return { bytesRead, buffers };
441}
442
443async function write(handle, buffer, offset, length, position) {
444  if (buffer && buffer.byteLength === 0)
445    return { bytesWritten: 0, buffer };
446
447  if (isArrayBufferView(buffer)) {
448    if (offset == null) {
449      offset = 0;
450    } else {
451      validateInteger(offset, 'offset', 0);
452    }
453    if (typeof length !== 'number')
454      length = buffer.byteLength - offset;
455    if (typeof position !== 'number')
456      position = null;
457    validateOffsetLengthWrite(offset, length, buffer.byteLength);
458    const bytesWritten =
459      (await binding.writeBuffer(handle.fd, buffer, offset,
460                                 length, position, kUsePromises)) || 0;
461    return { bytesWritten, buffer };
462  }
463
464  validatePrimitiveStringAfterArrayBufferView(buffer, 'buffer');
465  const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
466                                                  length, kUsePromises)) || 0;
467  return { bytesWritten, buffer };
468}
469
470async function writev(handle, buffers, position) {
471  validateBufferArray(buffers);
472
473  if (typeof position !== 'number')
474    position = null;
475
476  const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position,
477                                                   kUsePromises)) || 0;
478  return { bytesWritten, buffers };
479}
480
481async function rename(oldPath, newPath) {
482  oldPath = getValidatedPath(oldPath, 'oldPath');
483  newPath = getValidatedPath(newPath, 'newPath');
484  return binding.rename(pathModule.toNamespacedPath(oldPath),
485                        pathModule.toNamespacedPath(newPath),
486                        kUsePromises);
487}
488
489async function truncate(path, len = 0) {
490  const fd = await open(path, 'r+');
491  return PromisePrototypeFinally(ftruncate(fd, len), fd.close);
492}
493
494async function ftruncate(handle, len = 0) {
495  validateInteger(len, 'len');
496  len = MathMax(0, len);
497  return binding.ftruncate(handle.fd, len, kUsePromises);
498}
499
500async function rm(path, options) {
501  path = pathModule.toNamespacedPath(getValidatedPath(path));
502  options = await validateRmOptionsPromise(path, options);
503  return rimrafPromises(path, options);
504}
505
506async function rmdir(path, options) {
507  path = pathModule.toNamespacedPath(getValidatedPath(path));
508  options = validateRmdirOptions(options);
509
510  if (options.recursive) {
511    return rimrafPromises(path, options);
512  }
513
514  return binding.rmdir(path, kUsePromises);
515}
516
517async function fdatasync(handle) {
518  return binding.fdatasync(handle.fd, kUsePromises);
519}
520
521async function fsync(handle) {
522  return binding.fsync(handle.fd, kUsePromises);
523}
524
525async function mkdir(path, options) {
526  if (typeof options === 'number' || typeof options === 'string') {
527    options = { mode: options };
528  }
529  const {
530    recursive = false,
531    mode = 0o777
532  } = options || {};
533  path = getValidatedPath(path);
534  if (typeof recursive !== 'boolean')
535    throw new ERR_INVALID_ARG_TYPE('options.recursive', 'boolean', recursive);
536
537  return binding.mkdir(pathModule.toNamespacedPath(path),
538                       parseFileMode(mode, 'mode', 0o777), recursive,
539                       kUsePromises);
540}
541
542async function readdir(path, options) {
543  options = getOptions(options, {});
544  path = getValidatedPath(path);
545  const result = await binding.readdir(pathModule.toNamespacedPath(path),
546                                       options.encoding,
547                                       !!options.withFileTypes,
548                                       kUsePromises);
549  return options.withFileTypes ?
550    getDirectoryEntriesPromise(path, result) :
551    result;
552}
553
554async function readlink(path, options) {
555  options = getOptions(options, {});
556  path = getValidatedPath(path, 'oldPath');
557  return binding.readlink(pathModule.toNamespacedPath(path),
558                          options.encoding, kUsePromises);
559}
560
561async function symlink(target, path, type_) {
562  const type = (typeof type_ === 'string' ? type_ : null);
563  target = getValidatedPath(target, 'target');
564  path = getValidatedPath(path);
565  return binding.symlink(preprocessSymlinkDestination(target, type, path),
566                         pathModule.toNamespacedPath(path),
567                         stringToSymlinkType(type),
568                         kUsePromises);
569}
570
571async function fstat(handle, options = { bigint: false }) {
572  const result = await binding.fstat(handle.fd, options.bigint, kUsePromises);
573  return getStatsFromBinding(result);
574}
575
576async function lstat(path, options = { bigint: false }) {
577  path = getValidatedPath(path);
578  const result = await binding.lstat(pathModule.toNamespacedPath(path),
579                                     options.bigint, kUsePromises);
580  return getStatsFromBinding(result);
581}
582
583async function stat(path, options = { bigint: false }) {
584  path = getValidatedPath(path);
585  const result = await binding.stat(pathModule.toNamespacedPath(path),
586                                    options.bigint, kUsePromises);
587  return getStatsFromBinding(result);
588}
589
590async function link(existingPath, newPath) {
591  existingPath = getValidatedPath(existingPath, 'existingPath');
592  newPath = getValidatedPath(newPath, 'newPath');
593  return binding.link(pathModule.toNamespacedPath(existingPath),
594                      pathModule.toNamespacedPath(newPath),
595                      kUsePromises);
596}
597
598async function unlink(path) {
599  path = getValidatedPath(path);
600  return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
601}
602
603async function fchmod(handle, mode) {
604  mode = parseFileMode(mode, 'mode');
605  return binding.fchmod(handle.fd, mode, kUsePromises);
606}
607
608async function chmod(path, mode) {
609  path = getValidatedPath(path);
610  mode = parseFileMode(mode, 'mode');
611  return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
612}
613
614async function lchmod(path, mode) {
615  if (O_SYMLINK === undefined)
616    throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()');
617
618  const fd = await open(path, O_WRONLY | O_SYMLINK);
619  return PromisePrototypeFinally(fchmod(fd, mode), fd.close);
620}
621
622async function lchown(path, uid, gid) {
623  path = getValidatedPath(path);
624  validateInteger(uid, 'uid', -1, kMaxUserId);
625  validateInteger(gid, 'gid', -1, kMaxUserId);
626  return binding.lchown(pathModule.toNamespacedPath(path),
627                        uid, gid, kUsePromises);
628}
629
630async function fchown(handle, uid, gid) {
631  validateInteger(uid, 'uid', -1, kMaxUserId);
632  validateInteger(gid, 'gid', -1, kMaxUserId);
633  return binding.fchown(handle.fd, uid, gid, kUsePromises);
634}
635
636async function chown(path, uid, gid) {
637  path = getValidatedPath(path);
638  validateInteger(uid, 'uid', -1, kMaxUserId);
639  validateInteger(gid, 'gid', -1, kMaxUserId);
640  return binding.chown(pathModule.toNamespacedPath(path),
641                       uid, gid, kUsePromises);
642}
643
644async function utimes(path, atime, mtime) {
645  path = getValidatedPath(path);
646  return binding.utimes(pathModule.toNamespacedPath(path),
647                        toUnixTimestamp(atime),
648                        toUnixTimestamp(mtime),
649                        kUsePromises);
650}
651
652async function futimes(handle, atime, mtime) {
653  atime = toUnixTimestamp(atime, 'atime');
654  mtime = toUnixTimestamp(mtime, 'mtime');
655  return binding.futimes(handle.fd, atime, mtime, kUsePromises);
656}
657
658async function lutimes(path, atime, mtime) {
659  path = getValidatedPath(path);
660  return binding.lutimes(pathModule.toNamespacedPath(path),
661                         toUnixTimestamp(atime),
662                         toUnixTimestamp(mtime),
663                         kUsePromises);
664}
665
666async function realpath(path, options) {
667  options = getOptions(options, {});
668  path = getValidatedPath(path);
669  return binding.realpath(path, options.encoding, kUsePromises);
670}
671
672async function mkdtemp(prefix, options) {
673  options = getOptions(options, {});
674
675  validateString(prefix, 'prefix');
676  nullCheck(prefix);
677  warnOnNonPortableTemplate(prefix);
678  return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
679}
680
681async function writeFile(path, data, options) {
682  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
683  const flag = options.flag || 'w';
684
685  if (!isArrayBufferView(data) && !isCustomIterable(data)) {
686    validatePrimitiveStringAfterArrayBufferView(data, 'data');
687    data = Buffer.from(data, options.encoding || 'utf8');
688  }
689
690  validateAbortSignal(options.signal);
691  if (path instanceof FileHandle)
692    return writeFileHandle(path, data, options.signal, options.encoding);
693
694  if (options.signal?.aborted) {
695    throw lazyDOMException('The operation was aborted', 'AbortError');
696  }
697
698  const fd = await open(path, flag, options.mode);
699  return PromisePrototypeFinally(
700    writeFileHandle(fd, data, options.signal, options.encoding), fd.close);
701}
702
703function isCustomIterable(obj) {
704  return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string';
705}
706
707async function appendFile(path, data, options) {
708  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
709  options = copyObject(options);
710  options.flag = options.flag || 'a';
711  return writeFile(path, data, options);
712}
713
714async function readFile(path, options) {
715  options = getOptions(options, { flag: 'r' });
716  const flag = options.flag || 'r';
717
718  if (path instanceof FileHandle)
719    return readFileHandle(path, options);
720
721  if (options.signal?.aborted) {
722    throw lazyDOMException('The operation was aborted', 'AbortError');
723  }
724
725  const fd = await open(path, flag, 0o666);
726  return PromisePrototypeFinally(readFileHandle(fd, options), fd.close);
727}
728
729module.exports = {
730  exports: {
731    access,
732    copyFile,
733    open,
734    opendir: promisify(opendir),
735    rename,
736    truncate,
737    rm,
738    rmdir,
739    mkdir,
740    readdir,
741    readlink,
742    symlink,
743    lstat,
744    stat,
745    link,
746    unlink,
747    chmod,
748    lchmod,
749    lchown,
750    chown,
751    utimes,
752    lutimes,
753    realpath,
754    mkdtemp,
755    writeFile,
756    appendFile,
757    readFile,
758    watch,
759  },
760
761  FileHandle
762};
763