From d3eb1cb3850b4194f652a07e74631788dc94e4c4 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 19 Apr 2024 14:52:14 +0200 Subject: [PATCH] fs: fixes recursive fs.watch crash on Linux when deleting files Signed-off-by: Matteo Collina Fixes: https://github.com/nodejs/node/issues/52018 PR-URL: https://github.com/nodejs/node/pull/52349 Reviewed-By: Yagiz Nizipli Reviewed-By: Moshe Atlow --- lib/internal/fs/recursive_watch.js | 11 +++++-- .../test-fs-watch-recursive-delete.js | 30 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-fs-watch-recursive-delete.js diff --git a/lib/internal/fs/recursive_watch.js b/lib/internal/fs/recursive_watch.js index 7d8b12eeb93..d312e54013c 100644 --- a/lib/internal/fs/recursive_watch.js +++ b/lib/internal/fs/recursive_watch.js @@ -157,11 +157,16 @@ class FSWatcher extends EventEmitter { persistent: this.#options.persistent, }, (eventType, filename) => { const existingStat = this.#files.get(file); - const currentStats = statSync(file); + let currentStats; - this.#files.set(file, currentStats); + try { + currentStats = statSync(file); + this.#files.set(file, currentStats); + } catch { + // This happens if the file was removed + } - if (currentStats.birthtimeMs === 0 && existingStat.birthtimeMs !== 0) { + if (currentStats === undefined || (currentStats.birthtimeMs === 0 && existingStat.birthtimeMs !== 0)) { // The file is now deleted this.#files.delete(file); this.#watchers.delete(file); diff --git a/test/parallel/test-fs-watch-recursive-delete.js b/test/parallel/test-fs-watch-recursive-delete.js new file mode 100644 index 00000000000..27d1ebcc14e --- /dev/null +++ b/test/parallel/test-fs-watch-recursive-delete.js @@ -0,0 +1,30 @@ +'use strict'; + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const fs = require('fs'); + +if (common.isSunOS) + common.skip('SunOS behaves differently'); + +tmpdir.refresh(); + +fs.mkdirSync(tmpdir.resolve('./parent/child'), { recursive: true }); + +fs.writeFileSync(tmpdir.resolve('./parent/child/test.tmp'), 'test'); + +const toWatch = tmpdir.resolve('./parent'); + +const onFileUpdate = common.mustCallAtLeast((eventType, filename) => { + // We are only checking for the filename to avoid having Windows, Linux and Mac specific assertions + if (fs.readdirSync(tmpdir.resolve('./parent')).length === 0) { + watcher.close(); + } +}, 1); + +const watcher = fs.watch(toWatch, { recursive: true }, onFileUpdate); + +// We must wait a bit `fs.rm()` to let the watcher be set up properly +setTimeout(() => { + fs.rm(tmpdir.resolve('./parent/child'), { recursive: true }, common.mustCall()); +}, common.platformTimeout(500));