diff --git a/config-unix.mk b/config-unix.mk index 045987fe..8eb1c6e1 100644 --- a/config-unix.mk +++ b/config-unix.mk @@ -63,7 +63,7 @@ EIO_CONFIG=config_linux.h CSTDFLAG += -D_GNU_SOURCE CPPFLAGS += -Isrc/ares/config_linux LINKFLAGS+=-lrt -OBJS += src/unix/linux.o +OBJS += src/unix/linux/core.o src/unix/linux/inotify.o endif ifeq (FreeBSD,$(uname_S)) diff --git a/include/uv-private/uv-unix.h b/include/uv-private/uv-unix.h index 24ef37cb..4ce1cf87 100644 --- a/include/uv-private/uv-unix.h +++ b/include/uv-private/uv-unix.h @@ -55,6 +55,18 @@ typedef pthread_rwlock_t uv_rwlock_t; typedef void* uv_lib_t; #define UV_DYNAMIC /* empty */ +#if __linux__ +# define UV_LOOP_PRIVATE_PLATFORM_FIELDS \ + /* RB_HEAD(uv__inotify_watchers, uv_fs_event_s) */ \ + struct uv__inotify_watchers { \ + struct uv_fs_event_s* rbh_root; \ + } inotify_watchers; \ + ev_io inotify_read_watcher; \ + int inotify_fd; +#else +# define UV_LOOP_PRIVATE_PLATFORM_FIELDS +#endif + #define UV_LOOP_PRIVATE_FIELDS \ ares_channel channel; \ /* \ @@ -65,7 +77,8 @@ typedef void* uv_lib_t; ev_timer timer; \ /* Poll result queue */ \ eio_channel uv_eio_channel; \ - struct ev_loop* ev; + struct ev_loop* ev; \ + UV_LOOP_PRIVATE_PLATFORM_FIELDS #define UV_REQ_BUFSML_SIZE (4) @@ -195,9 +208,16 @@ typedef void* uv_lib_t; /* UV_FS_EVENT_PRIVATE_FIELDS */ #if defined(__linux__) -#define UV_FS_EVENT_PRIVATE_FIELDS \ - ev_io read_watcher; \ - uv_fs_event_cb cb; \ +#define UV_FS_EVENT_PRIVATE_FIELDS \ + /* RB_ENTRY(fs_event_s) node; */ \ + struct { \ + struct uv_fs_event_s* rbe_left; \ + struct uv_fs_event_s* rbe_right; \ + struct uv_fs_event_s* rbe_parent; \ + int rbe_color; \ + } node; \ + ev_io read_watcher; \ + uv_fs_event_cb cb; #elif (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060) \ || defined(__FreeBSD__) \ diff --git a/include/uv.h b/include/uv.h index f4ee49cc..4af154e9 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1504,6 +1504,8 @@ struct uv_loop_s { #undef UV_FS_REQ_PRIVATE_FIELDS #undef UV_WORK_PRIVATE_FIELDS #undef UV_FS_EVENT_PRIVATE_FIELDS +#undef UV_LOOP_PRIVATE_FIELDS +#undef UV_LOOP_PRIVATE_PLATFORM_FIELDS #ifdef __cplusplus } diff --git a/src/unix/core.c b/src/unix/core.c index af39fc80..93345a3b 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -157,6 +157,7 @@ static int uv__loop_init(uv_loop_t* loop, #endif ev_set_userdata(loop->ev, loop); eio_channel_init(&loop->uv_eio_channel, loop); + uv__loop_platform_init(loop); return 0; } @@ -179,11 +180,10 @@ uv_loop_t* uv_loop_new(void) { void uv_loop_delete(uv_loop_t* loop) { uv_ares_destroy(loop, loop->channel); ev_loop_destroy(loop->ev); - + uv__loop_platform_delete(loop); #ifndef NDEBUG - memset(loop, 0, sizeof *loop); + memset(loop, -1, sizeof *loop); #endif - if (loop == default_loop_ptr) default_loop_ptr = NULL; else diff --git a/src/unix/internal.h b/src/unix/internal.h index 3591090d..9a95a6f3 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -217,4 +217,14 @@ void uv__fs_event_destroy(uv_fs_event_t* handle); int uv__make_socketpair(int fds[2], int flags); int uv__make_pipe(int fds[2], int flags); +#if __linux__ +void uv__inotify_loop_init(uv_loop_t* loop); +void uv__inotify_loop_delete(uv_loop_t* loop); +# define uv__loop_platform_init(loop) uv__inotify_loop_init(loop) +# define uv__loop_platform_delete(loop) uv__inotify_loop_delete(loop) +#else +# define uv__loop_platform_init(loop) +# define uv__loop_platform_delete(loop) +#endif + #endif /* UV_UNIX_INTERNAL_H_ */ diff --git a/src/unix/linux.c b/src/unix/linux/core.c similarity index 67% rename from src/unix/linux.c rename to src/unix/linux/core.c index f6e09fc6..88c92f45 100644 --- a/src/unix/linux.c +++ b/src/unix/linux/core.c @@ -19,7 +19,7 @@ */ #include "uv.h" -#include "internal.h" +#include "../internal.h" #include #include @@ -48,91 +48,6 @@ #undef NANOSEC #define NANOSEC 1000000000 -#undef HAVE_INOTIFY_INIT -#undef HAVE_INOTIFY_INIT1 -#undef HAVE_INOTIFY_ADD_WATCH -#undef HAVE_INOTIFY_RM_WATCH - -#if __NR_inotify_init -# define HAVE_INOTIFY_INIT 1 -#endif -#if __NR_inotify_init1 -# define HAVE_INOTIFY_INIT1 1 -#endif -#if __NR_inotify_add_watch -# define HAVE_INOTIFY_ADD_WATCH 1 -#endif -#if __NR_inotify_rm_watch -# define HAVE_INOTIFY_RM_WATCH 1 -#endif - -#if HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 -# undef IN_ACCESS -# undef IN_MODIFY -# undef IN_ATTRIB -# undef IN_CLOSE_WRITE -# undef IN_CLOSE_NOWRITE -# undef IN_OPEN -# undef IN_MOVED_FROM -# undef IN_MOVED_TO -# undef IN_CREATE -# undef IN_DELETE -# undef IN_DELETE_SELF -# undef IN_MOVE_SELF -# define IN_ACCESS 0x001 -# define IN_MODIFY 0x002 -# define IN_ATTRIB 0x004 -# define IN_CLOSE_WRITE 0x008 -# define IN_CLOSE_NOWRITE 0x010 -# define IN_OPEN 0x020 -# define IN_MOVED_FROM 0x040 -# define IN_MOVED_TO 0x080 -# define IN_CREATE 0x100 -# define IN_DELETE 0x200 -# define IN_DELETE_SELF 0x400 -# define IN_MOVE_SELF 0x800 -struct inotify_event { - int32_t wd; - uint32_t mask; - uint32_t cookie; - uint32_t len; - /* char name[0]; */ -}; -#endif /* HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ - -#undef IN_CLOEXEC -#undef IN_NONBLOCK - -#if HAVE_INOTIFY_INIT1 -# define IN_CLOEXEC O_CLOEXEC -# define IN_NONBLOCK O_NONBLOCK -#endif /* HAVE_INOTIFY_INIT1 */ - -#if HAVE_INOTIFY_INIT -inline static int inotify_init(void) { - return syscall(__NR_inotify_init); -} -#endif /* HAVE_INOTIFY_INIT */ - -#if HAVE_INOTIFY_INIT1 -inline static int inotify_init1(int flags) { - return syscall(__NR_inotify_init1, flags); -} -#endif /* HAVE_INOTIFY_INIT1 */ - -#if HAVE_INOTIFY_ADD_WATCH -inline static int inotify_add_watch(int fd, const char* path, uint32_t mask) { - return syscall(__NR_inotify_add_watch, fd, path, mask); -} -#endif /* HAVE_INOTIFY_ADD_WATCH */ - -#if HAVE_INOTIFY_RM_WATCH -inline static int inotify_rm_watch(int fd, uint32_t wd) { - return syscall(__NR_inotify_rm_watch, fd, wd); -} -#endif /* HAVE_INOTIFY_RM_WATCH */ - - static char buf[MAXPATHLEN + 1]; static struct { @@ -141,13 +56,6 @@ static struct { } process_title; -/* Don't look aghast, this is exactly how glibc's basename() works. */ -static char* basename_r(const char* path) { - char* s = strrchr(path, '/'); - return s ? (s + 1) : (char*)path; -} - - /* * There's probably some way to get time from Linux than gettimeofday(). What * it is, I don't know. @@ -550,153 +458,3 @@ void uv_free_interface_addresses(uv_interface_address_t* addresses, free(addresses); } - -#if HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 - -static int new_inotify_fd(void) { -#if HAVE_INOTIFY_INIT1 - return inotify_init1(IN_NONBLOCK | IN_CLOEXEC); -#else - int fd; - - if ((fd = inotify_init()) == -1) - return -1; - - if (uv__cloexec(fd, 1) || uv__nonblock(fd, 1)) { - SAVE_ERRNO(uv__close(fd)); - fd = -1; - } - - return fd; -#endif -} - - -static void uv__inotify_read(EV_P_ ev_io* w, int revents) { - struct inotify_event* e; - uv_fs_event_t* handle; - const char* filename; - ssize_t size; - int events; - char *p; - /* needs to be large enough for sizeof(inotify_event) + strlen(filename) */ - char buf[4096]; - - handle = container_of(w, uv_fs_event_t, read_watcher); - - do { - do { - size = read(handle->fd, buf, sizeof buf); - } - while (size == -1 && errno == EINTR); - - if (size == -1) { - assert(errno == EAGAIN || errno == EWOULDBLOCK); - break; - } - - assert(size > 0); /* pre-2.6.21 thing, size=0 == read buffer too small */ - - /* Now we have one or more inotify_event structs. */ - for (p = buf; p < buf + size; p += sizeof(*e) + e->len) { - e = (void*)p; - - events = 0; - if (e->mask & (IN_ATTRIB|IN_MODIFY)) - events |= UV_CHANGE; - if (e->mask & ~(IN_ATTRIB|IN_MODIFY)) - events |= UV_RENAME; - - /* inotify does not return the filename when monitoring a single file - * for modifications. Repurpose the filename for API compatibility. - * I'm not convinced this is a good thing, maybe it should go. - */ - filename = e->len ? (const char*) (e + 1) : basename_r(handle->filename); - - handle->cb(handle, filename, events, 0); - - if (handle->fd == -1) - break; - } - } - while (handle->fd != -1); /* handle might've been closed by callback */ -} - - -int uv_fs_event_init(uv_loop_t* loop, - uv_fs_event_t* handle, - const char* filename, - uv_fs_event_cb cb, - int flags) { - int events; - int fd; - - loop->counters.fs_event_init++; - - /* We don't support any flags yet. */ - assert(!flags); - - /* - * TODO share a single inotify fd across the event loop? - * We'll run into fs.inotify.max_user_instances if we - * keep creating new inotify fds. - */ - if ((fd = new_inotify_fd()) == -1) { - uv__set_sys_error(loop, errno); - return -1; - } - - events = IN_ATTRIB - | IN_CREATE - | IN_MODIFY - | IN_DELETE - | IN_DELETE_SELF - | IN_MOVED_FROM - | IN_MOVED_TO; - - if (inotify_add_watch(fd, filename, events) == -1) { - uv__set_sys_error(loop, errno); - uv__close(fd); - return -1; - } - - uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_EVENT); - handle->filename = strdup(filename); /* this should go! */ - handle->cb = cb; - handle->fd = fd; - - ev_io_init(&handle->read_watcher, uv__inotify_read, fd, EV_READ); - ev_io_start(loop->ev, &handle->read_watcher); - ev_unref(loop->ev); - - return 0; -} - - -void uv__fs_event_destroy(uv_fs_event_t* handle) { - ev_ref(handle->loop->ev); - ev_io_stop(handle->loop->ev, &handle->read_watcher); - uv__close(handle->fd); - handle->fd = -1; - free(handle->filename); - handle->filename = NULL; -} - -#else /* !HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ - -int uv_fs_event_init(uv_loop_t* loop, - uv_fs_event_t* handle, - const char* filename, - uv_fs_event_cb cb, - int flags) { - loop->counters.fs_event_init++; - uv__set_sys_error(loop, ENOSYS); - return -1; -} - - -void uv__fs_event_destroy(uv_fs_event_t* handle) { - UNREACHABLE(); -} - -#endif /* HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ diff --git a/src/unix/linux/inotify.c b/src/unix/linux/inotify.c new file mode 100644 index 00000000..609be973 --- /dev/null +++ b/src/unix/linux/inotify.c @@ -0,0 +1,329 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "tree.h" +#include "../internal.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#undef HAVE_INOTIFY_INIT +#undef HAVE_INOTIFY_INIT1 +#undef HAVE_INOTIFY_ADD_WATCH +#undef HAVE_INOTIFY_RM_WATCH + +#if __NR_inotify_init +# define HAVE_INOTIFY_INIT 1 +#endif +#if __NR_inotify_init1 +# define HAVE_INOTIFY_INIT1 1 +#endif +#if __NR_inotify_add_watch +# define HAVE_INOTIFY_ADD_WATCH 1 +#endif +#if __NR_inotify_rm_watch +# define HAVE_INOTIFY_RM_WATCH 1 +#endif + +#if HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 +# undef IN_ACCESS +# undef IN_MODIFY +# undef IN_ATTRIB +# undef IN_CLOSE_WRITE +# undef IN_CLOSE_NOWRITE +# undef IN_OPEN +# undef IN_MOVED_FROM +# undef IN_MOVED_TO +# undef IN_CREATE +# undef IN_DELETE +# undef IN_DELETE_SELF +# undef IN_MOVE_SELF +# define IN_ACCESS 0x001 +# define IN_MODIFY 0x002 +# define IN_ATTRIB 0x004 +# define IN_CLOSE_WRITE 0x008 +# define IN_CLOSE_NOWRITE 0x010 +# define IN_OPEN 0x020 +# define IN_MOVED_FROM 0x040 +# define IN_MOVED_TO 0x080 +# define IN_CREATE 0x100 +# define IN_DELETE 0x200 +# define IN_DELETE_SELF 0x400 +# define IN_MOVE_SELF 0x800 +struct inotify_event { + int32_t wd; + uint32_t mask; + uint32_t cookie; + uint32_t len; + /* char name[0]; */ +}; +#endif /* HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ + +#undef IN_CLOEXEC +#undef IN_NONBLOCK + +#if HAVE_INOTIFY_INIT1 +# define IN_CLOEXEC O_CLOEXEC +# define IN_NONBLOCK O_NONBLOCK +#endif /* HAVE_INOTIFY_INIT1 */ + +#if HAVE_INOTIFY_INIT +inline static int inotify_init(void) { + return syscall(__NR_inotify_init); +} +#endif /* HAVE_INOTIFY_INIT */ + +#if HAVE_INOTIFY_INIT1 +inline static int inotify_init1(int flags) { + return syscall(__NR_inotify_init1, flags); +} +#endif /* HAVE_INOTIFY_INIT1 */ + +#if HAVE_INOTIFY_ADD_WATCH +inline static int inotify_add_watch(int fd, const char* path, uint32_t mask) { + return syscall(__NR_inotify_add_watch, fd, path, mask); +} +#endif /* HAVE_INOTIFY_ADD_WATCH */ + +#if HAVE_INOTIFY_RM_WATCH +inline static int inotify_rm_watch(int fd, uint32_t wd) { + return syscall(__NR_inotify_rm_watch, fd, wd); +} +#endif /* HAVE_INOTIFY_RM_WATCH */ + + +/* Don't look aghast, this is exactly how glibc's basename() works. */ +static char* basename_r(const char* path) { + char* s = strrchr(path, '/'); + return s ? (s + 1) : (char*)path; +} + + +static int compare_watchers(const uv_fs_event_t* a, const uv_fs_event_t* b) { + if (a->fd < b->fd) return -1; + if (a->fd > b->fd) return 1; + return 0; +} + + +RB_GENERATE_INTERNAL(uv__inotify_watchers, uv_fs_event_s, node, compare_watchers, + inline static __attribute__((unused))) + + +void uv__inotify_loop_init(uv_loop_t* loop) { + RB_INIT(&loop->inotify_watchers); + loop->inotify_fd = -1; +} + + +void uv__inotify_loop_delete(uv_loop_t* loop) { + if (loop->inotify_fd == -1) return; + ev_io_stop(loop->ev, &loop->inotify_read_watcher); + close(loop->inotify_fd); + loop->inotify_fd = -1; +} + + +#if HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 + +static void uv__inotify_read(EV_P_ ev_io* w, int revents); + + +static int new_inotify_fd(void) { +#if HAVE_INOTIFY_INIT1 + return inotify_init1(IN_NONBLOCK | IN_CLOEXEC); +#else + int fd; + + if ((fd = inotify_init()) == -1) + return -1; + + if (uv__cloexec(fd, 1) || uv__nonblock(fd, 1)) { + SAVE_ERRNO(uv__close(fd)); + return -1; + } + + return fd; +#endif +} + + +static int init_inotify(uv_loop_t* loop) { + if (loop->inotify_fd != -1) + return 0; + + loop->inotify_fd = new_inotify_fd(); + if (loop->inotify_fd == -1) { + uv__set_sys_error(loop, errno); + return -1; + } + + ev_io_init(&loop->inotify_read_watcher, + uv__inotify_read, + loop->inotify_fd, + EV_READ); + ev_io_start(loop->ev, &loop->inotify_read_watcher); + ev_unref(loop->ev); + + return 0; +} + + +static void add_watcher(uv_fs_event_t* handle) { + RB_INSERT(uv__inotify_watchers, &handle->loop->inotify_watchers, handle); +} + + +static uv_fs_event_t* find_watcher(uv_loop_t* loop, int wd) { + uv_fs_event_t handle; + handle.fd = wd; + return RB_FIND(uv__inotify_watchers, &loop->inotify_watchers, &handle); +} + + +static void remove_watcher(uv_fs_event_t* handle) { + RB_REMOVE(uv__inotify_watchers, &handle->loop->inotify_watchers, handle); +} + + +static void uv__inotify_read(EV_P_ ev_io* w, int revents) { + const struct inotify_event* e; + uv_fs_event_t* handle; + uv_loop_t* uv_loop; + const char* filename; + ssize_t size; + int events; + const char *p; + /* needs to be large enough for sizeof(inotify_event) + strlen(filename) */ + char buf[4096]; + + uv_loop = container_of(w, uv_loop_t, inotify_read_watcher); + + while (1) { + do { + size = read(uv_loop->inotify_fd, buf, sizeof buf); + } + while (size == -1 && errno == EINTR); + + if (size == -1) { + assert(errno == EAGAIN || errno == EWOULDBLOCK); + break; + } + + assert(size > 0); /* pre-2.6.21 thing, size=0 == read buffer too small */ + + /* Now we have one or more inotify_event structs. */ + for (p = buf; p < buf + size; p += sizeof(*e) + e->len) { + e = (const struct inotify_event*)p; + + events = 0; + if (e->mask & (IN_ATTRIB|IN_MODIFY)) + events |= UV_CHANGE; + if (e->mask & ~(IN_ATTRIB|IN_MODIFY)) + events |= UV_RENAME; + + handle = find_watcher(uv_loop, e->wd); + if (handle == NULL) + continue; /* Handle has already been closed. */ + + /* inotify does not return the filename when monitoring a single file + * for modifications. Repurpose the filename for API compatibility. + * I'm not convinced this is a good thing, maybe it should go. + */ + filename = e->len ? (const char*) (e + 1) : basename_r(handle->filename); + + handle->cb(handle, filename, events, 0); + } + } +} + + +int uv_fs_event_init(uv_loop_t* loop, + uv_fs_event_t* handle, + const char* filename, + uv_fs_event_cb cb, + int flags) { + int events; + int wd; + + loop->counters.fs_event_init++; + + /* We don't support any flags yet. */ + assert(!flags); + + if (init_inotify(loop)) return -1; + + events = IN_ATTRIB + | IN_CREATE + | IN_MODIFY + | IN_DELETE + | IN_DELETE_SELF + | IN_MOVED_FROM + | IN_MOVED_TO; + + wd = inotify_add_watch(loop->inotify_fd, filename, events); + if (wd == -1) return uv__set_sys_error(loop, errno); + + uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_EVENT); + handle->filename = strdup(filename); + handle->cb = cb; + handle->fd = wd; + add_watcher(handle); + + return 0; +} + + +void uv__fs_event_destroy(uv_fs_event_t* handle) { + inotify_rm_watch(handle->loop->inotify_fd, handle->fd); + remove_watcher(handle); + handle->fd = -1; + + free(handle->filename); + handle->filename = NULL; +} + +#else /* !HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ + +int uv_fs_event_init(uv_loop_t* loop, + uv_fs_event_t* handle, + const char* filename, + uv_fs_event_cb cb, + int flags) { + loop->counters.fs_event_init++; + uv__set_sys_error(loop, ENOSYS); + return -1; +} + + +void uv__fs_event_destroy(uv_fs_event_t* handle) { + UNREACHABLE(); +} + +#endif /* HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1 */ diff --git a/uv.gyp b/uv.gyp index 75f3634e..89a9445b 100644 --- a/uv.gyp +++ b/uv.gyp @@ -217,7 +217,10 @@ }], [ 'OS=="linux"', { 'include_dirs': [ 'src/ares/config_linux' ], - 'sources': [ 'src/unix/linux.c' ], + 'sources': [ + 'src/unix/linux/core.c', + 'src/unix/linux/inotify.c', + ], 'defines': [ 'EV_CONFIG_H="config_linux.h"', 'EIO_CONFIG_H="config_linux.h"',