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