win: fix path for removed and renamed fs events

Previous behavior on Windows was to set the path to NULL for removed
and renamed fs events. This was because the path provided by
ReadDirectoryChangesW might (in rare cases) be an 8.3 short name which
could then no longer be converted to a long name after the path had
been removed or renamed. This meant that the user had to detect which
path was actually deleted or renamed and required the user to rescan
the entire watched subtree, taking several seconds or more for large
subtrees.

However, ReadDirectoryChangesW is publicly documented to emit 8.3
short names if the original handle for the changed path was opened
using an 8.3 short name, and libuv may already emit 8.3 short names for
other events if the path cannot be explicitly resolved to a long name.

This commit fixes the path for removed and renamed fs events, and does
not set the path to NULL, even if the path might be an 8.3 short name.
This makes it possible for the user to do a partial scan of the
subtree, restricting the scan to paths which match the long form or 8.3
short name (even if some of these are false positive matches). This
means that deletes and renames can now be detected accurately on
Windows within a few milliseconds.

Fixes: https://github.com/libuv/libuv/issues/634
Refs: https://github.com/libuv/libuv/pull/199
PR-URL: https://github.com/libuv/libuv/pull/639
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
Joran Dirk Greef 2015-12-03 09:33:36 +02:00 committed by Saúl Ibarra Corretgé
parent e76b8838e5
commit e0250b7d5c
2 changed files with 41 additions and 8 deletions

View File

@ -381,9 +381,10 @@ void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
if (handle->dirw) {
/*
* We attempt to convert the file name to its long form for
* events that still point to valid files on disk.
* For removed and renamed events, we do not provide the file name.
* We attempt to resolve the long form of the file name explicitly.
* We only do this for file names that might still exist on disk.
* If this fails, we use the name given by ReadDirectoryChangesW.
* This may be the long form or the 8.3 short name in some cases.
*/
if (file_info->Action != FILE_ACTION_REMOVED &&
file_info->Action != FILE_ACTION_RENAMED_OLD_NAME) {
@ -438,16 +439,24 @@ void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
}
/*
* If we couldn't get the long name - just use the name
* provided by ReadDirectoryChangesW.
* We could not resolve the long form explicitly.
* We therefore use the name given by ReadDirectoryChangesW.
* This may be the long form or the 8.3 short name in some cases.
*/
if (!long_filenamew) {
filenamew = file_info->FileName;
sizew = file_info->FileNameLength / sizeof(WCHAR);
}
} else {
/* Removed or renamed callbacks don't provide filename. */
filenamew = NULL;
/*
* Removed or renamed events cannot be resolved to the long form.
* We therefore use the name given by ReadDirectoryChangesW.
* This may be the long form or the 8.3 short name in some cases.
*/
if (!long_filenamew) {
filenamew = file_info->FileName;
sizew = file_info->FileNameLength / sizeof(WCHAR);
}
}
} else {
/* We already have the long name of the file, so just use it. */

View File

@ -115,7 +115,11 @@ static void fs_event_cb_dir(uv_fs_event_t* handle, const char* filename,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_RENAME);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
ASSERT(strcmp(filename, "file1") == 0);
#else
ASSERT(filename == NULL || strcmp(filename, "file1") == 0);
#endif
ASSERT(0 == uv_fs_event_stop(handle));
uv_close((uv_handle_t*)handle, close_cb);
}
@ -178,8 +182,12 @@ static void fs_event_cb_dir_multi_file(uv_fs_event_t* handle,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE || UV_RENAME);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
ASSERT(strncmp(filename, file_prefix, sizeof(file_prefix) - 1) == 0);
#else
ASSERT(filename == NULL ||
strncmp(filename, file_prefix, sizeof(file_prefix) - 1) == 0);
#endif
if (fs_event_created + fs_event_removed == fs_event_file_count) {
/* Once we've processed all create events, delete all files */
@ -250,8 +258,16 @@ static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE || UV_RENAME);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
ASSERT(strncmp(filename,
file_prefix_in_subdir,
sizeof(file_prefix_in_subdir) - 1) == 0);
#else
ASSERT(filename == NULL ||
strncmp(filename, file_prefix_in_subdir, sizeof(file_prefix_in_subdir) - 1) == 0);
strncmp(filename,
file_prefix_in_subdir,
sizeof(file_prefix_in_subdir) - 1) == 0);
#endif
if (fs_event_created + fs_event_removed == fs_event_file_count) {
/* Once we've processed all create events, delete all files */
@ -270,7 +286,11 @@ static void fs_event_cb_file(uv_fs_event_t* handle, const char* filename,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
ASSERT(strcmp(filename, "file2") == 0);
#else
ASSERT(filename == NULL || strcmp(filename, "file2") == 0);
#endif
ASSERT(0 == uv_fs_event_stop(handle));
uv_close((uv_handle_t*)handle, close_cb);
}
@ -293,7 +313,11 @@ static void fs_event_cb_file_current_dir(uv_fs_event_t* handle,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
ASSERT(strcmp(filename, "watch_file") == 0);
#else
ASSERT(filename == NULL || strcmp(filename, "watch_file") == 0);
#endif
/* Regression test for SunOS: touch should generate just one event. */
{