From 2a1c32a60c8340c3c7dcca7c3033b715427898bd Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 20 Sep 2011 06:45:51 +0200 Subject: [PATCH] linux: implement file watcher API --- include/uv-private/uv-linux.h | 29 +++++++ include/uv-private/uv-unix.h | 4 + src/unix/core.c | 7 ++ src/unix/internal.h | 3 + src/unix/linux.c | 144 ++++++++++++++++++++++++++++++++++ test/test-fs-event.c | 2 + 6 files changed, 189 insertions(+) create mode 100644 include/uv-private/uv-linux.h diff --git a/include/uv-private/uv-linux.h b/include/uv-private/uv-linux.h new file mode 100644 index 00000000..7f7b0593 --- /dev/null +++ b/include/uv-private/uv-linux.h @@ -0,0 +1,29 @@ +/* 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. + */ + +#ifndef UV_LINUX_H +#define UV_LINUX_H + +#define UV_FS_EVENT_PRIVATE_FIELDS \ + ev_io read_watcher; \ + uv_fs_event_cb cb; \ + +#endif /* UV_LINUX_H */ diff --git a/include/uv-private/uv-unix.h b/include/uv-private/uv-unix.h index 6a0ef90b..1c6c046a 100644 --- a/include/uv-private/uv-unix.h +++ b/include/uv-private/uv-unix.h @@ -27,6 +27,10 @@ #include "ev.h" #include "eio.h" +#if defined(__linux__) +#include "uv-private/uv-linux.h" +#endif + #include #include #include diff --git a/src/unix/core.c b/src/unix/core.c index 58d12987..39d5641f 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -137,6 +137,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { ev_child_stop(process->loop->ev, &process->child_watcher); break; + case UV_FS_EVENT: + uv__fs_event_destroy((uv_fs_event_t*)handle); + break; + default: assert(0); } @@ -250,6 +254,9 @@ void uv__finish_close(uv_handle_t* handle) { assert(!ev_is_active(&((uv_process_t*)handle)->child_watcher)); break; + case UV_FS_EVENT: + break; + default: assert(0); break; diff --git a/src/unix/internal.h b/src/unix/internal.h index 98fc51fc..492efdad 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -110,4 +110,7 @@ int uv_pipe_cleanup(uv_pipe_t* handle); void uv__udp_destroy(uv_udp_t* handle); void uv__udp_watcher_stop(uv_udp_t* handle, ev_io* w); +/* fs */ +void uv__fs_event_destroy(uv_fs_event_t* handle); + #endif /* UV_UNIX_INTERNAL_H_ */ diff --git a/src/unix/linux.c b/src/unix/linux.c index 03396e9c..e0f72bb0 100644 --- a/src/unix/linux.c +++ b/src/unix/linux.c @@ -19,15 +19,41 @@ */ #include "uv.h" +#include "internal.h" #include #include +#include +#include +#include +#include + +#include #include #include #undef NANOSEC #define NANOSEC 1000000000 +#define container_of(ptr, type, member) \ + ((type *) ((char *) (ptr) - offsetof(type, member))) + +#define SAVE_ERRNO(block) \ + do { \ + int _saved_errno = errno; \ + do { block; } while (0); \ + errno = _saved_errno; \ + } \ + while (0); + + +/* 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. @@ -49,3 +75,121 @@ int uv_exepath(char* buffer, size_t* size) { buffer[*size] = '\0'; return 0; } + + +static int new_inotify_fd(void) { +#if defined(IN_NONBLOCK) && defined(IN_CLOEXEC) + 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 ? e->name : basename_r(handle->filename); + + handle->cb(handle, filename, events, 0); + } + } + 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 fd; + + /* + * 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_err_new(loop, errno); + return -1; + } + + flags = IN_ATTRIB + | IN_CREATE + | IN_MODIFY + | IN_DELETE + | IN_DELETE_SELF + | IN_MOVED_FROM + | IN_MOVED_TO; + + if (inotify_add_watch(fd, filename, flags) == -1) { + uv_err_new(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); + + return 0; +} + + +void uv__fs_event_destroy(uv_fs_event_t* handle) { + ev_io_stop(handle->loop->ev, &handle->read_watcher); + uv__close(handle->fd); + handle->fd = -1; + free(handle->filename); +} diff --git a/test/test-fs-event.c b/test/test-fs-event.c index 70b1f50e..d249ec66 100644 --- a/test/test-fs-event.c +++ b/test/test-fs-event.c @@ -21,6 +21,8 @@ #include "uv.h" #include "task.h" + +#include #include uv_fs_event_t fs_event;