11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst {
41cb0ef41Sopenharmony_ci  ArrayPrototypePush,
51cb0ef41Sopenharmony_ci  SafePromiseAllReturnVoid,
61cb0ef41Sopenharmony_ci  Promise,
71cb0ef41Sopenharmony_ci  PromisePrototypeThen,
81cb0ef41Sopenharmony_ci  SafeMap,
91cb0ef41Sopenharmony_ci  SafeSet,
101cb0ef41Sopenharmony_ci  StringPrototypeStartsWith,
111cb0ef41Sopenharmony_ci  SymbolAsyncIterator,
121cb0ef41Sopenharmony_ci} = primordials;
131cb0ef41Sopenharmony_ci
141cb0ef41Sopenharmony_ciconst { EventEmitter } = require('events');
151cb0ef41Sopenharmony_ciconst assert = require('internal/assert');
161cb0ef41Sopenharmony_ciconst {
171cb0ef41Sopenharmony_ci  AbortError,
181cb0ef41Sopenharmony_ci  codes: {
191cb0ef41Sopenharmony_ci    ERR_INVALID_ARG_VALUE,
201cb0ef41Sopenharmony_ci  },
211cb0ef41Sopenharmony_ci} = require('internal/errors');
221cb0ef41Sopenharmony_ciconst { getValidatedPath } = require('internal/fs/utils');
231cb0ef41Sopenharmony_ciconst { kFSWatchStart, StatWatcher } = require('internal/fs/watchers');
241cb0ef41Sopenharmony_ciconst { kEmptyObject } = require('internal/util');
251cb0ef41Sopenharmony_ciconst { validateBoolean, validateAbortSignal } = require('internal/validators');
261cb0ef41Sopenharmony_ciconst {
271cb0ef41Sopenharmony_ci  basename: pathBasename,
281cb0ef41Sopenharmony_ci  join: pathJoin,
291cb0ef41Sopenharmony_ci  relative: pathRelative,
301cb0ef41Sopenharmony_ci  resolve: pathResolve,
311cb0ef41Sopenharmony_ci} = require('path');
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_cilet internalSync;
341cb0ef41Sopenharmony_cilet internalPromises;
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_cifunction lazyLoadFsPromises() {
371cb0ef41Sopenharmony_ci  internalPromises ??= require('fs/promises');
381cb0ef41Sopenharmony_ci  return internalPromises;
391cb0ef41Sopenharmony_ci}
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_cifunction lazyLoadFsSync() {
421cb0ef41Sopenharmony_ci  internalSync ??= require('fs');
431cb0ef41Sopenharmony_ci  return internalSync;
441cb0ef41Sopenharmony_ci}
451cb0ef41Sopenharmony_cilet kResistStopPropagation;
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ciasync function traverse(dir, files = new SafeMap(), symbolicLinks = new SafeSet()) {
481cb0ef41Sopenharmony_ci  const { opendir } = lazyLoadFsPromises();
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci  const filenames = await opendir(dir);
511cb0ef41Sopenharmony_ci  const subdirectories = [];
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ci  for await (const file of filenames) {
541cb0ef41Sopenharmony_ci    const f = pathJoin(dir, file.name);
551cb0ef41Sopenharmony_ci
561cb0ef41Sopenharmony_ci    files.set(f, file);
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci    // Do not follow symbolic links
591cb0ef41Sopenharmony_ci    if (file.isSymbolicLink()) {
601cb0ef41Sopenharmony_ci      symbolicLinks.add(f);
611cb0ef41Sopenharmony_ci    } else if (file.isDirectory()) {
621cb0ef41Sopenharmony_ci      ArrayPrototypePush(subdirectories, traverse(f, files));
631cb0ef41Sopenharmony_ci    }
641cb0ef41Sopenharmony_ci  }
651cb0ef41Sopenharmony_ci
661cb0ef41Sopenharmony_ci  await SafePromiseAllReturnVoid(subdirectories);
671cb0ef41Sopenharmony_ci
681cb0ef41Sopenharmony_ci  return files;
691cb0ef41Sopenharmony_ci}
701cb0ef41Sopenharmony_ci
711cb0ef41Sopenharmony_ciclass FSWatcher extends EventEmitter {
721cb0ef41Sopenharmony_ci  #options = null;
731cb0ef41Sopenharmony_ci  #closed = false;
741cb0ef41Sopenharmony_ci  #files = new SafeMap();
751cb0ef41Sopenharmony_ci  #symbolicFiles = new SafeSet();
761cb0ef41Sopenharmony_ci  #rootPath = pathResolve();
771cb0ef41Sopenharmony_ci  #watchingFile = false;
781cb0ef41Sopenharmony_ci
791cb0ef41Sopenharmony_ci  constructor(options = kEmptyObject) {
801cb0ef41Sopenharmony_ci    super();
811cb0ef41Sopenharmony_ci
821cb0ef41Sopenharmony_ci    assert(typeof options === 'object');
831cb0ef41Sopenharmony_ci
841cb0ef41Sopenharmony_ci    const { persistent, recursive, signal, encoding } = options;
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci    // TODO(anonrig): Add non-recursive support to non-native-watcher for IBMi & AIX support.
871cb0ef41Sopenharmony_ci    if (recursive != null) {
881cb0ef41Sopenharmony_ci      validateBoolean(recursive, 'options.recursive');
891cb0ef41Sopenharmony_ci    }
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_ci    if (persistent != null) {
921cb0ef41Sopenharmony_ci      validateBoolean(persistent, 'options.persistent');
931cb0ef41Sopenharmony_ci    }
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci    if (signal != null) {
961cb0ef41Sopenharmony_ci      validateAbortSignal(signal, 'options.signal');
971cb0ef41Sopenharmony_ci    }
981cb0ef41Sopenharmony_ci
991cb0ef41Sopenharmony_ci    if (encoding != null) {
1001cb0ef41Sopenharmony_ci      // This is required since on macOS and Windows it throws ERR_INVALID_ARG_VALUE
1011cb0ef41Sopenharmony_ci      if (typeof encoding !== 'string') {
1021cb0ef41Sopenharmony_ci        throw new ERR_INVALID_ARG_VALUE(encoding, 'options.encoding');
1031cb0ef41Sopenharmony_ci      }
1041cb0ef41Sopenharmony_ci    }
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_ci    this.#options = { persistent, recursive, signal, encoding };
1071cb0ef41Sopenharmony_ci  }
1081cb0ef41Sopenharmony_ci
1091cb0ef41Sopenharmony_ci  close() {
1101cb0ef41Sopenharmony_ci    if (this.#closed) {
1111cb0ef41Sopenharmony_ci      return;
1121cb0ef41Sopenharmony_ci    }
1131cb0ef41Sopenharmony_ci
1141cb0ef41Sopenharmony_ci    const { unwatchFile } = lazyLoadFsSync();
1151cb0ef41Sopenharmony_ci    this.#closed = true;
1161cb0ef41Sopenharmony_ci
1171cb0ef41Sopenharmony_ci    for (const file of this.#files.keys()) {
1181cb0ef41Sopenharmony_ci      unwatchFile(file);
1191cb0ef41Sopenharmony_ci    }
1201cb0ef41Sopenharmony_ci
1211cb0ef41Sopenharmony_ci    this.#files.clear();
1221cb0ef41Sopenharmony_ci    this.#symbolicFiles.clear();
1231cb0ef41Sopenharmony_ci    this.emit('close');
1241cb0ef41Sopenharmony_ci  }
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci  #unwatchFiles(file) {
1271cb0ef41Sopenharmony_ci    const { unwatchFile } = lazyLoadFsSync();
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci    this.#symbolicFiles.delete(file);
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ci    for (const filename of this.#files.keys()) {
1321cb0ef41Sopenharmony_ci      if (StringPrototypeStartsWith(filename, file)) {
1331cb0ef41Sopenharmony_ci        unwatchFile(filename);
1341cb0ef41Sopenharmony_ci      }
1351cb0ef41Sopenharmony_ci    }
1361cb0ef41Sopenharmony_ci  }
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_ci  async #watchFolder(folder) {
1391cb0ef41Sopenharmony_ci    const { opendir } = lazyLoadFsPromises();
1401cb0ef41Sopenharmony_ci
1411cb0ef41Sopenharmony_ci    try {
1421cb0ef41Sopenharmony_ci      const files = await opendir(folder);
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ci      for await (const file of files) {
1451cb0ef41Sopenharmony_ci        if (this.#closed) {
1461cb0ef41Sopenharmony_ci          break;
1471cb0ef41Sopenharmony_ci        }
1481cb0ef41Sopenharmony_ci
1491cb0ef41Sopenharmony_ci        const f = pathJoin(folder, file.name);
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci        if (!this.#files.has(f)) {
1521cb0ef41Sopenharmony_ci          this.emit('change', 'rename', pathRelative(this.#rootPath, f));
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci          if (file.isSymbolicLink()) {
1551cb0ef41Sopenharmony_ci            this.#symbolicFiles.add(f);
1561cb0ef41Sopenharmony_ci          }
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_ci          if (file.isFile()) {
1591cb0ef41Sopenharmony_ci            this.#watchFile(f);
1601cb0ef41Sopenharmony_ci          } else {
1611cb0ef41Sopenharmony_ci            this.#files.set(f, file);
1621cb0ef41Sopenharmony_ci
1631cb0ef41Sopenharmony_ci            if (file.isDirectory() && !file.isSymbolicLink()) {
1641cb0ef41Sopenharmony_ci              await this.#watchFolder(f);
1651cb0ef41Sopenharmony_ci            }
1661cb0ef41Sopenharmony_ci          }
1671cb0ef41Sopenharmony_ci        }
1681cb0ef41Sopenharmony_ci      }
1691cb0ef41Sopenharmony_ci    } catch (error) {
1701cb0ef41Sopenharmony_ci      this.emit('error', error);
1711cb0ef41Sopenharmony_ci    }
1721cb0ef41Sopenharmony_ci  }
1731cb0ef41Sopenharmony_ci
1741cb0ef41Sopenharmony_ci  #watchFile(file) {
1751cb0ef41Sopenharmony_ci    if (this.#closed) {
1761cb0ef41Sopenharmony_ci      return;
1771cb0ef41Sopenharmony_ci    }
1781cb0ef41Sopenharmony_ci
1791cb0ef41Sopenharmony_ci    const { watchFile } = lazyLoadFsSync();
1801cb0ef41Sopenharmony_ci    const existingStat = this.#files.get(file);
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci    watchFile(file, {
1831cb0ef41Sopenharmony_ci      persistent: this.#options.persistent,
1841cb0ef41Sopenharmony_ci    }, (currentStats, previousStats) => {
1851cb0ef41Sopenharmony_ci      if (existingStat && !existingStat.isDirectory() &&
1861cb0ef41Sopenharmony_ci        currentStats.nlink !== 0 && existingStat.mtimeMs === currentStats.mtimeMs) {
1871cb0ef41Sopenharmony_ci        return;
1881cb0ef41Sopenharmony_ci      }
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci      this.#files.set(file, currentStats);
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ci      if (currentStats.birthtimeMs === 0 && previousStats.birthtimeMs !== 0) {
1931cb0ef41Sopenharmony_ci        // The file is now deleted
1941cb0ef41Sopenharmony_ci        this.#files.delete(file);
1951cb0ef41Sopenharmony_ci        this.emit('change', 'rename', pathRelative(this.#rootPath, file));
1961cb0ef41Sopenharmony_ci        this.#unwatchFiles(file);
1971cb0ef41Sopenharmony_ci      } else if (file === this.#rootPath && this.#watchingFile) {
1981cb0ef41Sopenharmony_ci        // This case will only be triggered when watching a file with fs.watch
1991cb0ef41Sopenharmony_ci        this.emit('change', 'change', pathBasename(file));
2001cb0ef41Sopenharmony_ci      } else if (this.#symbolicFiles.has(file)) {
2011cb0ef41Sopenharmony_ci        // Stats from watchFile does not return correct value for currentStats.isSymbolicLink()
2021cb0ef41Sopenharmony_ci        // Since it is only valid when using fs.lstat(). Therefore, check the existing symbolic files.
2031cb0ef41Sopenharmony_ci        this.emit('change', 'rename', pathRelative(this.#rootPath, file));
2041cb0ef41Sopenharmony_ci      } else if (currentStats.isDirectory()) {
2051cb0ef41Sopenharmony_ci        this.#watchFolder(file);
2061cb0ef41Sopenharmony_ci      }
2071cb0ef41Sopenharmony_ci    });
2081cb0ef41Sopenharmony_ci  }
2091cb0ef41Sopenharmony_ci
2101cb0ef41Sopenharmony_ci  [kFSWatchStart](filename) {
2111cb0ef41Sopenharmony_ci    filename = pathResolve(getValidatedPath(filename));
2121cb0ef41Sopenharmony_ci
2131cb0ef41Sopenharmony_ci    try {
2141cb0ef41Sopenharmony_ci      const file = lazyLoadFsSync().statSync(filename);
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ci      this.#rootPath = filename;
2171cb0ef41Sopenharmony_ci      this.#closed = false;
2181cb0ef41Sopenharmony_ci      this.#watchingFile = file.isFile();
2191cb0ef41Sopenharmony_ci
2201cb0ef41Sopenharmony_ci      if (file.isDirectory()) {
2211cb0ef41Sopenharmony_ci        this.#files.set(filename, file);
2221cb0ef41Sopenharmony_ci
2231cb0ef41Sopenharmony_ci        PromisePrototypeThen(
2241cb0ef41Sopenharmony_ci          traverse(filename, this.#files, this.#symbolicFiles),
2251cb0ef41Sopenharmony_ci          () => {
2261cb0ef41Sopenharmony_ci            for (const f of this.#files.keys()) {
2271cb0ef41Sopenharmony_ci              this.#watchFile(f);
2281cb0ef41Sopenharmony_ci            }
2291cb0ef41Sopenharmony_ci          },
2301cb0ef41Sopenharmony_ci        );
2311cb0ef41Sopenharmony_ci      } else {
2321cb0ef41Sopenharmony_ci        this.#watchFile(filename);
2331cb0ef41Sopenharmony_ci      }
2341cb0ef41Sopenharmony_ci    } catch (error) {
2351cb0ef41Sopenharmony_ci      if (error.code === 'ENOENT') {
2361cb0ef41Sopenharmony_ci        error.filename = filename;
2371cb0ef41Sopenharmony_ci        throw error;
2381cb0ef41Sopenharmony_ci      }
2391cb0ef41Sopenharmony_ci    }
2401cb0ef41Sopenharmony_ci
2411cb0ef41Sopenharmony_ci  }
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci  ref() {
2441cb0ef41Sopenharmony_ci    this.#files.forEach((file) => {
2451cb0ef41Sopenharmony_ci      if (file instanceof StatWatcher) {
2461cb0ef41Sopenharmony_ci        file.ref();
2471cb0ef41Sopenharmony_ci      }
2481cb0ef41Sopenharmony_ci    });
2491cb0ef41Sopenharmony_ci  }
2501cb0ef41Sopenharmony_ci
2511cb0ef41Sopenharmony_ci  unref() {
2521cb0ef41Sopenharmony_ci    this.#files.forEach((file) => {
2531cb0ef41Sopenharmony_ci      if (file instanceof StatWatcher) {
2541cb0ef41Sopenharmony_ci        file.unref();
2551cb0ef41Sopenharmony_ci      }
2561cb0ef41Sopenharmony_ci    });
2571cb0ef41Sopenharmony_ci  }
2581cb0ef41Sopenharmony_ci
2591cb0ef41Sopenharmony_ci  [SymbolAsyncIterator]() {
2601cb0ef41Sopenharmony_ci    const { signal } = this.#options;
2611cb0ef41Sopenharmony_ci    const promiseExecutor = signal == null ?
2621cb0ef41Sopenharmony_ci      (resolve) => {
2631cb0ef41Sopenharmony_ci        this.once('change', (eventType, filename) => {
2641cb0ef41Sopenharmony_ci          resolve({ __proto__: null, value: { eventType, filename } });
2651cb0ef41Sopenharmony_ci        });
2661cb0ef41Sopenharmony_ci      } : (resolve, reject) => {
2671cb0ef41Sopenharmony_ci        const onAbort = () => reject(new AbortError(undefined, { cause: signal.reason }));
2681cb0ef41Sopenharmony_ci        if (signal.aborted) return onAbort();
2691cb0ef41Sopenharmony_ci        kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
2701cb0ef41Sopenharmony_ci        signal.addEventListener('abort', onAbort, { __proto__: null, once: true, [kResistStopPropagation]: true });
2711cb0ef41Sopenharmony_ci        this.once('change', (eventType, filename) => {
2721cb0ef41Sopenharmony_ci          signal.removeEventListener('abort', onAbort);
2731cb0ef41Sopenharmony_ci          resolve({ __proto__: null, value: { eventType, filename } });
2741cb0ef41Sopenharmony_ci        });
2751cb0ef41Sopenharmony_ci      };
2761cb0ef41Sopenharmony_ci    return {
2771cb0ef41Sopenharmony_ci      next: () => (this.#closed ?
2781cb0ef41Sopenharmony_ci        { __proto__: null, done: true } :
2791cb0ef41Sopenharmony_ci        new Promise(promiseExecutor)),
2801cb0ef41Sopenharmony_ci      [SymbolAsyncIterator]() { return this; },
2811cb0ef41Sopenharmony_ci    };
2821cb0ef41Sopenharmony_ci  }
2831cb0ef41Sopenharmony_ci}
2841cb0ef41Sopenharmony_ci
2851cb0ef41Sopenharmony_cimodule.exports = {
2861cb0ef41Sopenharmony_ci  FSWatcher,
2871cb0ef41Sopenharmony_ci  kFSWatchStart,
2881cb0ef41Sopenharmony_ci};
289