fsevents: really watch files with fsevents on macos 10.7+

In the original PR, the ifdef conditional was reversed,
leading to the old code-path still being used.
This also reduces some of the redundancy in the conditional checks,
by factoring out the common test.
And fixes a divergence in functionality kFSEventsRenamed =>
kFSEventStreamEventFlagItemRenamed
And actually includes the part of the original PR to kqueue that enabled
watching files with fsevents!

Fixes: https://github.com/libuv/libuv/pull/387
PR-URL: https://github.com/libuv/libuv/pull/2082
Refs: https://github.com/libuv/libuv/pull/1572
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
Jameson Nash 2018-11-19 14:29:20 -05:00
parent 3be96bb723
commit 2d2af382ce
6 changed files with 132 additions and 112 deletions

View File

@ -733,7 +733,11 @@ int uv_fs_event_start(uv_fs_event_t* handle,
/* Figure out whether filename is absolute or not */ /* Figure out whether filename is absolute or not */
if (filename[0] == '/') { if (filename[0] == '\0') {
/* Missing a pathname */
return UV_ENOENT;
}
else if (filename[0] == '/') {
/* We have absolute pathname */ /* We have absolute pathname */
/* TODO(bnoordhuis) Check uv__strscpy() return value. */ /* TODO(bnoordhuis) Check uv__strscpy() return value. */
uv__strscpy(absolute_path, filename, sizeof(absolute_path)); uv__strscpy(absolute_path, filename, sizeof(absolute_path));

View File

@ -255,42 +255,55 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef,
path = paths[i]; path = paths[i];
len = strlen(path); len = strlen(path);
if (handle->realpath_len == 0)
continue; /* This should be unreachable */
/* Filter out paths that are outside handle's request */ /* Filter out paths that are outside handle's request */
if (strncmp(path, handle->realpath, handle->realpath_len) != 0) if (len < handle->realpath_len)
continue; continue;
if (handle->realpath_len > 1 || *handle->realpath != '/') { if (handle->realpath_len != len &&
path[handle->realpath_len] != '/')
/* Make sure that realpath actually named a directory,
* or that we matched the whole string */
continue;
if (memcmp(path, handle->realpath, handle->realpath_len) != 0)
continue;
if (!(handle->realpath_len == 1 && handle->realpath[0] == '/')) {
/* Remove common prefix, unless the watched folder is "/" */
path += handle->realpath_len; path += handle->realpath_len;
len -= handle->realpath_len; len -= handle->realpath_len;
/* Skip forward slash */ /* Ignore events with path equal to directory itself */
if (*path != '\0') { if (len <= 1 && (flags & kFSEventStreamEventFlagItemIsDir))
continue;
if (len == 0) {
/* Since we're using fsevents to watch the file itself,
* realpath == path, and we now need to get the basename of the file back
* (for commonality with other codepaths and platforms). */
while (len < handle->realpath_len && path[-1] != '/') {
path--;
len++;
}
/* Created and Removed seem to be always set, but don't make sense */
flags &= ~kFSEventsRenamed;
} else {
/* Skip forward slash */
path++; path++;
len--; len--;
} }
} }
#ifdef MAC_OS_X_VERSION_10_7
/* Ignore events with path equal to directory itself */
if (len == 0)
continue;
#else
if (len == 0 && (flags & kFSEventStreamEventFlagItemIsDir))
continue;
#endif /* MAC_OS_X_VERSION_10_7 */
/* Do not emit events from subdirectories (without option set) */ /* Do not emit events from subdirectories (without option set) */
if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != 0) { if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && *path != '\0') {
pos = strchr(path + 1, '/'); pos = strchr(path + 1, '/');
if (pos != NULL) if (pos != NULL)
continue; continue;
} }
#ifndef MAC_OS_X_VERSION_10_7
path = "";
len = 0;
#endif /* MAC_OS_X_VERSION_10_7 */
event = uv__malloc(sizeof(*event) + len); event = uv__malloc(sizeof(*event) + len);
if (event == NULL) if (event == NULL)
break; break;
@ -299,22 +312,11 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef,
memcpy(event->path, path, len + 1); memcpy(event->path, path, len + 1);
event->events = UV_RENAME; event->events = UV_RENAME;
#ifdef MAC_OS_X_VERSION_10_7 if (0 == (flags & kFSEventsRenamed)) {
if (0 != (flags & kFSEventsModified) && if (0 != (flags & kFSEventsModified) ||
0 == (flags & kFSEventsRenamed)) { 0 == (flags & kFSEventStreamEventFlagItemIsDir))
event->events = UV_CHANGE; event->events = UV_CHANGE;
} }
#else
if (0 != (flags & kFSEventsModified) &&
0 != (flags & kFSEventStreamEventFlagItemIsDir) &&
0 == (flags & kFSEventStreamEventFlagItemRenamed)) {
event->events = UV_CHANGE;
}
if (0 == (flags & kFSEventStreamEventFlagItemIsDir) &&
0 == (flags & kFSEventStreamEventFlagItemRenamed)) {
event->events = UV_CHANGE;
}
#endif /* MAC_OS_X_VERSION_10_7 */
QUEUE_INSERT_TAIL(&head, &event->member); QUEUE_INSERT_TAIL(&head, &event->member);
} }

View File

@ -284,24 +284,6 @@ int uv__fsevents_init(uv_fs_event_t* handle);
int uv__fsevents_close(uv_fs_event_t* handle); int uv__fsevents_close(uv_fs_event_t* handle);
void uv__fsevents_loop_delete(uv_loop_t* loop); void uv__fsevents_loop_delete(uv_loop_t* loop);
/* OSX < 10.7 has no file events, polyfill them */
#ifndef MAC_OS_X_VERSION_10_7
static const int kFSEventStreamCreateFlagFileEvents = 0x00000010;
static const int kFSEventStreamEventFlagItemCreated = 0x00000100;
static const int kFSEventStreamEventFlagItemRemoved = 0x00000200;
static const int kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400;
static const int kFSEventStreamEventFlagItemRenamed = 0x00000800;
static const int kFSEventStreamEventFlagItemModified = 0x00001000;
static const int kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000;
static const int kFSEventStreamEventFlagItemChangeOwner = 0x00004000;
static const int kFSEventStreamEventFlagItemXattrMod = 0x00008000;
static const int kFSEventStreamEventFlagItemIsFile = 0x00010000;
static const int kFSEventStreamEventFlagItemIsDir = 0x00020000;
static const int kFSEventStreamEventFlagItemIsSymlink = 0x00040000;
#endif /* __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070 */
#endif /* defined(__APPLE__) */ #endif /* defined(__APPLE__) */
UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) { UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {

View File

@ -452,49 +452,48 @@ int uv_fs_event_start(uv_fs_event_t* handle,
uv_fs_event_cb cb, uv_fs_event_cb cb,
const char* path, const char* path,
unsigned int flags) { unsigned int flags) {
#if defined(__APPLE__)
struct stat statbuf;
#endif /* defined(__APPLE__) */
int fd; int fd;
if (uv__is_active(handle)) if (uv__is_active(handle))
return UV_EINVAL; return UV_EINVAL;
/* TODO open asynchronously - but how do we report back errors? */
fd = open(path, O_RDONLY);
if (fd == -1)
return UV__ERR(errno);
uv__handle_start(handle);
uv__io_init(&handle->event_watcher, uv__fs_event, fd);
handle->path = uv__strdup(path);
handle->cb = cb;
#if defined(__APPLE__) #if defined(__APPLE__)
if (uv__has_forked_with_cfrunloop)
goto fallback;
/* Nullify field to perform checks later */ /* Nullify field to perform checks later */
handle->cf_cb = NULL; handle->cf_cb = NULL;
handle->realpath = NULL; handle->realpath = NULL;
handle->realpath_len = 0; handle->realpath_len = 0;
handle->cf_flags = flags; handle->cf_flags = flags;
if (fstat(fd, &statbuf)) if (!uv__has_forked_with_cfrunloop) {
goto fallback; int r;
/* FSEvents works only with directories */ /* The fallback fd is not used */
if (!(statbuf.st_mode & S_IFDIR)) handle->event_watcher.fd = -1;
goto fallback; handle->path = uv__strdup(path);
if (handle->path == NULL)
/* The fallback fd is no longer needed */ return UV_ENOMEM;
uv__close(fd); handle->cb = cb;
handle->event_watcher.fd = -1; r = uv__fsevents_init(handle);
if (r == 0) {
return uv__fsevents_init(handle); uv__handle_start(handle);
} else {
fallback: uv__free(handle->path);
handle->path = NULL;
}
return r;
}
#endif /* defined(__APPLE__) */ #endif /* defined(__APPLE__) */
/* TODO open asynchronously - but how do we report back errors? */
fd = open(path, O_RDONLY);
if (fd == -1)
return UV__ERR(errno);
handle->path = uv__strdup(path);
if (handle->path == NULL)
return UV_ENOMEM;
handle->cb = cb;
uv__handle_start(handle);
uv__io_init(&handle->event_watcher, uv__fs_event, fd);
uv__io_start(handle->loop, &handle->event_watcher, POLLIN); uv__io_start(handle->loop, &handle->event_watcher, POLLIN);
return 0; return 0;
@ -502,29 +501,29 @@ fallback:
int uv_fs_event_stop(uv_fs_event_t* handle) { int uv_fs_event_stop(uv_fs_event_t* handle) {
int r;
r = 0;
if (!uv__is_active(handle)) if (!uv__is_active(handle))
return 0; return 0;
uv__handle_stop(handle); uv__handle_stop(handle);
#if defined(__APPLE__) #if defined(__APPLE__)
if (uv__has_forked_with_cfrunloop || uv__fsevents_close(handle)) if (!uv__has_forked_with_cfrunloop)
#endif /* defined(__APPLE__) */ r = uv__fsevents_close(handle);
{ #endif
if (handle->event_watcher.fd != -1) {
uv__io_close(handle->loop, &handle->event_watcher); uv__io_close(handle->loop, &handle->event_watcher);
uv__close(handle->event_watcher.fd);
handle->event_watcher.fd = -1;
} }
uv__free(handle->path); uv__free(handle->path);
handle->path = NULL; handle->path = NULL;
if (handle->event_watcher.fd != -1) { return r;
/* When FSEvents is used, we don't use the event_watcher's fd under certain
* confitions. (see uv_fs_event_start) */
uv__close(handle->event_watcher.fd);
handle->event_watcher.fd = -1;
}
return 0;
} }

View File

@ -576,6 +576,14 @@ TEST_IMPL(fs_event_watch_file_exact_path) {
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");
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12)
/* Empirically, FSEvents seems to (reliably) report the preceeding
* create_file events prior to macOS 10.11.6 in the subsequent fs_watch
* creation, but that behavior hasn't been observed to occur on newer
* versions. Give a long delay here to let the system settle before running
* the test. */
uv_sleep(1100);
#endif
r = uv_fs_event_init(loop, &fs_event); r = uv_fs_event_init(loop, &fs_event);
ASSERT(r == 0); ASSERT(r == 0);
@ -648,7 +656,7 @@ TEST_IMPL(fs_event_watch_file_current_dir) {
r = uv_timer_init(loop, &timer); r = uv_timer_init(loop, &timer);
ASSERT(r == 0); ASSERT(r == 0);
r = uv_timer_start(&timer, timer_cb_touch, 100, 0); r = uv_timer_start(&timer, timer_cb_touch, 1100, 0);
ASSERT(r == 0); ASSERT(r == 0);
ASSERT(timer_cb_touch_called == 0); ASSERT(timer_cb_touch_called == 0);
@ -936,32 +944,48 @@ TEST_IMPL(fs_event_getpath) {
RETURN_SKIP(NO_FS_EVENTS); RETURN_SKIP(NO_FS_EVENTS);
#endif #endif
uv_loop_t* loop = uv_default_loop(); uv_loop_t* loop = uv_default_loop();
unsigned i;
int r; int r;
char buf[1024]; char buf[1024];
size_t len; size_t len;
const char* const watch_dir[] = {
"watch_dir",
"watch_dir/",
"watch_dir///",
"watch_dir/subfolder/..",
"watch_dir//subfolder//..//",
};
create_dir("watch_dir"); create_dir("watch_dir");
create_dir("watch_dir/subfolder");
r = uv_fs_event_init(loop, &fs_event);
ASSERT(r == 0);
len = sizeof buf;
r = uv_fs_event_getpath(&fs_event, buf, &len);
ASSERT(r == UV_EINVAL);
r = uv_fs_event_start(&fs_event, fail_cb, "watch_dir", 0);
ASSERT(r == 0);
len = sizeof buf;
r = uv_fs_event_getpath(&fs_event, buf, &len);
ASSERT(r == 0);
ASSERT(buf[len - 1] != 0);
ASSERT(buf[len] == '\0');
ASSERT(memcmp(buf, "watch_dir", len) == 0);
r = uv_fs_event_stop(&fs_event);
ASSERT(r == 0);
uv_close((uv_handle_t*) &fs_event, close_cb);
uv_run(loop, UV_RUN_DEFAULT); for (i = 0; i < ARRAY_SIZE(watch_dir); i++) {
r = uv_fs_event_init(loop, &fs_event);
ASSERT(r == 0);
len = sizeof buf;
r = uv_fs_event_getpath(&fs_event, buf, &len);
ASSERT(r == UV_EINVAL);
r = uv_fs_event_start(&fs_event, fail_cb, watch_dir[i], 0);
ASSERT(r == 0);
len = 0;
r = uv_fs_event_getpath(&fs_event, buf, &len);
ASSERT(r == UV_ENOBUFS);
ASSERT(len < sizeof buf); /* sanity check */
ASSERT(len == strlen(watch_dir[i]) + 1);
r = uv_fs_event_getpath(&fs_event, buf, &len);
ASSERT(r == 0);
ASSERT(len == strlen(watch_dir[i]));
ASSERT(strcmp(buf, watch_dir[i]) == 0);
r = uv_fs_event_stop(&fs_event);
ASSERT(r == 0);
uv_close((uv_handle_t*) &fs_event, close_cb);
ASSERT(close_cb_called == 1); uv_run(loop, UV_RUN_DEFAULT);
ASSERT(close_cb_called == 1);
close_cb_called = 0;
}
remove("watch_dir/"); remove("watch_dir/");
MAKE_VALGRIND_HAPPY(); MAKE_VALGRIND_HAPPY();
@ -1082,6 +1106,9 @@ TEST_IMPL(fs_event_watch_invalid_path) {
r = uv_fs_event_start(&fs_event, fs_event_cb_file, "<:;", 0); r = uv_fs_event_start(&fs_event, fs_event_cb_file, "<:;", 0);
ASSERT(r != 0); ASSERT(r != 0);
ASSERT(uv_is_active((uv_handle_t*) &fs_event) == 0); ASSERT(uv_is_active((uv_handle_t*) &fs_event) == 0);
r = uv_fs_event_start(&fs_event, fs_event_cb_file, "", 0);
ASSERT(r != 0);
ASSERT(uv_is_active((uv_handle_t*) &fs_event) == 0);
MAKE_VALGRIND_HAPPY(); MAKE_VALGRIND_HAPPY();
return 0; return 0;
} }

View File

@ -436,9 +436,13 @@ TEST_DECLARE (fork_socketpair)
TEST_DECLARE (fork_socketpair_started) TEST_DECLARE (fork_socketpair_started)
TEST_DECLARE (fork_signal_to_child) TEST_DECLARE (fork_signal_to_child)
TEST_DECLARE (fork_signal_to_child_closed) TEST_DECLARE (fork_signal_to_child_closed)
#ifndef __APPLE__ /* This is forbidden in a fork child: The process has forked
and you cannot use this CoreFoundation functionality
safely. You MUST exec(). */
TEST_DECLARE (fork_fs_events_child) TEST_DECLARE (fork_fs_events_child)
TEST_DECLARE (fork_fs_events_child_dir) TEST_DECLARE (fork_fs_events_child_dir)
TEST_DECLARE (fork_fs_events_file_parent_child) TEST_DECLARE (fork_fs_events_file_parent_child)
#endif
#ifndef __MVS__ #ifndef __MVS__
TEST_DECLARE (fork_threadpool_queue_work_simple) TEST_DECLARE (fork_threadpool_queue_work_simple)
#endif #endif
@ -945,9 +949,11 @@ TASK_LIST_START
TEST_ENTRY (fork_socketpair_started) TEST_ENTRY (fork_socketpair_started)
TEST_ENTRY (fork_signal_to_child) TEST_ENTRY (fork_signal_to_child)
TEST_ENTRY (fork_signal_to_child_closed) TEST_ENTRY (fork_signal_to_child_closed)
#ifndef __APPLE__
TEST_ENTRY (fork_fs_events_child) TEST_ENTRY (fork_fs_events_child)
TEST_ENTRY (fork_fs_events_child_dir) TEST_ENTRY (fork_fs_events_child_dir)
TEST_ENTRY (fork_fs_events_file_parent_child) TEST_ENTRY (fork_fs_events_file_parent_child)
#endif
#ifndef __MVS__ #ifndef __MVS__
TEST_ENTRY (fork_threadpool_queue_work_simple) TEST_ENTRY (fork_threadpool_queue_work_simple)
#endif #endif