text: // Copyright Joyent, Inc. and other Node contributors.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a
  // copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to permit
  // persons to whom the Software is furnished to do so, subject to the
  // following conditions:
  //
  // The above copyright notice and this permission notice shall be included
  // in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  // USE OR OTHER DEALINGS IN THE SOFTWARE.
  
  // Maintainers, keep in mind that ES1-style octal literals (`0666`) are not
  // allowed in strict mode. Use ES6-style octal literals instead (`0o666`).
  
  'use strict';
  
  // Most platforms don't allow reads or writes >= 2 GB.
  // See https://github.com/libuv/libuv/pull/1501.
  const kIoMaxLength = 2 ** 31 - 1;
  
  const {
    Map,
    MathMax,
    NumberIsSafeInteger,
    ObjectCreate,
    ObjectDefineProperties,
    ObjectDefineProperty,
    Promise,
  } = primordials;
  
  const { fs: constants } = internalBinding('constants');
  const {
    S_IFIFO,
    S_IFLNK,
    S_IFMT,
    S_IFREG,
    S_IFSOCK,
    F_OK,
    R_OK,
    W_OK,
    X_OK,
    O_WRONLY,
    O_SYMLINK
  } = constants;
  
  const pathModule = require('path');
  const { isArrayBufferView } = require('internal/util/types');
  const binding = internalBinding('fs');
  const { Buffer } = require('buffer');
  const {
    codes: {
      ERR_FS_FILE_TOO_LARGE,
      ERR_INVALID_ARG_VALUE,
      ERR_INVALID_ARG_TYPE,
      ERR_INVALID_CALLBACK
    },
    uvException
  } = require('internal/errors');
  
  const { FSReqCallback, statValues } = binding;
  const { toPathIfFileURL } = require('internal/url');
  const internalUtil = require('internal/util');
  const {
    copyObject,
    Dirent,
    getDirents,
    getOptions,
    getValidatedPath,
    handleErrorFromBinding,
    nullCheck,
    preprocessSymlinkDestination,
    Stats,
    getStatsFromBinding,
    realpathCacheKey,
    stringToFlags,
    stringToSymlinkType,
    toUnixTimestamp,
    validateBufferArray,
    validateOffsetLengthRead,
    validateOffsetLengthWrite,
    validatePath,
    validateRmdirOptions,
    warnOnNonPortableTemplate
  } = require('internal/fs/utils');
  const {
    Dir,
    opendir,
    opendirSync
  } = require('internal/fs/dir');
  const {
    CHAR_FORWARD_SLASH,
    CHAR_BACKWARD_SLASH,
  } = require('internal/constants');
  const {
    isUint32,
    parseMode,
    validateBuffer,
    validateInteger,
    validateInt32
  } = require('internal/validators');
  // 2 ** 32 - 1
  const kMaxUserId = 4294967295;
  
  let truncateWarn = true;
  let fs;
  
  // Lazy loaded
  let promises = null;
  let watchers;
  let ReadFileContext;
  let ReadStream;
  let WriteStream;
  let rimraf;
  let rimrafSync;
  
  // These have to be separate because of how graceful-fs happens to do it's
  // monkeypatching.
  let FileReadStream;
  let FileWriteStream;
  
  const isWindows = process.platform === 'win32';
  
  
  function showTruncateDeprecation() {
    if (truncateWarn) {
      process.emitWarning(
        'Using fs.truncate with a file descriptor is deprecated. Please use ' +
        'fs.ftruncate with a file descriptor instead.',
        'DeprecationWarning', 'DEP0081');
      truncateWarn = false;
    }
  }
  
  function maybeCallback(cb) {
    if (typeof cb === 'function')
      return cb;
  
    throw new ERR_INVALID_CALLBACK(cb);
  }
  
  // Ensure that callbacks run in the global context. Only use this function
  // for callbacks that are passed to the binding layer, callbacks that are
  // invoked from JS already run in the proper scope.
  function makeCallback(cb) {
    if (typeof cb !== 'function') {
      throw new ERR_INVALID_CALLBACK(cb);
    }
  
    return (...args) => cb(...args);
  }
  
  // Special case of `makeCallback()` that is specific to async `*stat()` calls as
  // an optimization, since the data passed back to the callback needs to be
  // transformed anyway.
  function makeStatsCallback(cb) {
    if (typeof cb !== 'function') {
      throw new ERR_INVALID_CALLBACK(cb);
    }
  
    return (err, stats) => {
      if (err) return cb(err);
      cb(err, getStatsFromBinding(stats));
    };
  }
  
  const isFd = isUint32;
  
  function isFileType(stats, fileType) {
    // Use stats array directly to avoid creating an fs.Stats instance just for
    // our internal use.
    return (stats[1/* mode */] & S_IFMT) === fileType;
  }
  
  function access(path, mode, callback) {
    if (typeof mode === 'function') {
      callback = mode;
      mode = F_OK;
    }
  
    path = getValidatedPath(path);
  
    mode = mode | 0;
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.access(pathModule.toNamespacedPath(path), mode, req);
  }
  
  function accessSync(path, mode) {
    path = getValidatedPath(path);
  
    if (mode === undefined)
      mode = F_OK;
    else
      mode = mode | 0;
  
    const ctx = { path };
    binding.access(pathModule.toNamespacedPath(path), mode, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function exists(path, callback) {
    maybeCallback(callback);
  
    function suppressedCallback(err) {
      callback(err ? false : true);
    }
  
    try {
      fs.access(path, F_OK, suppressedCallback);
    } catch {
      return callback(false);
    }
  }
  
  ObjectDefineProperty(exists, internalUtil.promisify.custom, {
    value: (path) => {
      return new Promise((resolve) => fs.exists(path, resolve));
    }
  });
  
  // fs.existsSync never throws, it only returns true or false.
  // Since fs.existsSync never throws, users have established
  // the expectation that passing invalid arguments to it, even like
  // fs.existsSync(), would only get a false in return, so we cannot signal
  // validation errors to users properly out of compatibility concerns.
  // TODO(joyeecheung): deprecate the never-throw-on-invalid-arguments behavior
  function existsSync(path) {
    try {
      path = getValidatedPath(path);
    } catch {
      return false;
    }
    const ctx = { path };
    const nPath = pathModule.toNamespacedPath(path);
    binding.access(nPath, F_OK, undefined, ctx);
  
    // In case of an invalid symlink, `binding.access()` on win32
    // will **not** return an error and is therefore not enough.
    // Double check with `binding.stat()`.
    if (isWindows && ctx.errno === undefined) {
      binding.stat(nPath, false, undefined, ctx);
    }
  
    return ctx.errno === undefined;
  }
  
  function readFileAfterOpen(err, fd) {
    const context = this.context;
  
    if (err) {
      context.callback(err);
      return;
    }
  
    context.fd = fd;
  
    const req = new FSReqCallback();
    req.oncomplete = readFileAfterStat;
    req.context = context;
    binding.fstat(fd, false, req);
  }
  
  function readFileAfterStat(err, stats) {
    const context = this.context;
  
    if (err)
      return context.close(err);
  
    const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0;
  
    if (size > kIoMaxLength) {
      err = new ERR_FS_FILE_TOO_LARGE(size);
      return context.close(err);
    }
  
    try {
      if (size === 0) {
        context.buffers = [];
      } else {
        context.buffer = Buffer.allocUnsafeSlow(size);
      }
    } catch (err) {
      return context.close(err);
    }
    context.read();
  }
  
  function readFile(path, options, callback) {
    callback = maybeCallback(callback || options);
    options = getOptions(options, { flag: 'r' });
    if (!ReadFileContext)
      ReadFileContext = require('internal/fs/read_file_context');
    const context = new ReadFileContext(callback, options.encoding);
    context.isUserFd = isFd(path); // File descriptor ownership
  
    const req = new FSReqCallback();
    req.context = context;
    req.oncomplete = readFileAfterOpen;
  
    if (context.isUserFd) {
      process.nextTick(function tick() {
        req.oncomplete(null, path);
      });
      return;
    }
  
    path = getValidatedPath(path);
    binding.open(pathModule.toNamespacedPath(path),
                 stringToFlags(options.flag || 'r'),
                 0o666,
                 req);
  }
  
  function tryStatSync(fd, isUserFd) {
    const ctx = {};
    const stats = binding.fstat(fd, false, undefined, ctx);
    if (ctx.errno !== undefined && !isUserFd) {
      fs.closeSync(fd);
      throw uvException(ctx);
    }
    return stats;
  }
  
  function tryCreateBuffer(size, fd, isUserFd) {
    let threw = true;
    let buffer;
    try {
      if (size > kIoMaxLength) {
        throw new ERR_FS_FILE_TOO_LARGE(size);
      }
      buffer = Buffer.allocUnsafe(size);
      threw = false;
    } finally {
      if (threw && !isUserFd) fs.closeSync(fd);
    }
    return buffer;
  }
  
  function tryReadSync(fd, isUserFd, buffer, pos, len) {
    let threw = true;
    let bytesRead;
    try {
      bytesRead = fs.readSync(fd, buffer, pos, len);
      threw = false;
    } finally {
      if (threw && !isUserFd) fs.closeSync(fd);
    }
    return bytesRead;
  }
  
  function readFileSync(path, options) {
    options = getOptions(options, { flag: 'r' });
    const isUserFd = isFd(path); // File descriptor ownership
    const fd = isUserFd ? path : fs.openSync(path, options.flag, 0o666);
  
    const stats = tryStatSync(fd, isUserFd);
    const size = isFileType(stats, S_IFREG) ? stats[8] : 0;
    let pos = 0;
    let buffer; // Single buffer with file data
    let buffers; // List for when size is unknown
  
    if (size === 0) {
      buffers = [];
    } else {
      buffer = tryCreateBuffer(size, fd, isUserFd);
    }
  
    let bytesRead;
  
    if (size !== 0) {
      do {
        bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
        pos += bytesRead;
      } while (bytesRead !== 0 && pos < size);
    } else {
      do {
        // The kernel lies about many files.
        // Go ahead and try to read some bytes.
        buffer = Buffer.allocUnsafe(8192);
        bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192);
        if (bytesRead !== 0) {
          buffers.push(buffer.slice(0, bytesRead));
        }
        pos += bytesRead;
      } while (bytesRead !== 0);
    }
  
    if (!isUserFd)
      fs.closeSync(fd);
  
    if (size === 0) {
      // Data was collected into the buffers list.
      buffer = Buffer.concat(buffers, pos);
    } else if (pos < size) {
      buffer = buffer.slice(0, pos);
    }
  
    if (options.encoding) buffer = buffer.toString(options.encoding);
    return buffer;
  }
  
  function close(fd, callback) {
    validateInt32(fd, 'fd', 0);
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.close(fd, req);
  }
  
  function closeSync(fd) {
    validateInt32(fd, 'fd', 0);
  
    const ctx = {};
    binding.close(fd, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function open(path, flags, mode, callback) {
    path = getValidatedPath(path);
    if (arguments.length < 3) {
      callback = flags;
      flags = 'r';
      mode = 0o666;
    } else if (typeof mode === 'function') {
      callback = mode;
      mode = 0o666;
    }
    const flagsNumber = stringToFlags(flags);
    if (arguments.length >= 4) {
      mode = parseMode(mode, 'mode', 0o666);
    }
    callback = makeCallback(callback);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
  
    binding.open(pathModule.toNamespacedPath(path),
                 flagsNumber,
                 mode,
                 req);
  }
  
  
  function openSync(path, flags, mode) {
    path = getValidatedPath(path);
    const flagsNumber = stringToFlags(flags || 'r');
    mode = parseMode(mode, 'mode', 0o666);
  
    const ctx = { path };
    const result = binding.open(pathModule.toNamespacedPath(path),
                                flagsNumber, mode,
                                undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  }
  
  // usage:
  // fs.read(fd, buffer, offset, length, position, callback);
  // OR
  // fs.read(fd, {}, callback)
  function read(fd, buffer, offset, length, position, callback) {
    validateInt32(fd, 'fd', 0);
  
    if (arguments.length <= 3) {
      // Assume fs.read(fd, options, callback)
      let options = {};
      if (arguments.length < 3) {
        // This is fs.read(fd, callback)
        // buffer will be the callback
        callback = buffer;
      } else {
        // This is fs.read(fd, {}, callback)
        // buffer will be the options object
        // offset is the callback
        options = buffer;
        callback = offset;
      }
  
      buffer = options.buffer || Buffer.alloc(16384);
      offset = options.offset || 0;
      length = options.length || buffer.length;
      position = options.position;
    }
  
    validateBuffer(buffer);
    callback = maybeCallback(callback);
  
    if (offset == null) {
      offset = 0;
    } else {
      validateInteger(offset, 'offset');
    }
  
    length |= 0;
  
    if (length === 0) {
      return process.nextTick(function tick() {
        callback(null, 0, buffer);
      });
    }
  
    if (buffer.byteLength === 0) {
      throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
                                      'is empty and cannot be written');
    }
  
    validateOffsetLengthRead(offset, length, buffer.byteLength);
  
    if (!NumberIsSafeInteger(position))
      position = -1;
  
    function wrapper(err, bytesRead) {
      // Retain a reference to buffer so that it can't be GC'ed too soon.
      callback(err, bytesRead || 0, buffer);
    }
  
    const req = new FSReqCallback();
    req.oncomplete = wrapper;
  
    binding.read(fd, buffer, offset, length, position, req);
  }
  
  ObjectDefineProperty(read, internalUtil.customPromisifyArgs,
                       { value: ['bytesRead', 'buffer'], enumerable: false });
  
  function readSync(fd, buffer, offset, length, position) {
    validateInt32(fd, 'fd', 0);
    validateBuffer(buffer);
  
    if (offset == null) {
      offset = 0;
    } else {
      validateInteger(offset, 'offset');
    }
  
    length |= 0;
  
    if (length === 0) {
      return 0;
    }
  
    if (buffer.byteLength === 0) {
      throw new ERR_INVALID_ARG_VALUE('buffer', buffer,
                                      'is empty and cannot be written');
    }
  
    validateOffsetLengthRead(offset, length, buffer.byteLength);
  
    if (!NumberIsSafeInteger(position))
      position = -1;
  
    const ctx = {};
    const result = binding.read(fd, buffer, offset, length, position,
                                undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  }
  
  // usage:
  //  fs.write(fd, buffer[, offset[, length[, position]]], callback);
  // OR
  //  fs.write(fd, string[, position[, encoding]], callback);
  function write(fd, buffer, offset, length, position, callback) {
    function wrapper(err, written) {
      // Retain a reference to buffer so that it can't be GC'ed too soon.
      callback(err, written || 0, buffer);
    }
  
    validateInt32(fd, 'fd', 0);
  
    const req = new FSReqCallback();
    req.oncomplete = wrapper;
  
    if (isArrayBufferView(buffer)) {
      callback = maybeCallback(callback || position || length || offset);
      if (offset == null || typeof offset === 'function') {
        offset = 0;
      } else {
        validateInteger(offset, 'offset');
      }
      if (typeof length !== 'number')
        length = buffer.length - offset;
      if (typeof position !== 'number')
        position = null;
      validateOffsetLengthWrite(offset, length, buffer.byteLength);
      return binding.writeBuffer(fd, buffer, offset, length, position, req);
    }
  
    if (typeof buffer !== 'string')
      buffer += '';
    if (typeof position !== 'function') {
      if (typeof offset === 'function') {
        position = offset;
        offset = null;
      } else {
        position = length;
      }
      length = 'utf8';
    }
    callback = maybeCallback(position);
    return binding.writeString(fd, buffer, offset, length, req);
  }
  
  ObjectDefineProperty(write, internalUtil.customPromisifyArgs,
                       { value: ['bytesWritten', 'buffer'], enumerable: false });
  
  // Usage:
  //  fs.writeSync(fd, buffer[, offset[, length[, position]]]);
  // OR
  //  fs.writeSync(fd, string[, position[, encoding]]);
  function writeSync(fd, buffer, offset, length, position) {
    validateInt32(fd, 'fd', 0);
    const ctx = {};
    let result;
    if (isArrayBufferView(buffer)) {
      if (position === undefined)
        position = null;
      if (offset == null) {
        offset = 0;
      } else {
        validateInteger(offset, 'offset');
      }
      if (typeof length !== 'number')
        length = buffer.byteLength - offset;
      validateOffsetLengthWrite(offset, length, buffer.byteLength);
      result = binding.writeBuffer(fd, buffer, offset, length, position,
                                   undefined, ctx);
    } else {
      if (typeof buffer !== 'string')
        buffer += '';
      if (offset === undefined)
        offset = null;
      result = binding.writeString(fd, buffer, offset, length,
                                   undefined, ctx);
    }
    handleErrorFromBinding(ctx);
    return result;
  }
  
  // usage:
  // fs.writev(fd, buffers[, position], callback);
  function writev(fd, buffers, position, callback) {
    function wrapper(err, written) {
      callback(err, written || 0, buffers);
    }
  
    validateInt32(fd, 'fd', 0);
    validateBufferArray(buffers);
  
    const req = new FSReqCallback();
    req.oncomplete = wrapper;
  
    callback = maybeCallback(callback || position);
  
    if (typeof position !== 'number')
      position = null;
  
    return binding.writeBuffers(fd, buffers, position, req);
  }
  
  ObjectDefineProperty(writev, internalUtil.customPromisifyArgs, {
    value: ['bytesWritten', 'buffer'],
    enumerable: false
  });
  
  function writevSync(fd, buffers, position) {
    validateInt32(fd, 'fd', 0);
    validateBufferArray(buffers);
  
    const ctx = {};
  
    if (typeof position !== 'number')
      position = null;
  
    const result = binding.writeBuffers(fd, buffers, position, undefined, ctx);
  
    handleErrorFromBinding(ctx);
    return result;
  }
  
  function rename(oldPath, newPath, callback) {
    callback = makeCallback(callback);
    oldPath = getValidatedPath(oldPath, 'oldPath');
    newPath = getValidatedPath(newPath, 'newPath');
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.rename(pathModule.toNamespacedPath(oldPath),
                   pathModule.toNamespacedPath(newPath),
                   req);
  }
  
  function renameSync(oldPath, newPath) {
    oldPath = getValidatedPath(oldPath, 'oldPath');
    newPath = getValidatedPath(newPath, 'newPath');
    const ctx = { path: oldPath, dest: newPath };
    binding.rename(pathModule.toNamespacedPath(oldPath),
                   pathModule.toNamespacedPath(newPath), undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function truncate(path, len, callback) {
    if (typeof path === 'number') {
      showTruncateDeprecation();
      return fs.ftruncate(path, len, callback);
    }
    if (typeof len === 'function') {
      callback = len;
      len = 0;
    } else if (len === undefined) {
      len = 0;
    }
  
    validateInteger(len, 'len');
    callback = maybeCallback(callback);
    fs.open(path, 'r+', (er, fd) => {
      if (er) return callback(er);
      const req = new FSReqCallback();
      req.oncomplete = function oncomplete(er) {
        fs.close(fd, (er2) => {
          callback(er || er2);
        });
      };
      binding.ftruncate(fd, len, req);
    });
  }
  
  function truncateSync(path, len) {
    if (typeof path === 'number') {
      // legacy
      showTruncateDeprecation();
      return fs.ftruncateSync(path, len);
    }
    if (len === undefined) {
      len = 0;
    }
    // Allow error to be thrown, but still close fd.
    const fd = fs.openSync(path, 'r+');
    let ret;
  
    try {
      ret = fs.ftruncateSync(fd, len);
    } finally {
      fs.closeSync(fd);
    }
    return ret;
  }
  
  function ftruncate(fd, len = 0, callback) {
    if (typeof len === 'function') {
      callback = len;
      len = 0;
    }
    validateInt32(fd, 'fd', 0);
    validateInteger(len, 'len');
    len = MathMax(0, len);
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.ftruncate(fd, len, req);
  }
  
  function ftruncateSync(fd, len = 0) {
    validateInt32(fd, 'fd', 0);
    validateInteger(len, 'len');
    len = MathMax(0, len);
    const ctx = {};
    binding.ftruncate(fd, len, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  
  function lazyLoadRimraf() {
    if (rimraf === undefined)
      ({ rimraf, rimrafSync } = require('internal/fs/rimraf'));
  }
  
  function rmdir(path, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = undefined;
    }
  
    callback = makeCallback(callback);
    path = pathModule.toNamespacedPath(getValidatedPath(path));
    options = validateRmdirOptions(options);
  
    if (options.recursive) {
      lazyLoadRimraf();
      return rimraf(path, options, callback);
    }
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.rmdir(path, req);
  }
  
  function rmdirSync(path, options) {
    path = getValidatedPath(path);
    options = validateRmdirOptions(options);
  
    if (options.recursive) {
      lazyLoadRimraf();
      return rimrafSync(pathModule.toNamespacedPath(path), options);
    }
  
    const ctx = { path };
    binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function fdatasync(fd, callback) {
    validateInt32(fd, 'fd', 0);
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.fdatasync(fd, req);
  }
  
  function fdatasyncSync(fd) {
    validateInt32(fd, 'fd', 0);
    const ctx = {};
    binding.fdatasync(fd, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function fsync(fd, callback) {
    validateInt32(fd, 'fd', 0);
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.fsync(fd, req);
  }
  
  function fsyncSync(fd) {
    validateInt32(fd, 'fd', 0);
    const ctx = {};
    binding.fsync(fd, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function mkdir(path, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    } else if (typeof options === 'number' || typeof options === 'string') {
      options = { mode: options };
    }
    const {
      recursive = false,
      mode = 0o777
    } = options || {};
    callback = makeCallback(callback);
    path = getValidatedPath(path);
  
    if (typeof recursive !== 'boolean')
      throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.mkdir(pathModule.toNamespacedPath(path),
                  parseMode(mode, 'mode', 0o777), recursive, req);
  }
  
  function mkdirSync(path, options) {
    if (typeof options === 'number' || typeof options === 'string') {
      options = { mode: options };
    }
    const {
      recursive = false,
      mode = 0o777
    } = options || {};
  
    path = getValidatedPath(path);
    if (typeof recursive !== 'boolean')
      throw new ERR_INVALID_ARG_TYPE('recursive', 'boolean', recursive);
  
    const ctx = { path };
    const result = binding.mkdir(pathModule.toNamespacedPath(path),
                                 parseMode(mode, 'mode', 0o777), recursive,
                                 undefined, ctx);
    handleErrorFromBinding(ctx);
    if (recursive) {
      return result;
    }
  }
  
  function readdir(path, options, callback) {
    callback = makeCallback(typeof options === 'function' ? options : callback);
    options = getOptions(options, {});
    path = getValidatedPath(path);
  
    const req = new FSReqCallback();
    if (!options.withFileTypes) {
      req.oncomplete = callback;
    } else {
      req.oncomplete = (err, result) => {
        if (err) {
          callback(err);
          return;
        }
        getDirents(path, result, callback);
      };
    }
    binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
                    !!options.withFileTypes, req);
  }
  
  function readdirSync(path, options) {
    options = getOptions(options, {});
    path = getValidatedPath(path);
    const ctx = { path };
    const result = binding.readdir(pathModule.toNamespacedPath(path),
                                   options.encoding, !!options.withFileTypes,
                                   undefined, ctx);
    handleErrorFromBinding(ctx);
    return options.withFileTypes ? getDirents(path, result) : result;
  }
  
  function fstat(fd, options = { bigint: false }, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    validateInt32(fd, 'fd', 0);
    const req = new FSReqCallback(options.bigint);
    req.oncomplete = makeStatsCallback(callback);
    binding.fstat(fd, options.bigint, req);
  }
  
  function lstat(path, options = { bigint: false }, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    callback = makeStatsCallback(callback);
    path = getValidatedPath(path);
    const req = new FSReqCallback(options.bigint);
    req.oncomplete = callback;
    binding.lstat(pathModule.toNamespacedPath(path), options.bigint, req);
  }
  
  function stat(path, options = { bigint: false }, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
    callback = makeStatsCallback(callback);
    path = getValidatedPath(path);
    const req = new FSReqCallback(options.bigint);
    req.oncomplete = callback;
    binding.stat(pathModule.toNamespacedPath(path), options.bigint, req);
  }
  
  function fstatSync(fd, options = { bigint: false }) {
    validateInt32(fd, 'fd', 0);
    const ctx = { fd };
    const stats = binding.fstat(fd, options.bigint, undefined, ctx);
    handleErrorFromBinding(ctx);
    return getStatsFromBinding(stats);
  }
  
  function lstatSync(path, options = { bigint: false }) {
    path = getValidatedPath(path);
    const ctx = { path };
    const stats = binding.lstat(pathModule.toNamespacedPath(path),
                                options.bigint, undefined, ctx);
    handleErrorFromBinding(ctx);
    return getStatsFromBinding(stats);
  }
  
  function statSync(path, options = { bigint: false }) {
    path = getValidatedPath(path);
    const ctx = { path };
    const stats = binding.stat(pathModule.toNamespacedPath(path),
                               options.bigint, undefined, ctx);
    handleErrorFromBinding(ctx);
    return getStatsFromBinding(stats);
  }
  
  function readlink(path, options, callback) {
    callback = makeCallback(typeof options === 'function' ? options : callback);
    options = getOptions(options, {});
    path = getValidatedPath(path, 'oldPath');
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.readlink(pathModule.toNamespacedPath(path), options.encoding, req);
  }
  
  function readlinkSync(path, options) {
    options = getOptions(options, {});
    path = getValidatedPath(path, 'oldPath');
    const ctx = { path };
    const result = binding.readlink(pathModule.toNamespacedPath(path),
                                    options.encoding, undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  }
  
  function symlink(target, path, type_, callback_) {
    const type = (typeof type_ === 'string' ? type_ : null);
    const callback = makeCallback(arguments[arguments.length - 1]);
  
    target = getValidatedPath(target, 'target');
    path = getValidatedPath(path);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
  
    if (isWindows && type === null) {
      let absoluteTarget;
      try {
        // Symlinks targets can be relative to the newly created path.
        // Calculate absolute file name of the symlink target, and check
        // if it is a directory. Ignore resolve error to keep symlink
        // errors consistent between platforms if invalid path is
        // provided.
        absoluteTarget = pathModule.resolve(path, '..', target);
      } catch { }
      if (absoluteTarget !== undefined) {
        stat(absoluteTarget, (err, stat) => {
          const resolvedType = !err && stat.isDirectory() ? 'dir' : 'file';
          const resolvedFlags = stringToSymlinkType(resolvedType);
          binding.symlink(preprocessSymlinkDestination(target,
                                                       resolvedType,
                                                       path),
                          pathModule.toNamespacedPath(path), resolvedFlags, req);
        });
        return;
      }
    }
  
    const flags = stringToSymlinkType(type);
    binding.symlink(preprocessSymlinkDestination(target, type, path),
                    pathModule.toNamespacedPath(path), flags, req);
  }
  
  function symlinkSync(target, path, type) {
    type = (typeof type === 'string' ? type : null);
    if (isWindows && type === null) {
      try {
        const absoluteTarget = pathModule.resolve(path, '..', target);
        if (statSync(absoluteTarget).isDirectory()) {
          type = 'dir';
        }
      } catch { }
    }
    target = getValidatedPath(target, 'target');
    path = getValidatedPath(path);
    const flags = stringToSymlinkType(type);
  
    const ctx = { path: target, dest: path };
    binding.symlink(preprocessSymlinkDestination(target, type, path),
                    pathModule.toNamespacedPath(path), flags, undefined, ctx);
  
    handleErrorFromBinding(ctx);
  }
  
  function link(existingPath, newPath, callback) {
    callback = makeCallback(callback);
  
    existingPath = getValidatedPath(existingPath, 'existingPath');
    newPath = getValidatedPath(newPath, 'newPath');
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
  
    binding.link(pathModule.toNamespacedPath(existingPath),
                 pathModule.toNamespacedPath(newPath),
                 req);
  }
  
  function linkSync(existingPath, newPath) {
    existingPath = getValidatedPath(existingPath, 'existingPath');
    newPath = getValidatedPath(newPath, 'newPath');
  
    const ctx = { path: existingPath, dest: newPath };
    const result = binding.link(pathModule.toNamespacedPath(existingPath),
                                pathModule.toNamespacedPath(newPath),
                                undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  }
  
  function unlink(path, callback) {
    callback = makeCallback(callback);
    path = getValidatedPath(path);
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.unlink(pathModule.toNamespacedPath(path), req);
  }
  
  function unlinkSync(path) {
    path = getValidatedPath(path);
    const ctx = { path };
    binding.unlink(pathModule.toNamespacedPath(path), undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function fchmod(fd, mode, callback) {
    validateInt32(fd, 'fd', 0);
    mode = parseMode(mode, 'mode');
    callback = makeCallback(callback);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.fchmod(fd, mode, req);
  }
  
  function fchmodSync(fd, mode) {
    validateInt32(fd, 'fd', 0);
    mode = parseMode(mode, 'mode');
    const ctx = {};
    binding.fchmod(fd, mode, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function lchmod(path, mode, callback) {
    callback = maybeCallback(callback);
    fs.open(path, O_WRONLY | O_SYMLINK, (err, fd) => {
      if (err) {
        callback(err);
        return;
      }
      // Prefer to return the chmod error, if one occurs,
      // but still try to close, and report closing errors if they occur.
      fs.fchmod(fd, mode, (err) => {
        fs.close(fd, (err2) => {
          callback(err || err2);
        });
      });
    });
  }
  
  function lchmodSync(path, mode) {
    const fd = fs.openSync(path, O_WRONLY | O_SYMLINK);
  
    // Prefer to return the chmod error, if one occurs,
    // but still try to close, and report closing errors if they occur.
    let ret;
    try {
      ret = fs.fchmodSync(fd, mode);
    } finally {
      fs.closeSync(fd);
    }
    return ret;
  }
  
  
  function chmod(path, mode, callback) {
    path = getValidatedPath(path);
    mode = parseMode(mode, 'mode');
    callback = makeCallback(callback);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.chmod(pathModule.toNamespacedPath(path), mode, req);
  }
  
  function chmodSync(path, mode) {
    path = getValidatedPath(path);
    mode = parseMode(mode, 'mode');
  
    const ctx = { path };
    binding.chmod(pathModule.toNamespacedPath(path), mode, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function lchown(path, uid, gid, callback) {
    callback = makeCallback(callback);
    path = getValidatedPath(path);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.lchown(pathModule.toNamespacedPath(path), uid, gid, req);
  }
  
  function lchownSync(path, uid, gid) {
    path = getValidatedPath(path);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
    const ctx = { path };
    binding.lchown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function fchown(fd, uid, gid, callback) {
    validateInt32(fd, 'fd', 0);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
  
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.fchown(fd, uid, gid, req);
  }
  
  function fchownSync(fd, uid, gid) {
    validateInt32(fd, 'fd', 0);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
  
    const ctx = {};
    binding.fchown(fd, uid, gid, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function chown(path, uid, gid, callback) {
    callback = makeCallback(callback);
    path = getValidatedPath(path);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.chown(pathModule.toNamespacedPath(path), uid, gid, req);
  }
  
  function chownSync(path, uid, gid) {
    path = getValidatedPath(path);
    validateInteger(uid, 'uid', -1, kMaxUserId);
    validateInteger(gid, 'gid', -1, kMaxUserId);
    const ctx = { path };
    binding.chown(pathModule.toNamespacedPath(path), uid, gid, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function utimes(path, atime, mtime, callback) {
    callback = makeCallback(callback);
    path = getValidatedPath(path);
  
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.utimes(pathModule.toNamespacedPath(path),
                   toUnixTimestamp(atime),
                   toUnixTimestamp(mtime),
                   req);
  }
  
  function utimesSync(path, atime, mtime) {
    path = getValidatedPath(path);
    const ctx = { path };
    binding.utimes(pathModule.toNamespacedPath(path),
                   toUnixTimestamp(atime), toUnixTimestamp(mtime),
                   undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function futimes(fd, atime, mtime, callback) {
    validateInt32(fd, 'fd', 0);
    atime = toUnixTimestamp(atime, 'atime');
    mtime = toUnixTimestamp(mtime, 'mtime');
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.futimes(fd, atime, mtime, req);
  }
  
  function futimesSync(fd, atime, mtime) {
    validateInt32(fd, 'fd', 0);
    atime = toUnixTimestamp(atime, 'atime');
    mtime = toUnixTimestamp(mtime, 'mtime');
    const ctx = {};
    binding.futimes(fd, atime, mtime, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function writeAll(fd, isUserFd, buffer, offset, length, callback) {
    // write(fd, buffer, offset, length, position, callback)
    fs.write(fd, buffer, offset, length, null, (writeErr, written) => {
      if (writeErr) {
        if (isUserFd) {
          callback(writeErr);
        } else {
          fs.close(fd, function close() {
            callback(writeErr);
          });
        }
      } else if (written === length) {
        if (isUserFd) {
          callback(null);
        } else {
          fs.close(fd, callback);
        }
      } else {
        offset += written;
        length -= written;
        writeAll(fd, isUserFd, buffer, offset, length, callback);
      }
    });
  }
  
  function writeFile(path, data, options, callback) {
    callback = maybeCallback(callback || options);
    options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
    const flag = options.flag || 'w';
  
    if (isFd(path)) {
      writeFd(path, true);
      return;
    }
  
    fs.open(path, flag, options.mode, (openErr, fd) => {
      if (openErr) {
        callback(openErr);
      } else {
        writeFd(fd, false);
      }
    });
  
    function writeFd(fd, isUserFd) {
      const buffer = isArrayBufferView(data) ?
        data : Buffer.from('' + data, options.encoding || 'utf8');
  
      writeAll(fd, isUserFd, buffer, 0, buffer.byteLength, callback);
    }
  }
  
  function writeFileSync(path, data, options) {
    options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'w' });
    const flag = options.flag || 'w';
  
    const isUserFd = isFd(path); // File descriptor ownership
    const fd = isUserFd ? path : fs.openSync(path, flag, options.mode);
  
    if (!isArrayBufferView(data)) {
      data = Buffer.from('' + data, options.encoding || 'utf8');
    }
    let offset = 0;
    let length = data.byteLength;
    try {
      while (length > 0) {
        const written = fs.writeSync(fd, data, offset, length);
        offset += written;
        length -= written;
      }
    } finally {
      if (!isUserFd) fs.closeSync(fd);
    }
  }
  
  function appendFile(path, data, options, callback) {
    callback = maybeCallback(callback || options);
    options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
  
    // Don't make changes directly on options object
    options = copyObject(options);
  
    // Force append behavior when using a supplied file descriptor
    if (!options.flag || isFd(path))
      options.flag = 'a';
  
    fs.writeFile(path, data, options, callback);
  }
  
  function appendFileSync(path, data, options) {
    options = getOptions(options, { encoding: 'utf8', mode: 0o666, flag: 'a' });
  
    // Don't make changes directly on options object
    options = copyObject(options);
  
    // Force append behavior when using a supplied file descriptor
    if (!options.flag || isFd(path))
      options.flag = 'a';
  
    fs.writeFileSync(path, data, options);
  }
  
  function watch(filename, options, listener) {
    if (typeof options === 'function') {
      listener = options;
    }
    options = getOptions(options, {});
  
    // Don't make changes directly on options object
    options = copyObject(options);
  
    if (options.persistent === undefined) options.persistent = true;
    if (options.recursive === undefined) options.recursive = false;
  
    if (!watchers)
      watchers = require('internal/fs/watchers');
    const watcher = new watchers.FSWatcher();
    watcher[watchers.kFSWatchStart](filename,
                                    options.persistent,
                                    options.recursive,
                                    options.encoding);
  
    if (listener) {
      watcher.addListener('change', listener);
    }
  
    return watcher;
  }
  
  
  const statWatchers = new Map();
  
  function watchFile(filename, options, listener) {
    filename = getValidatedPath(filename);
    filename = pathModule.resolve(filename);
    let stat;
  
    if (options === null || typeof options !== 'object') {
      listener = options;
      options = null;
    }
  
    options = {
      // Poll interval in milliseconds. 5007 is what libev used to use. It's
      // a little on the slow side but let's stick with it for now to keep
      // behavioral changes to a minimum.
      interval: 5007,
      persistent: true,
      ...options
    };
  
    if (typeof listener !== 'function') {
      throw new ERR_INVALID_ARG_TYPE('listener', 'Function', listener);
    }
  
    stat = statWatchers.get(filename);
  
    if (stat === undefined) {
      if (!watchers)
        watchers = require('internal/fs/watchers');
      stat = new watchers.StatWatcher(options.bigint);
      stat.start(filename, options.persistent, options.interval);
      statWatchers.set(filename, stat);
    }
  
    stat.addListener('change', listener);
    return stat;
  }
  
  function unwatchFile(filename, listener) {
    filename = getValidatedPath(filename);
    filename = pathModule.resolve(filename);
    const stat = statWatchers.get(filename);
  
    if (stat === undefined) return;
  
    if (typeof listener === 'function') {
      stat.removeListener('change', listener);
    } else {
      stat.removeAllListeners('change');
    }
  
    if (stat.listenerCount('change') === 0) {
      stat.stop();
      statWatchers.delete(filename);
    }
  }
  
  
  let splitRoot;
  if (isWindows) {
    // Regex to find the device root on Windows (e.g. 'c:\\'), including trailing
    // slash.
    const splitRootRe = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/][^\\/]+)?[\\/]*/;
    splitRoot = function splitRoot(str) {
      return splitRootRe.exec(str)[0];
    };
  } else {
    splitRoot = function splitRoot(str) {
      for (let i = 0; i < str.length; ++i) {
        if (str.charCodeAt(i) !== CHAR_FORWARD_SLASH)
          return str.slice(0, i);
      }
      return str;
    };
  }
  
  function encodeRealpathResult(result, options) {
    if (!options || !options.encoding || options.encoding === 'utf8')
      return result;
    const asBuffer = Buffer.from(result);
    if (options.encoding === 'buffer') {
      return asBuffer;
    } else {
      return asBuffer.toString(options.encoding);
    }
  }
  
  // Finds the next portion of a (partial) path, up to the next path delimiter
  let nextPart;
  if (isWindows) {
    nextPart = function nextPart(p, i) {
      for (; i < p.length; ++i) {
        const ch = p.charCodeAt(i);
  
        // Check for a separator character
        if (ch === CHAR_BACKWARD_SLASH || ch === CHAR_FORWARD_SLASH)
          return i;
      }
      return -1;
    };
  } else {
    nextPart = function nextPart(p, i) { return p.indexOf('/', i); };
  }
  
  const emptyObj = ObjectCreate(null);
  function realpathSync(p, options) {
    if (!options)
      options = emptyObj;
    else
      options = getOptions(options, emptyObj);
    p = toPathIfFileURL(p);
    if (typeof p !== 'string') {
      p += '';
    }
    validatePath(p);
    p = pathModule.resolve(p);
  
    const cache = options[realpathCacheKey];
    const maybeCachedResult = cache && cache.get(p);
    if (maybeCachedResult) {
      return maybeCachedResult;
    }
  
    const seenLinks = ObjectCreate(null);
    const knownHard = ObjectCreate(null);
    const original = p;
  
    // Current character position in p
    let pos;
    // The partial path so far, including a trailing slash if any
    let current;
    // The partial path without a trailing slash (except when pointing at a root)
    let base;
    // The partial path scanned in the previous round, with slash
    let previous;
  
    // Skip over roots
    current = base = splitRoot(p);
    pos = current.length;
  
    // On windows, check that the root exists. On unix there is no need.
    if (isWindows && !knownHard[base]) {
      const ctx = { path: base };
      binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
      handleErrorFromBinding(ctx);
      knownHard[base] = true;
    }
  
    // Walk down the path, swapping out linked path parts for their real
    // values
    // NB: p.length changes.
    while (pos < p.length) {
      // find the next part
      const result = nextPart(p, pos);
      previous = current;
      if (result === -1) {
        const last = p.slice(pos);
        current += last;
        base = previous + last;
        pos = p.length;
      } else {
        current += p.slice(pos, result + 1);
        base = previous + p.slice(pos, result);
        pos = result + 1;
      }
  
      // Continue if not a symlink, break if a pipe/socket
      if (knownHard[base] || (cache && cache.get(base) === base)) {
        if (isFileType(statValues, S_IFIFO) ||
            isFileType(statValues, S_IFSOCK)) {
          break;
        }
        continue;
      }
  
      let resolvedLink;
      const maybeCachedResolved = cache && cache.get(base);
      if (maybeCachedResolved) {
        resolvedLink = maybeCachedResolved;
      } else {
        // Use stats array directly to avoid creating an fs.Stats instance just
        // for our internal use.
  
        const baseLong = pathModule.toNamespacedPath(base);
        const ctx = { path: base };
        const stats = binding.lstat(baseLong, false, undefined, ctx);
        handleErrorFromBinding(ctx);
  
        if (!isFileType(stats, S_IFLNK)) {
          knownHard[base] = true;
          if (cache) cache.set(base, base);
          continue;
        }
  
        // Read the link if it wasn't read before
        // dev/ino always return 0 on windows, so skip the check.
        let linkTarget = null;
        let id;
        if (!isWindows) {
          const dev = stats[0].toString(32);
          const ino = stats[7].toString(32);
          id = `${dev}:${ino}`;
          if (seenLinks[id]) {
            linkTarget = seenLinks[id];
          }
        }
        if (linkTarget === null) {
          const ctx = { path: base };
          binding.stat(baseLong, false, undefined, ctx);
          handleErrorFromBinding(ctx);
          linkTarget = binding.readlink(baseLong, undefined, undefined, ctx);
          handleErrorFromBinding(ctx);
        }
        resolvedLink = pathModule.resolve(previous, linkTarget);
  
        if (cache) cache.set(base, resolvedLink);
        if (!isWindows) seenLinks[id] = linkTarget;
      }
  
      // Resolve the link, then start over
      p = pathModule.resolve(resolvedLink, p.slice(pos));
  
      // Skip over roots
      current = base = splitRoot(p);
      pos = current.length;
  
      // On windows, check that the root exists. On unix there is no need.
      if (isWindows && !knownHard[base]) {
        const ctx = { path: base };
        binding.lstat(pathModule.toNamespacedPath(base), false, undefined, ctx);
        handleErrorFromBinding(ctx);
        knownHard[base] = true;
      }
    }
  
    if (cache) cache.set(original, p);
    return encodeRealpathResult(p, options);
  }
  
  
  realpathSync.native = (path, options) => {
    options = getOptions(options, {});
    path = getValidatedPath(path);
    const ctx = { path };
    const result = binding.realpath(path, options.encoding, undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  };
  
  
  function realpath(p, options, callback) {
    callback = typeof options === 'function' ? options : maybeCallback(callback);
    options = getOptions(options, {});
    p = toPathIfFileURL(p);
  
    if (typeof p !== 'string') {
      p += '';
    }
    validatePath(p);
    p = pathModule.resolve(p);
  
    const seenLinks = ObjectCreate(null);
    const knownHard = ObjectCreate(null);
  
    // Current character position in p
    let pos;
    // The partial path so far, including a trailing slash if any
    let current;
    // The partial path without a trailing slash (except when pointing at a root)
    let base;
    // The partial path scanned in the previous round, with slash
    let previous;
  
    current = base = splitRoot(p);
    pos = current.length;
  
    // On windows, check that the root exists. On unix there is no need.
    if (isWindows && !knownHard[base]) {
      fs.lstat(base, (err, stats) => {
        if (err) return callback(err);
        knownHard[base] = true;
        LOOP();
      });
    } else {
      process.nextTick(LOOP);
    }
  
    // Walk down the path, swapping out linked path parts for their real
    // values
    function LOOP() {
      // Stop if scanned past end of path
      if (pos >= p.length) {
        return callback(null, encodeRealpathResult(p, options));
      }
  
      // find the next part
      const result = nextPart(p, pos);
      previous = current;
      if (result === -1) {
        const last = p.slice(pos);
        current += last;
        base = previous + last;
        pos = p.length;
      } else {
        current += p.slice(pos, result + 1);
        base = previous + p.slice(pos, result);
        pos = result + 1;
      }
  
      // Continue if not a symlink, break if a pipe/socket
      if (knownHard[base]) {
        if (isFileType(statValues, S_IFIFO) ||
            isFileType(statValues, S_IFSOCK)) {
          return callback(null, encodeRealpathResult(p, options));
        }
        return process.nextTick(LOOP);
      }
  
      return fs.lstat(base, gotStat);
    }
  
    function gotStat(err, stats) {
      if (err) return callback(err);
  
      // If not a symlink, skip to the next path part
      if (!stats.isSymbolicLink()) {
        knownHard[base] = true;
        return process.nextTick(LOOP);
      }
  
      // Stat & read the link if not read before.
      // Call `gotTarget()` as soon as the link target is known.
      // `dev`/`ino` always return 0 on windows, so skip the check.
      let id;
      if (!isWindows) {
        const dev = stats.dev.toString(32);
        const ino = stats.ino.toString(32);
        id = `${dev}:${ino}`;
        if (seenLinks[id]) {
          return gotTarget(null, seenLinks[id], base);
        }
      }
      fs.stat(base, (err) => {
        if (err) return callback(err);
  
        fs.readlink(base, (err, target) => {
          if (!isWindows) seenLinks[id] = target;
          gotTarget(err, target);
        });
      });
    }
  
    function gotTarget(err, target, base) {
      if (err) return callback(err);
  
      gotResolvedLink(pathModule.resolve(previous, target));
    }
  
    function gotResolvedLink(resolvedLink) {
      // Resolve the link, then start over
      p = pathModule.resolve(resolvedLink, p.slice(pos));
      current = base = splitRoot(p);
      pos = current.length;
  
      // On windows, check that the root exists. On unix there is no need.
      if (isWindows && !knownHard[base]) {
        fs.lstat(base, (err) => {
          if (err) return callback(err);
          knownHard[base] = true;
          LOOP();
        });
      } else {
        process.nextTick(LOOP);
      }
    }
  }
  
  
  realpath.native = (path, options, callback) => {
    callback = makeCallback(callback || options);
    options = getOptions(options, {});
    path = getValidatedPath(path);
    const req = new FSReqCallback();
    req.oncomplete = callback;
    return binding.realpath(path, options.encoding, req);
  };
  
  function mkdtemp(prefix, options, callback) {
    callback = makeCallback(typeof options === 'function' ? options : callback);
    options = getOptions(options, {});
    if (!prefix || typeof prefix !== 'string') {
      throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
    }
    nullCheck(prefix, 'prefix');
    warnOnNonPortableTemplate(prefix);
    const req = new FSReqCallback();
    req.oncomplete = callback;
    binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req);
  }
  
  
  function mkdtempSync(prefix, options) {
    options = getOptions(options, {});
    if (!prefix || typeof prefix !== 'string') {
      throw new ERR_INVALID_ARG_TYPE('prefix', 'string', prefix);
    }
    nullCheck(prefix, 'prefix');
    warnOnNonPortableTemplate(prefix);
    const path = `${prefix}XXXXXX`;
    const ctx = { path };
    const result = binding.mkdtemp(path, options.encoding,
                                   undefined, ctx);
    handleErrorFromBinding(ctx);
    return result;
  }
  
  
  function copyFile(src, dest, flags, callback) {
    if (typeof flags === 'function') {
      callback = flags;
      flags = 0;
    } else if (typeof callback !== 'function') {
      throw new ERR_INVALID_CALLBACK(callback);
    }
  
    src = getValidatedPath(src, 'src');
    dest = getValidatedPath(dest, 'dest');
  
    src = pathModule._makeLong(src);
    dest = pathModule._makeLong(dest);
    flags = flags | 0;
    const req = new FSReqCallback();
    req.oncomplete = makeCallback(callback);
    binding.copyFile(src, dest, flags, req);
  }
  
  
  function copyFileSync(src, dest, flags) {
    src = getValidatedPath(src, 'src');
    dest = getValidatedPath(dest, 'dest');
  
    const ctx = { path: src, dest };  // non-prefixed
  
    src = pathModule._makeLong(src);
    dest = pathModule._makeLong(dest);
    flags = flags | 0;
    binding.copyFile(src, dest, flags, undefined, ctx);
    handleErrorFromBinding(ctx);
  }
  
  function lazyLoadStreams() {
    if (!ReadStream) {
      ({ ReadStream, WriteStream } = require('internal/fs/streams'));
      [ FileReadStream, FileWriteStream ] = [ ReadStream, WriteStream ];
    }
  }
  
  function createReadStream(path, options) {
    lazyLoadStreams();
    return new ReadStream(path, options);
  }
  
  function createWriteStream(path, options) {
    lazyLoadStreams();
    return new WriteStream(path, options);
  }
  
  module.exports = fs = {
    appendFile,
    appendFileSync,
    access,
    accessSync,
    chown,
    chownSync,
    chmod,
    chmodSync,
    close,
    closeSync,
    copyFile,
    copyFileSync,
    createReadStream,
    createWriteStream,
    exists,
    existsSync,
    fchown,
    fchownSync,
    fchmod,
    fchmodSync,
    fdatasync,
    fdatasyncSync,
    fstat,
    fstatSync,
    fsync,
    fsyncSync,
    ftruncate,
    ftruncateSync,
    futimes,
    futimesSync,
    lchown,
    lchownSync,
    lchmod: constants.O_SYMLINK !== undefined ? lchmod : undefined,
    lchmodSync: constants.O_SYMLINK !== undefined ? lchmodSync : undefined,
    link,
    linkSync,
    lstat,
    lstatSync,
    mkdir,
    mkdirSync,
    mkdtemp,
    mkdtempSync,
    open,
    openSync,
    opendir,
    opendirSync,
    readdir,
    readdirSync,
    read,
    readSync,
    readFile,
    readFileSync,
    readlink,
    readlinkSync,
    realpath,
    realpathSync,
    rename,
    renameSync,
    rmdir,
    rmdirSync,
    stat,
    statSync,
    symlink,
    symlinkSync,
    truncate,
    truncateSync,
    unwatchFile,
    unlink,
    unlinkSync,
    utimes,
    utimesSync,
    watch,
    watchFile,
    writeFile,
    writeFileSync,
    write,
    writeSync,
    writev,
    writevSync,
    Dir,
    Dirent,
    Stats,
  
    get ReadStream() {
      lazyLoadStreams();
      return ReadStream;
    },
  
    set ReadStream(val) {
      ReadStream = val;
    },
  
    get WriteStream() {
      lazyLoadStreams();
      return WriteStream;
    },
  
    set WriteStream(val) {
      WriteStream = val;
    },
  
    // Legacy names... these have to be separate because of how graceful-fs
    // (and possibly other) modules monkey patch the values.
    get FileReadStream() {
      lazyLoadStreams();
      return FileReadStream;
    },
  
    set FileReadStream(val) {
      FileReadStream = val;
    },
  
    get FileWriteStream() {
      lazyLoadStreams();
      return FileWriteStream;
    },
  
    set FileWriteStream(val) {
      FileWriteStream = val;
    },
  
    // For tests
    _toUnixTimestamp: toUnixTimestamp
  };
  
  ObjectDefineProperties(fs, {
    F_OK: { enumerable: true, value: F_OK || 0 },
    R_OK: { enumerable: true, value: R_OK || 0 },
    W_OK: { enumerable: true, value: W_OK || 0 },
    X_OK: { enumerable: true, value: X_OK || 0 },
    constants: {
      configurable: false,
      enumerable: true,
      value: constants
    },
    promises: {
      configurable: true,
      enumerable: true,
      get() {
        if (promises === null)
          promises = require('internal/fs/promises').exports;
        return promises;
      }
    }
  });
  