fsevents: detect watched directory removal (#4376)

Which was broken both in `windows` and `macos`.
This commit is contained in:
Santiago Gimeno 2024-07-30 00:59:38 +02:00 committed by GitHub
parent 83306585ff
commit badecdca14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 152 additions and 60 deletions

View File

@ -276,10 +276,6 @@ static void uv__fsevents_event_cb(const FSEventStreamRef streamRef,
path += handle->realpath_len; path += handle->realpath_len;
len -= handle->realpath_len; len -= handle->realpath_len;
/* Ignore events with path equal to directory itself */
if (len <= 1 && (flags & kFSEventStreamEventFlagItemIsDir))
continue;
if (len == 0) { if (len == 0) {
/* Since we're using fsevents to watch the file itself, /* Since we're using fsevents to watch the file itself,
* realpath == path, and we now need to get the basename of the file back * realpath == path, and we now need to get the basename of the file back

View File

@ -561,8 +561,26 @@ void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
} }
} else { } else {
err = GET_REQ_ERROR(req); err = GET_REQ_ERROR(req);
/*
* Check whether the ERROR_ACCESS_DENIED is caused by the watched directory
* being actually deleted (not an actual error) or a legit error. Retrieve
* FileStandardInfo to check whether the directory is pending deletion.
*/
FILE_STANDARD_INFO info;
if (err == ERROR_ACCESS_DENIED &&
handle->dirw != NULL &&
GetFileInformationByHandleEx(handle->dir_handle,
FileStandardInfo,
&info,
sizeof(info)) &&
info.Directory &&
info.DeletePending) {
uv__convert_utf16_to_utf8(handle->dirw, -1, &filename);
handle->cb(handle, filename, UV_RENAME, 0);
} else {
handle->cb(handle, NULL, 0, uv_translate_sys_error(err)); handle->cb(handle, NULL, 0, uv_translate_sys_error(err));
} }
}
if (handle->flags & UV_HANDLE_CLOSING) { if (handle->flags & UV_HANDLE_CLOSING) {
uv__want_endgame(loop, (uv_handle_t*)handle); uv__want_endgame(loop, (uv_handle_t*)handle);

View File

@ -81,6 +81,22 @@ static void create_file(const char* name) {
uv_fs_req_cleanup(&req); uv_fs_req_cleanup(&req);
} }
static int delete_dir(const char* name) {
int r;
uv_fs_t req;
r = uv_fs_rmdir(NULL, &req, name, NULL);
uv_fs_req_cleanup(&req);
return r;
}
static int delete_file(const char* name) {
int r;
uv_fs_t req;
r = uv_fs_unlink(NULL, &req, name, NULL);
uv_fs_req_cleanup(&req);
return r;
}
static void touch_file(const char* name) { static void touch_file(const char* name) {
int r; int r;
uv_file file; uv_file file;
@ -129,6 +145,19 @@ static void fs_event_cb_dir(uv_fs_event_t* handle, const char* filename,
uv_close((uv_handle_t*)handle, close_cb); uv_close((uv_handle_t*)handle, close_cb);
} }
static void fs_event_cb_del_dir(uv_fs_event_t* handle,
const char* filename,
int events,
int status) {
++fs_event_cb_called;
ASSERT_PTR_EQ(handle, &fs_event);
ASSERT_OK(status);
ASSERT_EQ(events, UV_RENAME);
ASSERT_OK(strcmp(filename, "watch_del_dir"));
ASSERT_OK(uv_fs_event_stop(handle));
uv_close((uv_handle_t*)handle, close_cb);
}
static const char* fs_event_get_filename(int i) { static const char* fs_event_get_filename(int i) {
snprintf(fs_event_filename, snprintf(fs_event_filename,
sizeof(fs_event_filename), sizeof(fs_event_filename),
@ -152,6 +181,15 @@ static void fs_event_create_files(uv_timer_t* handle) {
} }
} }
static void fs_event_del_dir(uv_timer_t* handle) {
int r;
r = delete_dir("watch_del_dir");
ASSERT_OK(r);
uv_close((uv_handle_t*)handle, close_cb);
}
static void fs_event_unlink_files(uv_timer_t* handle) { static void fs_event_unlink_files(uv_timer_t* handle) {
int r; int r;
int i; int i;
@ -160,7 +198,7 @@ static void fs_event_unlink_files(uv_timer_t* handle) {
if (handle == NULL) { if (handle == NULL) {
/* Unlink all files */ /* Unlink all files */
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
r = remove(fs_event_get_filename(i)); r = delete_file(fs_event_get_filename(i));
if (handle != NULL) if (handle != NULL)
ASSERT_OK(r); ASSERT_OK(r);
} }
@ -169,7 +207,7 @@ static void fs_event_unlink_files(uv_timer_t* handle) {
ASSERT_LT(fs_event_removed, fs_event_file_count); ASSERT_LT(fs_event_removed, fs_event_file_count);
/* Remove the file */ /* Remove the file */
ASSERT_OK(remove(fs_event_get_filename(fs_event_removed))); ASSERT_OK(delete_file(fs_event_get_filename(fs_event_removed)));
if (++fs_event_removed < fs_event_file_count) { if (++fs_event_removed < fs_event_file_count) {
/* Remove another file on a different event loop tick. We do it this way /* Remove another file on a different event loop tick. We do it this way
@ -237,7 +275,7 @@ static void fs_event_unlink_files_in_subdir(uv_timer_t* handle) {
if (handle == NULL) { if (handle == NULL) {
/* Unlink all files */ /* Unlink all files */
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
r = remove(fs_event_get_filename_in_subdir(i)); r = delete_file(fs_event_get_filename_in_subdir(i));
if (handle != NULL) if (handle != NULL)
ASSERT_OK(r); ASSERT_OK(r);
} }
@ -246,7 +284,7 @@ static void fs_event_unlink_files_in_subdir(uv_timer_t* handle) {
ASSERT_LT(fs_event_removed, fs_event_file_count); ASSERT_LT(fs_event_removed, fs_event_file_count);
/* Remove the file */ /* Remove the file */
ASSERT_OK(remove(fs_event_get_filename_in_subdir(fs_event_removed))); ASSERT_OK(delete_file(fs_event_get_filename_in_subdir(fs_event_removed)));
if (++fs_event_removed < fs_event_file_count) { if (++fs_event_removed < fs_event_file_count) {
/* Remove another file on a different event loop tick. We do it this way /* Remove another file on a different event loop tick. We do it this way
@ -407,9 +445,9 @@ TEST_IMPL(fs_event_watch_dir) {
/* Setup */ /* Setup */
fs_event_unlink_files(NULL); fs_event_unlink_files(NULL);
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
r = uv_fs_event_init(loop, &fs_event); r = uv_fs_event_init(loop, &fs_event);
@ -428,9 +466,47 @@ TEST_IMPL(fs_event_watch_dir) {
/* Cleanup */ /* Cleanup */
fs_event_unlink_files(NULL); fs_event_unlink_files(NULL);
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
TEST_IMPL(fs_event_watch_delete_dir) {
#if defined(NO_FS_EVENTS)
RETURN_SKIP(NO_FS_EVENTS);
#elif defined(__MVS__)
RETURN_SKIP("Directory watching not supported on this platform.");
#elif defined(__APPLE__) && defined(__TSAN__)
RETURN_SKIP("Times out under TSAN.");
#endif
uv_loop_t* loop = uv_default_loop();
int r;
/* Setup */
fs_event_unlink_files(NULL);
delete_dir("watch_del_dir/");
create_dir("watch_del_dir");
r = uv_fs_event_init(loop, &fs_event);
ASSERT_OK(r);
r = uv_fs_event_start(&fs_event, fs_event_cb_del_dir, "watch_del_dir", 0);
ASSERT_OK(r);
r = uv_timer_init(loop, &timer);
ASSERT_OK(r);
r = uv_timer_start(&timer, fs_event_del_dir, 100, 0);
ASSERT_OK(r);
uv_run(loop, UV_RUN_DEFAULT);
ASSERT_EQ(1, fs_event_cb_called);
ASSERT_EQ(2, close_cb_called);
/* Cleanup */
fs_event_unlink_files(NULL);
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -448,10 +524,10 @@ TEST_IMPL(fs_event_watch_dir_recursive) {
/* Setup */ /* Setup */
loop = uv_default_loop(); loop = uv_default_loop();
fs_event_unlink_files(NULL); fs_event_unlink_files(NULL);
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/subdir"); delete_dir("watch_dir/subdir");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_dir("watch_dir/subdir"); create_dir("watch_dir/subdir");
@ -491,10 +567,10 @@ TEST_IMPL(fs_event_watch_dir_recursive) {
/* Cleanup */ /* Cleanup */
fs_event_unlink_files_in_subdir(NULL); fs_event_unlink_files_in_subdir(NULL);
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/subdir"); delete_dir("watch_dir/subdir");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -512,8 +588,8 @@ TEST_IMPL(fs_event_watch_dir_short_path) {
/* Setup */ /* Setup */
loop = uv_default_loop(); loop = uv_default_loop();
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_file("watch_dir/file1"); create_file("watch_dir/file1");
@ -540,8 +616,8 @@ TEST_IMPL(fs_event_watch_dir_short_path) {
} }
/* Cleanup */ /* Cleanup */
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
@ -562,9 +638,9 @@ TEST_IMPL(fs_event_watch_file) {
int r; int r;
/* Setup */ /* Setup */
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_file("watch_dir/file1"); create_file("watch_dir/file1");
create_file("watch_dir/file2"); create_file("watch_dir/file2");
@ -585,9 +661,9 @@ TEST_IMPL(fs_event_watch_file) {
ASSERT_EQ(2, close_cb_called); ASSERT_EQ(2, close_cb_called);
/* Cleanup */ /* Cleanup */
remove("watch_dir/file2"); delete_file("watch_dir/file2");
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -609,9 +685,9 @@ TEST_IMPL(fs_event_watch_file_exact_path) {
loop = uv_default_loop(); loop = uv_default_loop();
/* Setup */ /* Setup */
remove("watch_dir/file.js"); delete_file("watch_dir/file.js");
remove("watch_dir/file.jsx"); delete_file("watch_dir/file.jsx");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_file("watch_dir/file.js"); create_file("watch_dir/file.js");
create_file("watch_dir/file.jsx"); create_file("watch_dir/file.jsx");
@ -638,9 +714,9 @@ TEST_IMPL(fs_event_watch_file_exact_path) {
ASSERT_EQ(2, timer_cb_exact_called); ASSERT_EQ(2, timer_cb_exact_called);
/* Cleanup */ /* Cleanup */
remove("watch_dir/file.js"); delete_file("watch_dir/file.js");
remove("watch_dir/file.jsx"); delete_file("watch_dir/file.jsx");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -681,7 +757,7 @@ TEST_IMPL(fs_event_watch_file_current_dir) {
loop = uv_default_loop(); loop = uv_default_loop();
/* Setup */ /* Setup */
remove("watch_file"); delete_file("watch_file");
create_file("watch_file"); create_file("watch_file");
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12) #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12)
/* Empirically, kevent seems to (sometimes) report the preceding /* Empirically, kevent seems to (sometimes) report the preceding
@ -719,7 +795,7 @@ TEST_IMPL(fs_event_watch_file_current_dir) {
ASSERT_EQ(1, close_cb_called); ASSERT_EQ(1, close_cb_called);
/* Cleanup */ /* Cleanup */
remove("watch_file"); delete_file("watch_file");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -761,8 +837,8 @@ TEST_IMPL(fs_event_no_callback_after_close) {
int r; int r;
/* Setup */ /* Setup */
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_file("watch_dir/file1"); create_file("watch_dir/file1");
@ -783,8 +859,8 @@ TEST_IMPL(fs_event_no_callback_after_close) {
ASSERT_EQ(1, close_cb_called); ASSERT_EQ(1, close_cb_called);
/* Cleanup */ /* Cleanup */
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -799,8 +875,8 @@ TEST_IMPL(fs_event_no_callback_on_close) {
int r; int r;
/* Setup */ /* Setup */
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
create_file("watch_dir/file1"); create_file("watch_dir/file1");
@ -820,8 +896,8 @@ TEST_IMPL(fs_event_no_callback_on_close) {
ASSERT_EQ(1, close_cb_called); ASSERT_EQ(1, close_cb_called);
/* Cleanup */ /* Cleanup */
remove("watch_dir/file1"); delete_file("watch_dir/file1");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -893,8 +969,8 @@ TEST_IMPL(fs_event_close_with_pending_event) {
ASSERT_EQ(1, close_cb_called); ASSERT_EQ(1, close_cb_called);
/* Clean up */ /* Clean up */
remove("watch_dir/file"); delete_file("watch_dir/file");
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -918,7 +994,7 @@ TEST_IMPL(fs_event_close_with_pending_delete_event) {
ASSERT_OK(r); ASSERT_OK(r);
/* Generate an fs event. */ /* Generate an fs event. */
remove("watch_dir/file"); delete_file("watch_dir/file");
/* Allow time for the remove event to propagate to the pending list. */ /* Allow time for the remove event to propagate to the pending list. */
/* XXX - perhaps just for __sun? */ /* XXX - perhaps just for __sun? */
@ -932,7 +1008,7 @@ TEST_IMPL(fs_event_close_with_pending_delete_event) {
ASSERT_EQ(1, close_cb_called); ASSERT_EQ(1, close_cb_called);
/* Clean up */ /* Clean up */
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -975,7 +1051,7 @@ TEST_IMPL(fs_event_close_in_callback) {
/* Clean up */ /* Clean up */
fs_event_unlink_files(NULL); fs_event_unlink_files(NULL);
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
@ -1011,7 +1087,7 @@ TEST_IMPL(fs_event_start_and_close) {
ASSERT_EQ(2, close_cb_called); ASSERT_EQ(2, close_cb_called);
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
} }
@ -1064,7 +1140,7 @@ TEST_IMPL(fs_event_getpath) {
close_cb_called = 0; close_cb_called = 0;
} }
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(loop); MAKE_VALGRIND_HAPPY(loop);
return 0; return 0;
} }
@ -1103,7 +1179,7 @@ TEST_IMPL(fs_event_error_reporting) {
TEST_FILE_LIMIT(ARRAY_SIZE(loops) * 3); TEST_FILE_LIMIT(ARRAY_SIZE(loops) * 3);
remove("watch_dir/"); delete_dir("watch_dir/");
create_dir("watch_dir"); create_dir("watch_dir");
/* Create a lot of loops, and start FSEventStream in each of them. /* Create a lot of loops, and start FSEventStream in each of them.
@ -1153,7 +1229,7 @@ TEST_IMPL(fs_event_error_reporting) {
uv_loop_close(loop); uv_loop_close(loop);
} while (i-- != 0); } while (i-- != 0);
remove("watch_dir/"); delete_dir("watch_dir/");
MAKE_VALGRIND_HAPPY(uv_default_loop()); MAKE_VALGRIND_HAPPY(uv_default_loop());
return 0; return 0;
} }
@ -1207,7 +1283,7 @@ TEST_IMPL(fs_event_stop_in_cb) {
RETURN_SKIP(NO_FS_EVENTS); RETURN_SKIP(NO_FS_EVENTS);
#endif #endif
remove(path); delete_file(path);
create_file(path); create_file(path);
ASSERT_OK(uv_fs_event_init(uv_default_loop(), &fs)); ASSERT_OK(uv_fs_event_init(uv_default_loop(), &fs));
@ -1230,7 +1306,7 @@ TEST_IMPL(fs_event_stop_in_cb) {
ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT));
ASSERT_EQ(1, fs_event_cb_stop_calls); ASSERT_EQ(1, fs_event_cb_stop_calls);
remove(path); delete_file(path);
MAKE_VALGRIND_HAPPY(uv_default_loop()); MAKE_VALGRIND_HAPPY(uv_default_loop());
return 0; return 0;

View File

@ -396,6 +396,7 @@ TEST_DECLARE (fs_stat_missing_path)
TEST_DECLARE (fs_read_bufs) TEST_DECLARE (fs_read_bufs)
TEST_DECLARE (fs_read_file_eof) TEST_DECLARE (fs_read_file_eof)
TEST_DECLARE (fs_event_watch_dir) TEST_DECLARE (fs_event_watch_dir)
TEST_DECLARE (fs_event_watch_delete_dir)
TEST_DECLARE (fs_event_watch_dir_recursive) TEST_DECLARE (fs_event_watch_dir_recursive)
#ifdef _WIN32 #ifdef _WIN32
TEST_DECLARE (fs_event_watch_dir_short_path) TEST_DECLARE (fs_event_watch_dir_short_path)
@ -1105,6 +1106,7 @@ TASK_LIST_START
TEST_ENTRY (fs_read_file_eof) TEST_ENTRY (fs_read_file_eof)
TEST_ENTRY (fs_file_open_append) TEST_ENTRY (fs_file_open_append)
TEST_ENTRY (fs_event_watch_dir) TEST_ENTRY (fs_event_watch_dir)
TEST_ENTRY (fs_event_watch_delete_dir)
TEST_ENTRY (fs_event_watch_dir_recursive) TEST_ENTRY (fs_event_watch_dir_recursive)
#ifdef _WIN32 #ifdef _WIN32
TEST_ENTRY (fs_event_watch_dir_short_path) TEST_ENTRY (fs_event_watch_dir_short_path)