• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypePush,
5  ArrayPrototypePop,
6  Error,
7  MathMax,
8  MathMin,
9  NumberIsSafeInteger,
10  Promise,
11  PromisePrototypeThen,
12  PromiseResolve,
13  PromiseReject,
14  SafeArrayIterator,
15  SafePromisePrototypeFinally,
16  Symbol,
17  SymbolAsyncDispose,
18  Uint8Array,
19  FunctionPrototypeBind,
20} = primordials;
21
22const { fs: constants } = internalBinding('constants');
23const {
24  F_OK,
25  O_SYMLINK,
26  O_WRONLY,
27  S_IFMT,
28  S_IFREG,
29} = constants;
30
31const binding = internalBinding('fs');
32const { Buffer } = require('buffer');
33
34const {
35  codes: {
36    ERR_FS_FILE_TOO_LARGE,
37    ERR_INVALID_ARG_VALUE,
38    ERR_INVALID_STATE,
39    ERR_METHOD_NOT_IMPLEMENTED,
40  },
41  AbortError,
42  aggregateTwoErrors,
43} = require('internal/errors');
44const { isArrayBufferView } = require('internal/util/types');
45const { rimrafPromises } = require('internal/fs/rimraf');
46const {
47  constants: {
48    kIoMaxLength,
49    kMaxUserId,
50    kReadFileBufferLength,
51    kReadFileUnknownBufferLength,
52    kWriteFileMaxChunkSize,
53  },
54  copyObject,
55  emitRecursiveRmdirWarning,
56  getDirents,
57  getOptions,
58  getStatFsFromBinding,
59  getStatsFromBinding,
60  getValidatedPath,
61  getValidMode,
62  nullCheck,
63  preprocessSymlinkDestination,
64  stringToFlags,
65  stringToSymlinkType,
66  toUnixTimestamp,
67  validateBufferArray,
68  validateCpOptions,
69  validateOffsetLengthRead,
70  validateOffsetLengthWrite,
71  validateRmOptions,
72  validateRmdirOptions,
73  validatePrimitiveStringAfterArrayBufferView,
74  warnOnNonPortableTemplate,
75} = require('internal/fs/utils');
76const { opendir } = require('internal/fs/dir');
77const {
78  parseFileMode,
79  validateAbortSignal,
80  validateBoolean,
81  validateBuffer,
82  validateEncoding,
83  validateInteger,
84  validateString,
85} = require('internal/validators');
86const pathModule = require('path');
87const {
88  kEmptyObject,
89  lazyDOMException,
90  promisify,
91} = require('internal/util');
92const { EventEmitterMixin } = require('internal/event_target');
93const { StringDecoder } = require('string_decoder');
94const { watch } = require('internal/fs/watchers');
95const { isIterable } = require('internal/streams/utils');
96const assert = require('internal/assert');
97
98const kHandle = Symbol('kHandle');
99const kFd = Symbol('kFd');
100const kRefs = Symbol('kRefs');
101const kClosePromise = Symbol('kClosePromise');
102const kCloseResolve = Symbol('kCloseResolve');
103const kCloseReject = Symbol('kCloseReject');
104const kRef = Symbol('kRef');
105const kUnref = Symbol('kUnref');
106const kLocked = Symbol('kLocked');
107
108const { kUsePromises } = binding;
109const { Interface } = require('internal/readline/interface');
110const {
111  JSTransferable, kDeserialize, kTransfer, kTransferList,
112} = require('internal/worker/js_transferable');
113
114const getDirectoryEntriesPromise = promisify(getDirents);
115const validateRmOptionsPromise = promisify(validateRmOptions);
116
117let cpPromises;
118function lazyLoadCpPromises() {
119  return cpPromises ??= require('internal/fs/cp/cp').cpFn;
120}
121
122// Lazy loaded to avoid circular dependency.
123let fsStreams;
124function lazyFsStreams() {
125  return fsStreams ??= require('internal/fs/streams');
126}
127
128class FileHandle extends EventEmitterMixin(JSTransferable) {
129  /**
130   * @param {InternalFSBinding.FileHandle | undefined} filehandle
131   */
132  constructor(filehandle) {
133    super();
134    this[kHandle] = filehandle;
135    this[kFd] = filehandle ? filehandle.fd : -1;
136
137    this[kRefs] = 1;
138    this[kClosePromise] = null;
139  }
140
141  getAsyncId() {
142    return this[kHandle].getAsyncId();
143  }
144
145  get fd() {
146    return this[kFd];
147  }
148
149  appendFile(data, options) {
150    return fsCall(writeFile, this, data, options);
151  }
152
153  chmod(mode) {
154    return fsCall(fchmod, this, mode);
155  }
156
157  chown(uid, gid) {
158    return fsCall(fchown, this, uid, gid);
159  }
160
161  datasync() {
162    return fsCall(fdatasync, this);
163  }
164
165  sync() {
166    return fsCall(fsync, this);
167  }
168
169  read(buffer, offset, length, position) {
170    return fsCall(read, this, buffer, offset, length, position);
171  }
172
173  readv(buffers, position) {
174    return fsCall(readv, this, buffers, position);
175  }
176
177  readFile(options) {
178    return fsCall(readFile, this, options);
179  }
180
181  readLines(options = undefined) {
182    return new Interface({
183      input: this.createReadStream(options),
184      crlfDelay: Infinity,
185    });
186  }
187
188  stat(options) {
189    return fsCall(fstat, this, options);
190  }
191
192  truncate(len = 0) {
193    return fsCall(ftruncate, this, len);
194  }
195
196  utimes(atime, mtime) {
197    return fsCall(futimes, this, atime, mtime);
198  }
199
200  write(buffer, offset, length, position) {
201    return fsCall(write, this, buffer, offset, length, position);
202  }
203
204  writev(buffers, position) {
205    return fsCall(writev, this, buffers, position);
206  }
207
208  writeFile(data, options) {
209    return fsCall(writeFile, this, data, options);
210  }
211
212  close = () => {
213    if (this[kFd] === -1) {
214      return PromiseResolve();
215    }
216
217    if (this[kClosePromise]) {
218      return this[kClosePromise];
219    }
220
221    this[kRefs]--;
222    if (this[kRefs] === 0) {
223      this[kFd] = -1;
224      this[kClosePromise] = SafePromisePrototypeFinally(
225        this[kHandle].close(),
226        () => { this[kClosePromise] = undefined; },
227      );
228    } else {
229      this[kClosePromise] = SafePromisePrototypeFinally(
230        new Promise((resolve, reject) => {
231          this[kCloseResolve] = resolve;
232          this[kCloseReject] = reject;
233        }), () => {
234          this[kClosePromise] = undefined;
235          this[kCloseReject] = undefined;
236          this[kCloseResolve] = undefined;
237        },
238      );
239    }
240
241    this.emit('close');
242    return this[kClosePromise];
243  };
244
245  async [SymbolAsyncDispose]() {
246    return this.close();
247  }
248
249  /**
250   * @typedef {import('../webstreams/readablestream').ReadableStream
251   * } ReadableStream
252   * @returns {ReadableStream}
253   */
254  readableWebStream(options = kEmptyObject) {
255    if (this[kFd] === -1)
256      throw new ERR_INVALID_STATE('The FileHandle is closed');
257    if (this[kClosePromise])
258      throw new ERR_INVALID_STATE('The FileHandle is closing');
259    if (this[kLocked])
260      throw new ERR_INVALID_STATE('The FileHandle is locked');
261    this[kLocked] = true;
262
263    if (options.type !== undefined) {
264      validateString(options.type, 'options.type');
265    }
266
267    let readable;
268
269    if (options.type !== 'bytes') {
270      const {
271        newReadableStreamFromStreamBase,
272      } = require('internal/webstreams/adapters');
273      readable = newReadableStreamFromStreamBase(
274        this[kHandle],
275        undefined,
276        { ondone: () => this[kUnref]() });
277
278      const {
279        readableStreamCancel,
280      } = require('internal/webstreams/readablestream');
281      this[kRef]();
282      this.once('close', () => {
283        readableStreamCancel(readable);
284      });
285    } else {
286      const {
287        readableStreamCancel,
288        ReadableStream,
289      } = require('internal/webstreams/readablestream');
290
291      const readFn = FunctionPrototypeBind(this.read, this);
292      const ondone = FunctionPrototypeBind(this[kUnref], this);
293
294      readable = new ReadableStream({
295        type: 'bytes',
296        autoAllocateChunkSize: 16384,
297
298        async pull(controller) {
299          const view = controller.byobRequest.view;
300          const { bytesRead } = await readFn(view, view.byteOffset, view.byteLength);
301
302          if (bytesRead === 0) {
303            ondone();
304            controller.close();
305          }
306
307          controller.byobRequest.respond(bytesRead);
308        },
309
310        cancel() {
311          ondone();
312        },
313      });
314
315      this[kRef]();
316
317      this.once('close', () => {
318        readableStreamCancel(readable);
319      });
320    }
321
322    return readable;
323  }
324
325  /**
326   * @typedef {import('./streams').ReadStream
327   * } ReadStream
328   * @param {{
329   *   encoding?: string;
330   *   autoClose?: boolean;
331   *   emitClose?: boolean;
332   *   start: number;
333   *   end?: number;
334   *   highWaterMark?: number;
335   *   }} [options]
336   * @returns {ReadStream}
337   */
338  createReadStream(options = undefined) {
339    const { ReadStream } = lazyFsStreams();
340    return new ReadStream(undefined, { ...options, fd: this });
341  }
342
343  /**
344   * @typedef {import('./streams').WriteStream
345   * } WriteStream
346   * @param {{
347   *   encoding?: string;
348   *   autoClose?: boolean;
349   *   emitClose?: boolean;
350   *   start: number;
351   *   }} [options]
352   * @returns {WriteStream}
353   */
354  createWriteStream(options = undefined) {
355    const { WriteStream } = lazyFsStreams();
356    return new WriteStream(undefined, { ...options, fd: this });
357  }
358
359  [kTransfer]() {
360    if (this[kClosePromise] || this[kRefs] > 1) {
361      throw lazyDOMException('Cannot transfer FileHandle while in use',
362                             'DataCloneError');
363    }
364
365    const handle = this[kHandle];
366    this[kFd] = -1;
367    this[kHandle] = null;
368    this[kRefs] = 0;
369
370    return {
371      data: { handle },
372      deserializeInfo: 'internal/fs/promises:FileHandle',
373    };
374  }
375
376  [kTransferList]() {
377    return [ this[kHandle] ];
378  }
379
380  [kDeserialize]({ handle }) {
381    this[kHandle] = handle;
382    this[kFd] = handle.fd;
383  }
384
385  [kRef]() {
386    this[kRefs]++;
387  }
388
389  [kUnref]() {
390    this[kRefs]--;
391    if (this[kRefs] === 0) {
392      this[kFd] = -1;
393      PromisePrototypeThen(
394        this[kHandle].close(),
395        this[kCloseResolve],
396        this[kCloseReject],
397      );
398    }
399  }
400}
401
402async function handleFdClose(fileOpPromise, closeFunc) {
403  return PromisePrototypeThen(
404    fileOpPromise,
405    (result) => PromisePrototypeThen(closeFunc(), () => result),
406    (opError) =>
407      PromisePrototypeThen(
408        closeFunc(),
409        () => PromiseReject(opError),
410        (closeError) => PromiseReject(aggregateTwoErrors(closeError, opError)),
411      ),
412  );
413}
414
415async function fsCall(fn, handle, ...args) {
416  assert(handle[kRefs] !== undefined,
417         'handle must be an instance of FileHandle');
418
419  if (handle.fd === -1) {
420    // eslint-disable-next-line no-restricted-syntax
421    const err = new Error('file closed');
422    err.code = 'EBADF';
423    err.syscall = fn.name;
424    throw err;
425  }
426
427  try {
428    handle[kRef]();
429    return await fn(handle, ...new SafeArrayIterator(args));
430  } finally {
431    handle[kUnref]();
432  }
433}
434
435function checkAborted(signal) {
436  if (signal?.aborted)
437    throw new AbortError(undefined, { cause: signal?.reason });
438}
439
440async function writeFileHandle(filehandle, data, signal, encoding) {
441  checkAborted(signal);
442  if (isCustomIterable(data)) {
443    for await (const buf of data) {
444      checkAborted(signal);
445      const toWrite =
446        isArrayBufferView(buf) ? buf : Buffer.from(buf, encoding || 'utf8');
447      let remaining = toWrite.byteLength;
448      while (remaining > 0) {
449        const writeSize = MathMin(kWriteFileMaxChunkSize, remaining);
450        const { bytesWritten } = await write(
451          filehandle, toWrite, toWrite.byteLength - remaining, writeSize);
452        remaining -= bytesWritten;
453        checkAborted(signal);
454      }
455    }
456    return;
457  }
458  data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
459  let remaining = data.byteLength;
460  if (remaining === 0) return;
461  do {
462    checkAborted(signal);
463    const { bytesWritten } =
464      await write(filehandle, data, 0,
465                  MathMin(kWriteFileMaxChunkSize, data.byteLength));
466    remaining -= bytesWritten;
467    data = new Uint8Array(
468      data.buffer,
469      data.byteOffset + bytesWritten,
470      data.byteLength - bytesWritten,
471    );
472  } while (remaining > 0);
473}
474
475async function readFileHandle(filehandle, options) {
476  const signal = options?.signal;
477  const encoding = options?.encoding;
478  const decoder = encoding && new StringDecoder(encoding);
479
480  checkAborted(signal);
481
482  const statFields = await binding.fstat(filehandle.fd, false, kUsePromises);
483
484  checkAborted(signal);
485
486  let size = 0;
487  let length = 0;
488  if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) {
489    size = statFields[8/* size */];
490    length = encoding ? MathMin(size, kReadFileBufferLength) : size;
491  }
492  if (length === 0) {
493    length = kReadFileUnknownBufferLength;
494  }
495
496  if (size > kIoMaxLength)
497    throw new ERR_FS_FILE_TOO_LARGE(size);
498
499  let totalRead = 0;
500  let buffer = Buffer.allocUnsafeSlow(length);
501  let result = '';
502  let offset = 0;
503  let buffers;
504  const chunkedRead = length > kReadFileBufferLength;
505
506  while (true) {
507    checkAborted(signal);
508
509    if (chunkedRead) {
510      length = MathMin(size - totalRead, kReadFileBufferLength);
511    }
512
513    const bytesRead = (await binding.read(filehandle.fd, buffer, offset,
514                                          length, -1, kUsePromises)) ?? 0;
515    totalRead += bytesRead;
516
517    if (bytesRead === 0 ||
518        totalRead === size ||
519        (bytesRead !== buffer.length && !chunkedRead)) {
520      const singleRead = bytesRead === totalRead;
521
522      const bytesToCheck = chunkedRead ? totalRead : bytesRead;
523
524      if (bytesToCheck !== buffer.length) {
525        buffer = buffer.subarray(0, bytesToCheck);
526      }
527
528      if (!encoding) {
529        if (size === 0 && !singleRead) {
530          ArrayPrototypePush(buffers, buffer);
531          return Buffer.concat(buffers, totalRead);
532        }
533        return buffer;
534      }
535
536      if (singleRead) {
537        return buffer.toString(encoding);
538      }
539      result += decoder.end(buffer);
540      return result;
541    }
542
543    if (encoding) {
544      result += decoder.write(buffer);
545    } else if (size !== 0) {
546      offset = totalRead;
547    } else {
548      buffers ??= [];
549      // Unknown file size requires chunks.
550      ArrayPrototypePush(buffers, buffer);
551      buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
552    }
553  }
554}
555
556// All of the functions are defined as async in order to ensure that errors
557// thrown cause promise rejections rather than being thrown synchronously.
558async function access(path, mode = F_OK) {
559  path = getValidatedPath(path);
560
561  mode = getValidMode(mode, 'access');
562  return binding.access(pathModule.toNamespacedPath(path), mode,
563                        kUsePromises);
564}
565
566async function cp(src, dest, options) {
567  options = validateCpOptions(options);
568  src = pathModule.toNamespacedPath(getValidatedPath(src, 'src'));
569  dest = pathModule.toNamespacedPath(getValidatedPath(dest, 'dest'));
570  return lazyLoadCpPromises()(src, dest, options);
571}
572
573async function copyFile(src, dest, mode) {
574  src = getValidatedPath(src, 'src');
575  dest = getValidatedPath(dest, 'dest');
576  mode = getValidMode(mode, 'copyFile');
577  return binding.copyFile(pathModule.toNamespacedPath(src),
578                          pathModule.toNamespacedPath(dest),
579                          mode,
580                          kUsePromises);
581}
582
583// Note that unlike fs.open() which uses numeric file descriptors,
584// fsPromises.open() uses the fs.FileHandle class.
585async function open(path, flags, mode) {
586  path = getValidatedPath(path);
587  const flagsNumber = stringToFlags(flags);
588  mode = parseFileMode(mode, 'mode', 0o666);
589  return new FileHandle(
590    await binding.openFileHandle(pathModule.toNamespacedPath(path),
591                                 flagsNumber, mode, kUsePromises));
592}
593
594async function read(handle, bufferOrParams, offset, length, position) {
595  let buffer = bufferOrParams;
596  if (!isArrayBufferView(buffer)) {
597    // This is fh.read(params)
598    ({
599      buffer = Buffer.alloc(16384),
600      offset = 0,
601      length = buffer.byteLength - offset,
602      position = null,
603    } = bufferOrParams ?? kEmptyObject);
604
605    validateBuffer(buffer);
606  }
607
608  if (offset !== null && typeof offset === 'object') {
609    // This is fh.read(buffer, options)
610    ({
611      offset = 0,
612      length = buffer.byteLength - offset,
613      position = null,
614    } = offset);
615  }
616
617  if (offset == null) {
618    offset = 0;
619  } else {
620    validateInteger(offset, 'offset', 0);
621  }
622
623  length |= 0;
624
625  if (length === 0)
626    return { bytesRead: length, buffer };
627
628  if (buffer.byteLength === 0) {
629    throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
630                                    'is empty and cannot be written');
631  }
632
633  validateOffsetLengthRead(offset, length, buffer.byteLength);
634
635  if (!NumberIsSafeInteger(position))
636    position = -1;
637
638  const bytesRead = (await binding.read(handle.fd, buffer, offset, length,
639                                        position, kUsePromises)) || 0;
640
641  return { bytesRead, buffer };
642}
643
644async function readv(handle, buffers, position) {
645  validateBufferArray(buffers);
646
647  if (typeof position !== 'number')
648    position = null;
649
650  const bytesRead = (await binding.readBuffers(handle.fd, buffers, position,
651                                               kUsePromises)) || 0;
652  return { bytesRead, buffers };
653}
654
655async function write(handle, buffer, offsetOrOptions, length, position) {
656  if (buffer?.byteLength === 0)
657    return { bytesWritten: 0, buffer };
658
659  let offset = offsetOrOptions;
660  if (isArrayBufferView(buffer)) {
661    if (typeof offset === 'object') {
662      ({
663        offset = 0,
664        length = buffer.byteLength - offset,
665        position = null,
666      } = offsetOrOptions ?? kEmptyObject);
667    }
668
669    if (offset == null) {
670      offset = 0;
671    } else {
672      validateInteger(offset, 'offset', 0);
673    }
674    if (typeof length !== 'number')
675      length = buffer.byteLength - offset;
676    if (typeof position !== 'number')
677      position = null;
678    validateOffsetLengthWrite(offset, length, buffer.byteLength);
679    const bytesWritten =
680      (await binding.writeBuffer(handle.fd, buffer, offset,
681                                 length, position, kUsePromises)) || 0;
682    return { bytesWritten, buffer };
683  }
684
685  validatePrimitiveStringAfterArrayBufferView(buffer, 'buffer');
686  validateEncoding(buffer, length);
687  const bytesWritten = (await binding.writeString(handle.fd, buffer, offset,
688                                                  length, kUsePromises)) || 0;
689  return { bytesWritten, buffer };
690}
691
692async function writev(handle, buffers, position) {
693  validateBufferArray(buffers);
694
695  if (typeof position !== 'number')
696    position = null;
697
698  if (buffers.length === 0) {
699    return { bytesWritten: 0, buffers };
700  }
701
702  const bytesWritten = (await binding.writeBuffers(handle.fd, buffers, position,
703                                                   kUsePromises)) || 0;
704  return { bytesWritten, buffers };
705}
706
707async function rename(oldPath, newPath) {
708  oldPath = getValidatedPath(oldPath, 'oldPath');
709  newPath = getValidatedPath(newPath, 'newPath');
710  return binding.rename(pathModule.toNamespacedPath(oldPath),
711                        pathModule.toNamespacedPath(newPath),
712                        kUsePromises);
713}
714
715async function truncate(path, len = 0) {
716  const fd = await open(path, 'r+');
717  return handleFdClose(ftruncate(fd, len), fd.close);
718}
719
720async function ftruncate(handle, len = 0) {
721  validateInteger(len, 'len');
722  len = MathMax(0, len);
723  return binding.ftruncate(handle.fd, len, kUsePromises);
724}
725
726async function rm(path, options) {
727  path = pathModule.toNamespacedPath(getValidatedPath(path));
728  options = await validateRmOptionsPromise(path, options, false);
729  return rimrafPromises(path, options);
730}
731
732async function rmdir(path, options) {
733  path = pathModule.toNamespacedPath(getValidatedPath(path));
734  options = validateRmdirOptions(options);
735
736  if (options.recursive) {
737    emitRecursiveRmdirWarning();
738    const stats = await stat(path);
739    if (stats.isDirectory()) {
740      return rimrafPromises(path, options);
741    }
742  }
743
744  return binding.rmdir(path, kUsePromises);
745}
746
747async function fdatasync(handle) {
748  return binding.fdatasync(handle.fd, kUsePromises);
749}
750
751async function fsync(handle) {
752  return binding.fsync(handle.fd, kUsePromises);
753}
754
755async function mkdir(path, options) {
756  if (typeof options === 'number' || typeof options === 'string') {
757    options = { mode: options };
758  }
759  const {
760    recursive = false,
761    mode = 0o777,
762  } = options || kEmptyObject;
763  path = getValidatedPath(path);
764  validateBoolean(recursive, 'options.recursive');
765
766  return binding.mkdir(pathModule.toNamespacedPath(path),
767                       parseFileMode(mode, 'mode', 0o777), recursive,
768                       kUsePromises);
769}
770
771async function readdirRecursive(originalPath, options) {
772  const result = [];
773  const queue = [
774    [
775      originalPath,
776      await binding.readdir(
777        pathModule.toNamespacedPath(originalPath),
778        options.encoding,
779        !!options.withFileTypes,
780        kUsePromises,
781      ),
782    ],
783  ];
784
785
786  if (options.withFileTypes) {
787    while (queue.length > 0) {
788      // If we want to implement BFS make this a `shift` call instead of `pop`
789      const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
790      for (const dirent of getDirents(path, readdir)) {
791        ArrayPrototypePush(result, dirent);
792        if (dirent.isDirectory()) {
793          const direntPath = pathModule.join(path, dirent.name);
794          ArrayPrototypePush(queue, [
795            direntPath,
796            await binding.readdir(
797              direntPath,
798              options.encoding,
799              true,
800              kUsePromises,
801            ),
802          ]);
803        }
804      }
805    }
806  } else {
807    while (queue.length > 0) {
808      const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
809      for (const ent of readdir) {
810        const direntPath = pathModule.join(path, ent);
811        const stat = binding.internalModuleStat(direntPath);
812        ArrayPrototypePush(
813          result,
814          pathModule.relative(originalPath, direntPath),
815        );
816        if (stat === 1) {
817          ArrayPrototypePush(queue, [
818            direntPath,
819            await binding.readdir(
820              pathModule.toNamespacedPath(direntPath),
821              options.encoding,
822              false,
823              kUsePromises,
824            ),
825          ]);
826        }
827      }
828    }
829  }
830
831  return result;
832}
833
834async function readdir(path, options) {
835  options = getOptions(options);
836  path = getValidatedPath(path);
837  if (options.recursive) {
838    return readdirRecursive(path, options);
839  }
840  const result = await binding.readdir(
841    pathModule.toNamespacedPath(path),
842    options.encoding,
843    !!options.withFileTypes,
844    kUsePromises,
845  );
846  return options.withFileTypes ?
847    getDirectoryEntriesPromise(path, result) :
848    result;
849}
850
851async function readlink(path, options) {
852  options = getOptions(options);
853  path = getValidatedPath(path, 'oldPath');
854  return binding.readlink(pathModule.toNamespacedPath(path),
855                          options.encoding, kUsePromises);
856}
857
858async function symlink(target, path, type_) {
859  const type = (typeof type_ === 'string' ? type_ : null);
860  target = getValidatedPath(target, 'target');
861  path = getValidatedPath(path);
862  return binding.symlink(preprocessSymlinkDestination(target, type, path),
863                         pathModule.toNamespacedPath(path),
864                         stringToSymlinkType(type),
865                         kUsePromises);
866}
867
868async function fstat(handle, options = { bigint: false }) {
869  const result = await binding.fstat(handle.fd, options.bigint, kUsePromises);
870  return getStatsFromBinding(result);
871}
872
873async function lstat(path, options = { bigint: false }) {
874  path = getValidatedPath(path);
875  const result = await binding.lstat(pathModule.toNamespacedPath(path),
876                                     options.bigint, kUsePromises);
877  return getStatsFromBinding(result);
878}
879
880async function stat(path, options = { bigint: false }) {
881  path = getValidatedPath(path);
882  const result = await binding.stat(pathModule.toNamespacedPath(path),
883                                    options.bigint, kUsePromises);
884  return getStatsFromBinding(result);
885}
886
887async function statfs(path, options = { bigint: false }) {
888  path = getValidatedPath(path);
889  const result = await binding.statfs(pathModule.toNamespacedPath(path),
890                                      options.bigint, kUsePromises);
891  return getStatFsFromBinding(result);
892}
893
894async function link(existingPath, newPath) {
895  existingPath = getValidatedPath(existingPath, 'existingPath');
896  newPath = getValidatedPath(newPath, 'newPath');
897  return binding.link(pathModule.toNamespacedPath(existingPath),
898                      pathModule.toNamespacedPath(newPath),
899                      kUsePromises);
900}
901
902async function unlink(path) {
903  path = getValidatedPath(path);
904  return binding.unlink(pathModule.toNamespacedPath(path), kUsePromises);
905}
906
907async function fchmod(handle, mode) {
908  mode = parseFileMode(mode, 'mode');
909  return binding.fchmod(handle.fd, mode, kUsePromises);
910}
911
912async function chmod(path, mode) {
913  path = getValidatedPath(path);
914  mode = parseFileMode(mode, 'mode');
915  return binding.chmod(pathModule.toNamespacedPath(path), mode, kUsePromises);
916}
917
918async function lchmod(path, mode) {
919  if (O_SYMLINK === undefined)
920    throw new ERR_METHOD_NOT_IMPLEMENTED('lchmod()');
921
922  const fd = await open(path, O_WRONLY | O_SYMLINK);
923  return handleFdClose(fchmod(fd, mode), fd.close);
924}
925
926async function lchown(path, uid, gid) {
927  path = getValidatedPath(path);
928  validateInteger(uid, 'uid', -1, kMaxUserId);
929  validateInteger(gid, 'gid', -1, kMaxUserId);
930  return binding.lchown(pathModule.toNamespacedPath(path),
931                        uid, gid, kUsePromises);
932}
933
934async function fchown(handle, uid, gid) {
935  validateInteger(uid, 'uid', -1, kMaxUserId);
936  validateInteger(gid, 'gid', -1, kMaxUserId);
937  return binding.fchown(handle.fd, uid, gid, kUsePromises);
938}
939
940async function chown(path, uid, gid) {
941  path = getValidatedPath(path);
942  validateInteger(uid, 'uid', -1, kMaxUserId);
943  validateInteger(gid, 'gid', -1, kMaxUserId);
944  return binding.chown(pathModule.toNamespacedPath(path),
945                       uid, gid, kUsePromises);
946}
947
948async function utimes(path, atime, mtime) {
949  path = getValidatedPath(path);
950  return binding.utimes(pathModule.toNamespacedPath(path),
951                        toUnixTimestamp(atime),
952                        toUnixTimestamp(mtime),
953                        kUsePromises);
954}
955
956async function futimes(handle, atime, mtime) {
957  atime = toUnixTimestamp(atime, 'atime');
958  mtime = toUnixTimestamp(mtime, 'mtime');
959  return binding.futimes(handle.fd, atime, mtime, kUsePromises);
960}
961
962async function lutimes(path, atime, mtime) {
963  path = getValidatedPath(path);
964  return binding.lutimes(pathModule.toNamespacedPath(path),
965                         toUnixTimestamp(atime),
966                         toUnixTimestamp(mtime),
967                         kUsePromises);
968}
969
970async function realpath(path, options) {
971  options = getOptions(options);
972  path = getValidatedPath(path);
973  return binding.realpath(path, options.encoding, kUsePromises);
974}
975
976async function mkdtemp(prefix, options) {
977  options = getOptions(options);
978
979  validateString(prefix, 'prefix');
980  nullCheck(prefix);
981  warnOnNonPortableTemplate(prefix);
982  return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
983}
984
985async function writeFile(path, data, options) {
986  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
987  const flag = options.flag || 'w';
988
989  if (!isArrayBufferView(data) && !isCustomIterable(data)) {
990    validatePrimitiveStringAfterArrayBufferView(data, 'data');
991    data = Buffer.from(data, options.encoding || 'utf8');
992  }
993
994  validateAbortSignal(options.signal);
995  if (path instanceof FileHandle)
996    return writeFileHandle(path, data, options.signal, options.encoding);
997
998  checkAborted(options.signal);
999
1000  const fd = await open(path, flag, options.mode);
1001  return handleFdClose(
1002    writeFileHandle(fd, data, options.signal, options.encoding), fd.close);
1003}
1004
1005function isCustomIterable(obj) {
1006  return isIterable(obj) && !isArrayBufferView(obj) && typeof obj !== 'string';
1007}
1008
1009async function appendFile(path, data, options) {
1010  options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
1011  options = copyObject(options);
1012  options.flag = options.flag || 'a';
1013  return writeFile(path, data, options);
1014}
1015
1016async function readFile(path, options) {
1017  options = getOptions(options, { flag: 'r' });
1018  const flag = options.flag || 'r';
1019
1020  if (path instanceof FileHandle)
1021    return readFileHandle(path, options);
1022
1023  checkAborted(options.signal);
1024
1025  const fd = await open(path, flag, 0o666);
1026  return handleFdClose(readFileHandle(fd, options), fd.close);
1027}
1028
1029module.exports = {
1030  exports: {
1031    access,
1032    copyFile,
1033    cp,
1034    open,
1035    opendir: promisify(opendir),
1036    rename,
1037    truncate,
1038    rm,
1039    rmdir,
1040    mkdir,
1041    readdir,
1042    readlink,
1043    symlink,
1044    lstat,
1045    stat,
1046    statfs,
1047    link,
1048    unlink,
1049    chmod,
1050    lchmod,
1051    lchown,
1052    chown,
1053    utimes,
1054    lutimes,
1055    realpath,
1056    mkdtemp,
1057    writeFile,
1058    appendFile,
1059    readFile,
1060    watch,
1061    constants,
1062  },
1063
1064  FileHandle,
1065  kRef,
1066  kUnref,
1067};
1068