@ 261106.9
current
'use strict';

const {
  ArrayPrototypeForEach,
  MapPrototypeForEach,
  ObjectKeys,
  PromiseResolve,
  SafeMap,
  String,
  StringPrototypeEndsWith,
  StringPrototypeStartsWith,
} = primordials;

const { Buffer } = require('buffer');
const { isArrayBufferView } = require('internal/util/types');
const { dirname, join, sep } = require('path');
const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
const { kEmptyObject } = require('internal/util');
const { validateObject } = require('internal/validators');
const {
  codes: {
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_STATE,
    ERR_MODULE_NOT_FOUND,
  },
} = require('internal/errors');
const { createENOENT, createEROFS, createEXDEV } = require('internal/vfs/errors');
const {
  VirtualFileSystem,
  kLayerId,
  kReservedRoot,
  normalizeMountedPath,
} = require('internal/vfs/file_system');
const {
  getLayerIdFromPath,
  getNormalizedVfsRoot,
} = require('internal/vfs/router');
const { ReservedRootProvider } = require('internal/vfs/root');
const { getVirtualFd, closeVirtualFd, createVfsFileHandle } = require('internal/vfs/fd');
const {
  assertEncoding,
  encodeRealpathResult,
  getOptions,
  setVfsHandlers,
} = require('internal/fs/utils');
const permission = require('internal/process/permission');
const { getOptionValue } = require('internal/options');
const nativeModulesBinding = internalBinding('modules');
const { UV_ENOENT } = internalBinding('uv');
let debug = require('internal/util/debuglog').debuglog('vfs', (fn) => {
  debug = fn;
});

function toPathStr(pathOrUrl) {
  if (typeof pathOrUrl === 'string') return pathOrUrl;
  if (pathOrUrl instanceof URL) return fileURLToPath(pathOrUrl);
  if (Buffer.isBuffer(pathOrUrl)) return pathOrUrl.toString();
  return null;
}

function noopFdSync(fd) {
  if (getVirtualFd(fd)) return true;
  return undefined;
}

const noopFdPromise = PromiseResolve(true);
function noopFd(fd) {
  if (getVirtualFd(fd)) return noopFdPromise;
  return undefined;
}

function toWriteBuffer(data, options) {
  if (Buffer.isBuffer(data)) return data;
  if (isArrayBufferView(data)) {
    return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
  }
  const encoding = typeof options === 'string' ? options : options?.encoding;
  return Buffer.from(data, encoding || 'utf8');
}

function getMkdtempOptions(prefix, options) {
  options = getOptions(options);
  if (Buffer.isBuffer(prefix)) {
    options = { __proto__: null, ...options, encoding: 'buffer' };
  }
  return options;
}

function writeFileSyncFd(fd, data, options) {
  const vfd = getVirtualFd(fd);
  if (vfd === undefined) return undefined;

  const buffer = toWriteBuffer(data, options);
  let offset = 0;
  let length = buffer.byteLength;
  while (length > 0) {
    const written = vfd.entry.writeSync(buffer, offset, length, null);
    offset += written;
    length -= written;
  }

  return true;
}

const activeVFSLayers = new SafeMap();
// Serves the reserved root directory itself, while any layer is mounted.
let rootVFS = null;

let hooksInstalled = false;
let vfsHandlerObj;
// Lazy: os.devNull may not be available at snapshot time.
let normalizedVfsRoot = null;
let normalizedVfsRootPrefix = null;

function registerVFS(vfs) {
  // TODO(rafaelgss): publish a diagnostics channel message in audit mode.
  if (permission.isEnabled() && !permission.isAuditMode() &&
      !getOptionValue('--allow-fs-vfs')) {
    throw new ERR_INVALID_STATE(
      'VFS cannot be used when the permission model is enabled. ' +
      'Use --allow-fs-vfs to allow it.',
    );
  }
  if (activeVFSLayers.has(vfs[kLayerId])) return;
  activeVFSLayers.set(vfs[kLayerId], vfs);
  debug('register layer=%d mount=%s active=%d',
        vfs[kLayerId], vfs.mountPoint, activeVFSLayers.size);
  if (!hooksInstalled) {
    installHooks();
  }
}

function deregisterVFS(vfs) {
  if (!activeVFSLayers.delete(vfs[kLayerId])) return;
  debug('deregister layer=%d active=%d', vfs[kLayerId], activeVFSLayers.size);
  purgeLoaderCachesForVFS(vfs);
  if (activeVFSLayers.size === 0) {
    uninstallHooks();
  }
}

/**
 * Resolves a path string to the reserved VFS root, or null for a path
 * other than the root itself and those under it. Ownership is decidable
 * from the path alone: all mount points live under the reserved
 * `${os.devNull}/vfs/<id>` namespace, so a single prefix comparison
 * rejects every real-file-system path and a map lookup finds the owning
 * layer. The normalized path is returned
 * alongside the layer so downstream helpers can skip renormalization.
 *
 * The root itself, and a path under it that no active layer owns, come
 * back with `vfs: null` rather than as `null`, because the two cases must not be
 * treated alike by the module loader. The loader manufactures such
 * paths itself: resolving a mount point as a directory first probes the
 * sibling names `<mount>.js`, `<mount>.json`, ..., and a package.json
 * walk-up passes the parents of the mount point. They cannot name
 * anything real, but on Windows the root sits under `\\.\nul`, and
 * `\\.\nul\<anything>` opens the NUL device, which stats as a character
 * device and reads as empty. Handed to the native loader, such a probe
 * "finds" a file and the walk-up above it rejects the empty device as an
 * invalid package.json, so the loader must answer for the whole root.
 * @param {string} inputPath
 * @returns {{ vfs: object|null, normalized: string }|null}
 */
function findVFSOrRoot(inputPath) {
  const normalized = normalizeMountedPath(inputPath);
  if (normalized !== normalizedVfsRoot &&
      !StringPrototypeStartsWith(normalized, normalizedVfsRootPrefix)) {
    return null;
  }
  const layerId = getLayerIdFromPath(normalized);
  const vfs = layerId === -1 ? undefined : activeVFSLayers.get(layerId);
  if (vfs === undefined || !vfs.shouldHandleNormalized(normalized)) {
    return { vfs: null, normalized };
  }
  return { vfs, normalized };
}

/**
 * Resolves a path string to the VFS that serves it for the fs functions,
 * or null for a path outside the reserved VFS root. Unlike the loader, the
 * fs functions see the root as a directory, so a path under the root that
 * no layer serves is the root file system's to answer.
 * @param {string} inputPath
 * @returns {{ vfs: object, normalized: string }|null}
 */
function findVFS(inputPath) {
  const r = findVFSOrRoot(inputPath);
  if (r === null) return null;
  if (r.vfs === null) r.vfs = rootVFS;
  return r;
}

/**
 * Drop the cache entries under `vfs`'s mount point from the
 * JS-reachable loader caches. Real-fs entries and other-VFS entries
 * are left in place. Because mount points cannot collide with real
 * paths - and symlink resolution inside a VFS always yields paths
 * under the same mount point - a prefix scan is exact.
 * @param {object} vfs The VFS being deregistered
 */
function purgeLoaderCachesForVFS(vfs) {
  const mountPoint = vfs.mountPoint;

  const cjsLoader = require('internal/modules/cjs/loader');
  cjsLoader.purgeModuleCachesForPrefix(mountPoint);

  const helpers = require('internal/modules/helpers');
  helpers.purgeRealpathCacheForPrefix(mountPoint);

  const pkgReader = require('internal/modules/package_json_reader');
  pkgReader.purgePackageJSONCacheForPrefix(mountPoint);

  const esmLoader = require('internal/modules/esm/loader');
  if (esmLoader.isCascadedLoaderInitialized()) {
    const loader = esmLoader.getOrInitializeCascadedLoader();
    const loadCache = loader.loadCache;
    const mountURL = pathToFileURL(mountPoint).href;
    const mountURLPrefix = mountURL + '/';
    // Iterate via MapPrototypeForEach (not for-of map.keys()) so a
    // polluted Map iterator can't break the cleanup path.
    if (loadCache && typeof loadCache.delete === 'function') {
      MapPrototypeForEach(loadCache, (variants, url) => {
        if (typeof url === 'string' &&
            (url === mountURL ||
             StringPrototypeStartsWith(url, mountURLPrefix)) &&
            variants) {
          ArrayPrototypeForEach(ObjectKeys(variants), (type) => {
            loadCache.delete(url, type);
          });
        }
      });
    }
  }
}

/**
 * Returns the stat result code for a VFS path.
 * @param {object} vfs The VFS instance
 * @param {string} filePath The path to check
 * @returns {number} 0 for file, 1 for directory, -2 for not found
 */
function vfsStat(vfs, filePath) {
  try {
    const stats = vfs.statSync(filePath);
    if (stats.isDirectory()) return 1;
    return 0;
  } catch (err) {
    if (typeof err?.errno === 'number') return err.errno;
    throw err;
  }
}

/**
 * Finds the VFS owning `filename` and stats it.
 * @param {string} filename The absolute path to check
 * @returns {{ vfs: object, result: number }|null}
 */
function findVFSForStat(filename) {
  const r = findVFS(filename);
  if (r === null) return null;
  return { vfs: r.vfs, result: vfsStat(r.vfs, filename) };
}

/**
 * Reads `filename` from the VFS that owns it, reporting a missing file
 * or a directory the way the native loader's read does.
 * @param {object} vfs The VFS owning filename
 * @param {string} filename The absolute path to read
 * @param {string|object} options Read options
 * @returns {Buffer|string}
 */
function readVFS(vfs, filename, options) {
  try {
    return vfs.readFileSync(filename, options);
  } catch (e) {
    const code = e?.code;
    if (code === 'ENOENT' || code === 'EISDIR') {
      throw createENOENT('open', filename);
    }
    throw e;
  }
}

/**
 * Walk up directories inside `vfs`'s mount looking for package.json.
 * Always returns an object. When a package.json is found `.tuple` is
 * populated with the C++ binding's serialized shape
 * (`[name, main, type, imports, exports, filePath]`); otherwise
 * `.sentinel` is the last candidate path checked - matches the "not
 * found" marker the C++ binding returns for getPackageScopeConfig.
 * Syntactically malformed package.json aborts the walk with
 * ERR_INVALID_PACKAGE_CONFIG (thrown by the native parser), matching
 * native GetPackageJSON.
 * @param {object} vfs The VFS that owns startPath
 * @param {string} startPath Absolute path to start from
 * @param {string} normalizedStart Same as startPath but passed through
 *   `normalizeMountedPath`. Used to walk the directory chain so the
 *   containment check is a plain string prefix rather than a fresh
 *   `toNamespacedPath(resolve(...))` on every iteration.
 * @returns {{ vfs?: object, pjsonPath?: string, tuple?: Array, sentinel: string }}
 */
function findVFSPackageJSON(vfs, startPath, normalizedStart) {
  let currentDir = dirname(startPath);
  let currentNorm = dirname(normalizedStart);
  let lastDir;
  let sentinel = join(currentDir, 'package.json');
  while (currentDir !== lastDir) {
    if (StringPrototypeEndsWith(currentDir, '/node_modules') ||
        StringPrototypeEndsWith(currentDir, '\\node_modules')) {
      break;
    }
    if (!vfs.shouldHandleNormalized(currentNorm)) {
      break;
    }
    const pjsonPath = join(currentDir, 'package.json');
    sentinel = pjsonPath;
    if (vfsStat(vfs, pjsonPath) === 0) {
      let content;
      try {
        content = vfs.readFileSync(pjsonPath);
      } catch {
        content = null;
      }
      if (content !== null) {
        const tuple = nativeModulesBinding.parsePackageJSON(
          content, pjsonPath);
        return { vfs, pjsonPath, tuple, sentinel: pjsonPath };
      }
    }
    lastDir = currentDir;
    currentDir = dirname(currentDir);
    currentNorm = dirname(currentNorm);
  }
  return { sentinel };
}

function findVFSForExists(filename) {
  const r = findVFS(filename);
  if (r === null) return null;
  return { vfs: r.vfs, exists: r.vfs.existsSync(filename) };
}

function findVFSForPath(filename) {
  const r = findVFS(filename);
  if (r === null) return null;
  return { vfs: r.vfs, path: filename };
}

function findVFSWith(filename, syscall, fn) {
  const r = findVFS(filename);
  if (r === null) return undefined;
  if (r.vfs.existsSync(filename)) {
    if (fn === undefined) return true;
    return fn(r.vfs, filename);
  }
  throw createENOENT(syscall, filename);
}

function vfsRead(path, syscall, fn) {
  const pathStr = toPathStr(path);
  if (pathStr === null) return undefined;
  return findVFSWith(pathStr, syscall, fn);
}

function vfsOp(path, fn) {
  const pathStr = toPathStr(path);
  if (pathStr !== null) {
    const r = findVFSForPath(pathStr);
    if (r !== null) return fn(r.vfs, r.path);
  }
  return undefined;
}

function vfsOpVoid(path, fn) {
  const pathStr = toPathStr(path);
  if (pathStr !== null) {
    const r = findVFSForPath(pathStr);
    if (r !== null) { fn(r.vfs, r.path); return true; }
  }
  return undefined;
}

function checkSameVFS(srcPath, destPath, syscall, srcVfs) {
  if (findVFS(destPath)?.vfs !== srcVfs) {
    throw createEXDEV(syscall, srcPath);
  }
}

// Nothing in the root directory can be removed, and a recursive removal of
// it would walk into the layers' mount points, which only the layers serve,
// so it is refused before it starts.
function rmRootSync(path, options) {
  try {
    rootVFS.lstatSync(path);
  } catch (err) {
    if (options?.force === true && err?.code === 'ENOENT') return;
    throw err;
  }
  throw createEROFS('rm', path);
}

function createVfsHandlers() {
  return {
    __proto__: null,

    existsSync(path) {
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const r = findVFSForExists(pathStr);
      return r !== null ? r.exists : undefined;
    },
    readFileSync(path, options) {
      if (typeof path === 'number') {
        const vfd = getVirtualFd(path);
        if (vfd) {
          const enc = typeof options === 'string' ? options : options?.encoding;
          if (enc && enc !== 'buffer') assertEncoding(enc);
          return vfd.entry.readFileSync(options);
        }
        return undefined;
      }
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const enc = typeof options === 'string' ? options : options?.encoding;
      if (enc && enc !== 'buffer') assertEncoding(enc);
      return findVFSWith(pathStr, 'open', (vfs, n) => vfs.readFileSync(n, options));
    },
    readdirSync(path, options) {
      const result = vfsRead(path, 'scandir', (vfs, n) => vfs.readdirSync(n, options));
      if (result !== undefined && options?.encoding === 'buffer' && !options?.withFileTypes) {
        for (let i = 0; i < result.length; i++) {
          if (typeof result[i] === 'string') result[i] = Buffer.from(result[i]);
        }
      }
      return result;
    },
    lstatSync(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const r = findVFS(pathStr);
      if (r === null) return undefined;
      return r.vfs.lstatSync(pathStr, options);
    },
    statSync(path, options) {
      return vfsRead(path, 'stat', (vfs, n) => vfs.statSync(n, options));
    },
    realpathSync(path, options) {
      return vfsRead(path, 'realpath', (vfs, n) => vfs.realpathSync(n, options));
    },
    accessSync(path, mode) {
      const pathStr = toPathStr(path);
      if (pathStr !== null) {
        const r = findVFSForPath(pathStr);
        if (r !== null) {
          if (mode != null && typeof mode !== 'number') {
            throw new ERR_INVALID_ARG_TYPE('mode', 'integer', mode);
          }
          r.vfs.accessSync(r.path, mode);
          return true;
        }
      }
      return undefined;
    },
    readlinkSync(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const r = findVFS(pathStr);
      if (r === null) return undefined;
      return r.vfs.readlinkSync(pathStr, options);
    },
    statfsSync(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr !== null && findVFSWith(pathStr, 'statfs')) {
        if (options?.bigint) {
          return {
            type: 0n, bsize: 4096n, blocks: 0n,
            bfree: 0n, bavail: 0n, files: 0n, ffree: 0n,
          };
        }
        return { type: 0, bsize: 4096, blocks: 0, bfree: 0, bavail: 0, files: 0, ffree: 0 };
      }
      return undefined;
    },

    writeFileSync(path, data, options) {
      if (typeof path === 'number') return writeFileSyncFd(path, data, options);
      return vfsOpVoid(path, (vfs, n) => vfs.writeFileSync(n, data, options));
    },
    appendFileSync(path, data, options) {
      if (typeof path === 'number') return writeFileSyncFd(path, data, options);
      return vfsOpVoid(path, (vfs, n) => vfs.appendFileSync(n, data, options));
    },
    mkdirSync: (path, options) =>
      vfsOp(path, (vfs, n) => ({ result: vfs.mkdirSync(n, options) })),
    rmdirSync: (path) => vfsOpVoid(path, (vfs, n) => vfs.rmdirSync(n)),
    rmSync(path, options) {
      return vfsOpVoid(path, (vfs, n) => {
        if (vfs === rootVFS) rmRootSync(n, options);
        else vfs.rmSync(n, options);
      });
    },
    unlinkSync: (path) => vfsOpVoid(path, (vfs, n) => vfs.unlinkSync(n)),
    renameSync(oldPath, newPath) {
      return vfsOpVoid(oldPath, (vfs, n) => {
        const destStr = toPathStr(newPath);
        checkSameVFS(n, destStr, 'rename', vfs);
        vfs.renameSync(n, destStr);
      });
    },
    copyFileSync(src, dest, mode) {
      return vfsOpVoid(src, (vfs, n) => {
        const destStr = toPathStr(dest);
        checkSameVFS(n, destStr, 'copyfile', vfs);
        vfs.copyFileSync(n, destStr, mode);
      });
    },
    symlinkSync: (target, path, type) =>
      vfsOpVoid(path, (vfs, n) => vfs.symlinkSync(target, n, type)),
    chmodSync: (path, mode) => vfsOpVoid(path, (vfs, n) => vfs.chmodSync(n, mode)),
    chownSync: (path, uid, gid) => vfsOpVoid(path, (vfs, n) => vfs.chownSync(n, uid, gid)),
    lchownSync: (path, uid, gid) => vfsOpVoid(path, (vfs, n) => vfs.lchownSync(n, uid, gid)),
    utimesSync: (path, atime, mtime) =>
      vfsOpVoid(path, (vfs, n) => vfs.utimesSync(n, atime, mtime)),
    lutimesSync: (path, atime, mtime) =>
      vfsOpVoid(path, (vfs, n) => vfs.lutimesSync(n, atime, mtime)),
    truncateSync: (path, len) => vfsOpVoid(path, (vfs, n) => vfs.truncateSync(n, len)),
    linkSync(existingPath, newPath) {
      return vfsOpVoid(existingPath, (vfs, n) => {
        const destStr = toPathStr(newPath);
        checkSameVFS(n, destStr, 'link', vfs);
        vfs.linkSync(n, destStr);
      });
    },
    mkdtempSync(prefix, options) {
      return vfsOp(prefix, (vfs, n) => {
        options = getMkdtempOptions(prefix, options);
        return encodeRealpathResult(vfs.mkdtempSync(n), options);
      });
    },
    mkstempSync(prefix, options) {
      const result = vfsOp(prefix, (vfs, n) => vfs.mkstempSync(n));
      if (result !== undefined && options?.encoding === 'buffer') {
        result.path = Buffer.from(result.path);
      }
      return result;
    },
    opendirSync: (path, options) => vfsOp(path, (vfs, n) => vfs.opendirSync(n, options)),
    openAsBlob(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr !== null) {
        const r = findVFS(pathStr);
        if (r !== null && r.vfs.existsSync(pathStr)) {
          return r.vfs.openAsBlob(pathStr, options);
        }
      }
      return undefined;
    },
    openAsBlobSync: (path, options) =>
      vfsRead(path, 'stat', (vfs, n) => vfs.openAsBlob(n, options)),

    openSync: (path, flags, mode) => vfsOp(path, (vfs, n) => vfs.openSync(n, flags, mode)),
    closeSync(fd) {
      const vfd = getVirtualFd(fd);
      if (vfd) { vfd.entry.closeSync(); closeVirtualFd(fd); return true; }
      return undefined;
    },
    readSync(fd, buffer, offset, length, position) {
      const vfd = getVirtualFd(fd);
      if (vfd) return vfd.entry.readSync(buffer, offset, length, position);
      return undefined;
    },
    writeSync(fd, buffer, offset, length, position) {
      const vfd = getVirtualFd(fd);
      if (vfd) return vfd.entry.writeSync(buffer, offset, length, position);
      return undefined;
    },
    fstatSync(fd, options) {
      const vfd = getVirtualFd(fd);
      if (vfd) return vfd.entry.statSync(options);
      return undefined;
    },
    ftruncateSync(fd, len) {
      const vfd = getVirtualFd(fd);
      if (vfd) { vfd.entry.truncateSync(len); return true; }
      return undefined;
    },
    fchmodSync(fd, mode) {
      const vfd = getVirtualFd(fd);
      if (vfd) { vfd.entry.chmodSync(mode); return true; }
      return undefined;
    },
    fchownSync: noopFdSync,
    futimesSync(fd, atime, mtime) {
      const vfd = getVirtualFd(fd);
      if (vfd) { vfd.entry.utimesSync(atime, mtime); return true; }
      return undefined;
    },
    fdatasyncSync: noopFdSync,
    fsyncSync: noopFdSync,
    readvSync(fd, buffers, position) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      let totalRead = 0;
      for (let i = 0; i < buffers.length; i++) {
        const buf = buffers[i];
        const pos = position != null ? position + totalRead : position;
        const bytesRead = vfd.entry.readSync(buf, 0, buf.byteLength, pos);
        totalRead += bytesRead;
        if (bytesRead < buf.byteLength) break;
      }
      return totalRead;
    },
    writevSync(fd, buffers, position) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      let totalWritten = 0;
      for (let i = 0; i < buffers.length; i++) {
        const buf = buffers[i];
        const pos = position != null ? position + totalWritten : position;
        const bytesWritten = vfd.entry.writeSync(buf, 0, buf.byteLength, pos);
        totalWritten += bytesWritten;
        if (bytesWritten < buf.byteLength) break;
      }
      return totalWritten;
    },

    close(fd) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.close().then(() => { closeVirtualFd(fd); return true; });
    },
    read(fd, buffer, offset, length, position) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.read(buffer, offset, length, position)
        .then(({ bytesRead }) => bytesRead);
    },
    write(fd, buffer, offset, length, position) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.write(buffer, offset, length, position)
        .then(({ bytesWritten }) => bytesWritten);
    },
    fstat(fd, options) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.stat(options);
    },
    ftruncate(fd, len) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.truncate(len).then(() => true);
    },
    fchmod(fd, mode) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.chmod(mode).then(() => true);
    },
    fchown: noopFd,
    futimes(fd, atime, mtime) {
      const vfd = getVirtualFd(fd);
      if (!vfd) return undefined;
      return vfd.entry.utimes(atime, mtime).then(() => true);
    },
    fdatasync: noopFd,
    fsync: noopFd,

    createReadStream(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr !== null) {
        const r = findVFSForPath(pathStr);
        if (r !== null) return r.vfs.createReadStream(r.path, options);
      }
      return undefined;
    },
    createWriteStream(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr !== null) {
        const r = findVFSForPath(pathStr);
        if (r !== null) return r.vfs.createWriteStream(r.path, options);
      }
      return undefined;
    },

    watch(filename, options, listener) {
      if (typeof options === 'function') {
        listener = options;
        options = kEmptyObject;
      } else if (options != null) {
        validateObject(options, 'options');
      } else {
        options = kEmptyObject;
      }
      const pathStr = toPathStr(filename);
      if (pathStr !== null) {
        const r = findVFSForPath(pathStr);
        if (r !== null) {
          if (!r.vfs.existsSync(pathStr)) throw createENOENT('watch', pathStr);
          return r.vfs.watch(pathStr, options, listener);
        }
      }
      return undefined;
    },
    watchFile(filename, options, listener) {
      const pathStr = toPathStr(filename);
      if (pathStr === null) return undefined;
      const r = findVFSForPath(pathStr);
      if (r === null) return undefined;
      if (options === null || typeof options !== 'object') {
        listener = options;
        options = kEmptyObject;
      }
      return r.vfs.watchFile(pathStr, options, listener);
    },
    unwatchFile(filename, listener) {
      const pathStr = toPathStr(filename);
      if (pathStr === null) return undefined;
      const r = findVFSForPath(pathStr);
      if (r === null) return undefined;
      r.vfs.unwatchFile(pathStr, listener);
      return true;
    },
    promisesWatch(filename, options) {
      const pathStr = toPathStr(filename);
      if (pathStr === null) return undefined;
      const r = findVFSForPath(pathStr);
      if (r === null) return undefined;
      if (!r.vfs.existsSync(pathStr)) throw createENOENT('watch', pathStr);
      return r.vfs.promises.watch(pathStr, options);
    },

    readdir(path, options) {
      const promise = vfsOp(path, (vfs, n) => vfs.promises.readdir(n, options));
      if (promise !== undefined && options?.encoding === 'buffer' && !options?.withFileTypes) {
        return promise.then((result) => {
          for (let i = 0; i < result.length; i++) {
            if (typeof result[i] === 'string') result[i] = Buffer.from(result[i]);
          }
          return result;
        });
      }
      return promise;
    },
    lstat(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const r = findVFS(pathStr);
      if (r === null) return undefined;
      return r.vfs.promises.lstat(pathStr, options);
    },
    stat(path, options) {
      const promise = vfsOp(path, (vfs, n) => vfs.promises.stat(n, options));
      if (promise !== undefined && options?.throwIfNoEntry === false) {
        return promise.catch((err) => {
          if (err?.code === 'ENOENT') return undefined;
          throw err;
        });
      }
      return promise;
    },
    readFile(path, options) {
      if (typeof path === 'number') {
        const vfd = getVirtualFd(path);
        if (vfd) {
          const enc = typeof options === 'string' ? options : options?.encoding;
          if (enc && enc !== 'buffer') assertEncoding(enc);
          return vfd.entry.readFile(options);
        }
        return undefined;
      }
      const enc = typeof options === 'string' ? options : options?.encoding;
      if (enc && enc !== 'buffer') assertEncoding(enc);
      return vfsOp(path, (vfs, n) => vfs.promises.readFile(n, options));
    },
    realpath(path, options) {
      return vfsOp(path, (vfs, n) => vfs.promises.realpath(n, options));
    },
    access(path, mode) {
      return vfsOp(path, (vfs, n) => {
        if (mode != null && typeof mode !== 'number') {
          throw new ERR_INVALID_ARG_TYPE('mode', 'integer', mode);
        }
        return vfs.promises.access(n, mode).then(() => true);
      });
    },
    readlink(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr === null) return undefined;
      const r = findVFS(pathStr);
      if (r === null) return undefined;
      return r.vfs.promises.readlink(pathStr, options);
    },
    chown: (path, uid, gid) =>
      vfsOp(path, (vfs, n) => vfs.promises.chown(n, uid, gid).then(() => true)),
    lchown: (path, uid, gid) =>
      vfsOp(path, (vfs, n) => vfs.promises.lchown(n, uid, gid).then(() => true)),
    lutimes: (path, atime, mtime) =>
      vfsOp(path, (vfs, n) => vfs.promises.lutimes(n, atime, mtime).then(() => true)),
    statfs(path, options) {
      const pathStr = toPathStr(path);
      if (pathStr !== null && findVFSWith(pathStr, 'statfs')) {
        if (options?.bigint) {
          return {
            __proto__: null,
            type: 0n, bsize: 4096n, blocks: 0n,
            bfree: 0n, bavail: 0n, files: 0n, ffree: 0n,
          };
        }
        return {
          __proto__: null,
          type: 0, bsize: 4096, blocks: 0,
          bfree: 0, bavail: 0, files: 0, ffree: 0,
        };
      }
      return undefined;
    },
    writeFile(path, data, options) {
      return vfsOp(path, (vfs, n) => vfs.promises.writeFile(n, data, options).then(() => true));
    },
    appendFile(path, data, options) {
      return vfsOp(path, (vfs, n) => vfs.promises.appendFile(n, data, options).then(() => true));
    },
    mkdir(path, options) {
      return vfsOp(path, (vfs, n) =>
        vfs.promises.mkdir(n, options).then((result) => ({ __proto__: null, result })));
    },
    rmdir: (path) => vfsOp(path, (vfs, n) => vfs.promises.rmdir(n).then(() => true)),
    rm(path, options) {
      return vfsOp(path, async (vfs, n) => {
        if (vfs === rootVFS) rmRootSync(n, options);
        else await vfs.promises.rm(n, options);
        return true;
      });
    },
    unlink: (path) => vfsOp(path, (vfs, n) => vfs.promises.unlink(n).then(() => true)),
    rename(oldPath, newPath) {
      return vfsOp(oldPath, (vfs, n) => {
        const destStr = toPathStr(newPath);
        checkSameVFS(n, destStr, 'rename', vfs);
        return vfs.promises.rename(n, destStr).then(() => true);
      });
    },
    copyFile(src, dest, mode) {
      return vfsOp(src, (vfs, n) => {
        const destStr = toPathStr(dest);
        checkSameVFS(n, destStr, 'copyfile', vfs);
        return vfs.promises.copyFile(n, destStr, mode).then(() => true);
      });
    },
    symlink(target, path, type) {
      return vfsOp(path, (vfs, n) => vfs.promises.symlink(target, n, type).then(() => true));
    },
    truncate: (path, len) =>
      vfsOp(path, (vfs, n) => vfs.promises.truncate(n, len).then(() => true)),
    link(existingPath, newPath) {
      return vfsOp(existingPath, (vfs, n) => {
        const destStr = toPathStr(newPath);
        checkSameVFS(n, destStr, 'link', vfs);
        return vfs.promises.link(n, destStr).then(() => true);
      });
    },
    mkdtemp(prefix, options) {
      return vfsOp(prefix, (vfs, n) => {
        options = getMkdtempOptions(prefix, options);
        return vfs.promises.mkdtemp(n).then((result) => encodeRealpathResult(result, options));
      });
    },
    // Wrapped in async functions so provider throws become rejections.
    mkstemp(prefix, options) {
      return vfsOp(prefix, async (vfs, n) => {
        const { path, fd } = vfs.mkstempSync(n);
        return {
          __proto__: null,
          path: options?.encoding === 'buffer' ? Buffer.from(path) : path,
          fd,
        };
      });
    },
    promisesMkstemp(prefix, options) {
      return vfsOp(prefix, async (vfs, n) => {
        const { path, fd } = vfs.mkstempSync(n);
        return {
          __proto__: null,
          path: options?.encoding === 'buffer' ? Buffer.from(path) : path,
          handle: createVfsFileHandle(getVirtualFd(fd)),
        };
      });
    },
    chmod: (path, mode) =>
      vfsOp(path, (vfs, n) => vfs.promises.chmod(n, mode).then(() => true)),
    utimes: (path, atime, mtime) =>
      vfsOp(path, (vfs, n) => vfs.promises.utimes(n, atime, mtime).then(() => true)),
    open(path, flags, mode) {
      // Wrap openSync in an async fn so provider throws become rejections
      // instead of escaping past fs.open's callback.
      return vfsOp(path, async (vfs, n) => vfs.openSync(n, flags, mode));
    },
    promisesOpen(path, flags, mode) {
      const pathStr = toPathStr(path);
      if (pathStr !== null) {
        const r = findVFSForPath(pathStr);
        if (r !== null) {
          const fd = r.vfs.openSync(r.path, flags, mode);
          return PromiseResolve(createVfsFileHandle(getVirtualFd(fd)));
        }
      }
      return undefined;
    },
    lchmod: (path, mode) =>
      vfsOp(path, (vfs, n) => vfs.promises.lchmod(n, mode).then(() => true)),
  };
}

/**
 * Install toggleable loader overrides so that the module loader's
 * internal fs operations (stat, readFile, realpath) are redirected
 * to VFS when appropriate.
 */
function installModuleLoaderOverrides() {
  const {
    legacyMainResolveExtensions,
    legacyMainResolveExtensionsIndexes,
    kLoaderOverrideNoResult,
    setLoaderOverrides,
  } = require('internal/modules/helpers');
  const { kResolvedByMainIndexNode } = legacyMainResolveExtensionsIndexes;
  const { internal: internalConstants } = internalBinding('constants');

  // Overrides return `undefined` for paths the VFS does not own;
  // wrapLoaderMethod then falls through to the native binding.
  setLoaderOverrides({
    internalModuleStat(filename) {
      const r = findVFSOrRoot(filename);
      if (r === null) return undefined;
      return r.vfs === null ? UV_ENOENT : vfsStat(r.vfs, filename);
    },
    readFileSync(filename, options) {
      const pathStr = typeof filename === 'string' ? filename :
        (filename instanceof URL ? fileURLToPath(filename) : String(filename));
      const r = findVFSOrRoot(pathStr);
      if (r === null) return undefined;
      if (r.vfs === null) throw createENOENT('open', pathStr);
      return readVFS(r.vfs, pathStr, options);
    },
    realpathSync(filename) {
      const r = findVFSOrRoot(filename);
      if (r === null) return undefined;
      if (r.vfs === null || !r.vfs.existsSync(filename)) {
        throw createENOENT('realpath', filename);
      }
      return r.vfs.realpathSync(filename);
    },
    getResolutionRoot(pathStr) {
      const r = findVFSOrRoot(pathStr);
      if (r === null) return undefined;
      // The boundary is compared as a plain string prefix by the
      // callers, so only report it when the input carries it verbatim.
      // An unowned path stops at the reserved root itself, so no
      // node_modules lookup walks out into the real file system.
      const boundary = r.vfs === null ?
        getNormalizedVfsRoot() : r.vfs.mountPoint;
      return StringPrototypeStartsWith(pathStr, boundary) ?
        boundary : undefined;
    },
    legacyMainResolve(pkgPath, main, base) {
      if (findVFSOrRoot(pkgPath) === null) return undefined;

      for (let i = 0; i < legacyMainResolveExtensions.length; i++) {
        const byMain = i <= kResolvedByMainIndexNode;
        if (byMain && !main) continue;
        const prefix = byMain ? main : '';
        const candidate = join(pkgPath, prefix + legacyMainResolveExtensions[i]);
        if (findVFSForStat(candidate)?.result === 0) return i;
      }

      // Third arg `exactUrl` must be undefined (not a string) so the
      // message uses the "package" word and err.url is not overwritten.
      // ERR_MODULE_NOT_FOUND enforces strict arity in getMessage(), so
      // the undefined has to be passed explicitly.
      const initial = main ? join(pkgPath, main) : join(pkgPath, 'index.js');
      throw new ERR_MODULE_NOT_FOUND(initial, base, undefined);
    },
    getFormatOfExtensionlessFile(filePath) {
      const r = findVFSOrRoot(filePath);
      if (r === null) return undefined;
      let content;
      try {
        content = r.vfs === null ? null : readVFS(r.vfs, filePath, null);
      } catch {
        return internalConstants.EXTENSIONLESS_FORMAT_JAVASCRIPT;
      }
      // Wasm magic bytes: 0x00 0x61 0x73 0x6d
      if (content && content.length >= 4 &&
          content[0] === 0x00 && content[1] === 0x61 &&
          content[2] === 0x73 && content[3] === 0x6d) {
        return internalConstants.EXTENSIONLESS_FORMAT_WASM;
      }
      return internalConstants.EXTENSIONLESS_FORMAT_JAVASCRIPT;
    },
    readPackageJSON(jsonPath, isESM, base, specifier) {
      const r = findVFSOrRoot(jsonPath);
      if (r === null) return undefined;
      if (r.vfs === null) return kLoaderOverrideNoResult;
      const { vfs } = r;
      if (vfsStat(vfs, jsonPath) !== 0) return kLoaderOverrideNoResult;
      let content;
      try {
        content = vfs.readFileSync(jsonPath);
      } catch {
        return kLoaderOverrideNoResult;
      }
      // Both CJS and ESM raise ERR_INVALID_PACKAGE_CONFIG since
      // nodejs/node#48606.
      return nativeModulesBinding.parsePackageJSON(
        content, jsonPath, isESM, base, specifier);
    },
    getNearestParentPackageJSON(checkPath) {
      const r = findVFSOrRoot(checkPath);
      if (r === null) return undefined;
      if (r.vfs === null) return kLoaderOverrideNoResult;
      const found = findVFSPackageJSON(r.vfs, checkPath, r.normalized);
      return found.tuple ?? kLoaderOverrideNoResult;
    },
    getNearestParentPackageJSONType(checkPath) {
      const r = findVFSOrRoot(checkPath);
      if (r === null) return undefined;
      if (r.vfs === null) return kLoaderOverrideNoResult;
      const found = findVFSPackageJSON(r.vfs, checkPath, r.normalized);
      // Tuple shape: [name, main, type, imports, exports, filePath]. No
      // package.json above the path is "no result", which the caller reads
      // the same way it reads a scope without a `type`.
      return found.tuple?.[2] ?? kLoaderOverrideNoResult;
    },
    getPackageScopeConfig(resolved) {
      let filePath;
      if (StringPrototypeStartsWith(resolved, 'file:')) {
        try {
          filePath = fileURLToPath(resolved);
        } catch {
          return undefined;
        }
      } else {
        filePath = resolved;
      }
      const r = findVFSOrRoot(filePath);
      if (r === null) return undefined;
      // The "not found" marker is the package.json beside the queried
      // path, which is what the native binding reports for it.
      if (r.vfs === null) return join(dirname(filePath), 'package.json');
      const found = findVFSPackageJSON(r.vfs, filePath, r.normalized);
      if (found.tuple !== undefined) return found.tuple;
      return found.sentinel;
    },
    getPackageType(url) {
      let filePath;
      if (StringPrototypeStartsWith(url, 'file:')) {
        try {
          filePath = fileURLToPath(url);
        } catch {
          return undefined;
        }
      } else {
        filePath = url;
      }
      const r = findVFSOrRoot(filePath);
      if (r === null) return undefined;
      if (r.vfs === null) return kLoaderOverrideNoResult;
      const found = findVFSPackageJSON(r.vfs, filePath, r.normalized);
      if (found.tuple !== undefined) {
        // Tuple shape: [name, main, type, imports, exports, filePath].
        const type = found.tuple[2];
        if (type === 'module' || type === 'commonjs') return type;
      }
      return kLoaderOverrideNoResult;
    },
  });
}

let originalDlopen;

function installAddonLoader() {
  originalDlopen = process.dlopen;
  process.dlopen = function(module, filename, flags) {
    // dlopen(2) cannot open a native addon that lives in a VFS by path (it has
    // no real inode). Read its bytes and hand them to the internal
    // dlopenBinary(), which writes them to a private, self-cleaning temporary
    // image - an in-memory memfd on Linux - and loads that. Only VFS paths take
    // this route; everything else loads straight from disk through the
    // unchanged process.dlopen().
    if (StringPrototypeStartsWith(filename, normalizedVfsRootPrefix)) {
      const { readFileSync } = require('fs');
      const { dlopenBinary } = internalBinding('process_methods');
      return dlopenBinary(module, filename, flags, readFileSync(filename));
    }
    // Do not forward a missing flags argument as `undefined`:
    // process.dlopen() coerces it to 0, which is not a valid dlopen(2)
    // mode, instead of applying the default flags.
    if (flags === undefined) return originalDlopen(module, filename);
    return originalDlopen(module, filename, flags);
  };
}

/**
 * Reads the bytes of a file that lives in a mounted VFS. Returns undefined
 * for a path outside the reserved VFS root - the caller should open the
 * path itself - and throws ENOENT for a path under the root that no
 * mounted VFS serves, since no real file can exist there. Installed into
 * node:ffi while hooks are installed, so DynamicLibrary can load a
 * VFS-resident library from a private image, the same way the module
 * loader handles a native addon in a VFS.
 * @param {string} pathStr The path of the library
 * @returns {Buffer|undefined} The library's bytes, or undefined
 */
function readVirtualBinary(pathStr) {
  const normalized = normalizeMountedPath(pathStr);
  if (!StringPrototypeStartsWith(normalized, normalizedVfsRootPrefix)) {
    return undefined;
  }
  const layerId = getLayerIdFromPath(normalized);
  const vfs = layerId === -1 ? undefined : activeVFSLayers.get(layerId);
  if (vfs === undefined || !vfs.shouldHandleNormalized(normalized)) {
    throw createENOENT('open', pathStr);
  }
  return vfs.readFileSync(normalized);
}

/**
 * Install all VFS hooks: module loader overrides and fs handlers.
 */
function installHooks() {
  if (hooksInstalled) return;
  debug('install hooks');
  normalizedVfsRoot = getNormalizedVfsRoot();
  normalizedVfsRootPrefix = normalizedVfsRoot + sep;
  rootVFS ??= new VirtualFileSystem(
    new ReservedRootProvider(activeVFSLayers),
    { __proto__: null, emitExperimentalWarning: false, [kReservedRoot]: true });
  installModuleLoaderOverrides();
  installAddonLoader();
  const { setVfsLibraryReader } = require('internal/ffi/vfs');
  setVfsLibraryReader(readVirtualBinary);
  vfsHandlerObj = createVfsHandlers();
  setVfsHandlers(vfsHandlerObj);
  hooksInstalled = true;
}

/**
 * Tear down all VFS hooks when the last instance is deregistered. The
 * fast path in the loader wrappers is restored so subsequent require/
 * import calls pay zero overhead until another VFS is mounted.
 */
function uninstallHooks() {
  if (!hooksInstalled) return;
  debug('uninstall hooks');
  const { setLoaderOverrides } = require('internal/modules/helpers');
  setLoaderOverrides();
  setVfsHandlers(null);
  vfsHandlerObj = undefined;
  const { setVfsLibraryReader } = require('internal/ffi/vfs');
  setVfsLibraryReader(null);
  process.dlopen = originalDlopen;
  hooksInstalled = false;
}

module.exports = {
  registerVFS,
  deregisterVFS,
};