From 2aedc6befff0ce599bc1eb841bb904459b7e51ec Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 14 Aug 2024 21:24:27 -0500 Subject: [PATCH] Begin stubbing out win32 abstraction. Win32 abstraction will use ReadDirectoryChanges with overlapped IO to get results. Some work will be required to allow for single file watches, as well as allow for recursive watching on Linux. Unfortunately, various operating systems have very differnet abstractions here. I would rather expose inotify, kqueue, and ReadDirectoryChanges fairly transparently before adding shims to make cross platform code easier. --- src/core/filewatch.c | 135 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 18 deletions(-) diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 05dd04e9..cc97f65f 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -29,23 +29,42 @@ #ifdef JANET_EV #ifdef JANET_FILEWATCH -typedef struct { - JanetTable *watch_descriptors; - JanetStream *stream; - JanetChannel *channel; - uint32_t default_flags; -} JanetWatcher; - -#ifdef JANET_LINUX - -#include -#include +#ifdef JANET_WINDOWS +#include +#endif typedef struct { const char *name; uint32_t flag; } JanetWatchFlagName; +typedef struct { +#ifdef JANET_WINDOWS + OVERLAPPED overlapped; +#else + JanetStream *stream; +#endif + JanetTable watch_descriptors; + JanetChannel *channel; + uint32_t default_flags; +} JanetWatcher; + +/* Reject certain filename events without sending anything to the channel + * to make things faster and not waste time and memory creating events. This + * should also let us watch only certain file names, patterns, etc. */ +static int janet_watch_filter(JanetWatcher *watcher, Janet filename, int wd) { + /* TODO - add filtering */ + (void) watcher; + (void) filename; + (void) wd; + return 0; +} + +#ifdef JANET_LINUX + +#include +#include + static const JanetWatchFlagName watcher_flags_linux[] = { {"access", IN_ACCESS}, {"all", IN_ALL_EVENTS}, @@ -92,7 +111,7 @@ static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uin if (fd == -1) { janet_panicv(janet_ev_lasterr()); } - watcher->watch_descriptors = janet_table(0); + janet_table_init_raw(&watcher->watch_descriptors, 0); watcher->channel = channel; watcher->default_flags = default_flags; watcher->stream = janet_stream(fd, JANET_STREAM_READABLE, NULL); @@ -109,13 +128,13 @@ static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t } Janet name = janet_cstringv(path); Janet wd = janet_wrap_integer(result); - janet_table_put(watcher->watch_descriptors, name, wd); - janet_table_put(watcher->watch_descriptors, wd, name); + janet_table_put(&watcher->watch_descriptors, name, wd); + janet_table_put(&watcher->watch_descriptors, wd, name); } static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { if (watcher->stream == NULL) janet_panic("watcher closed"); - Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path)); + Janet check = janet_table_get(&watcher->watch_descriptors, janet_cstringv(path)); janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor"); int watch_handle = janet_unwrap_integer(check); int result; @@ -197,8 +216,11 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { cursor += inevent.len; } + /* Filter events by pattern */ + if (!janet_watch_filter(watcher, name, inevent.wd)) continue; + /* Got an event */ - Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); + Janet path = janet_table_get(&watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); JanetKV *event = janet_struct_begin(6); janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd)); janet_struct_put(event, janet_ckeywordv("wd-path"), path); @@ -226,6 +248,76 @@ static void janet_watcher_listen(JanetWatcher *watcher) { janet_async_start(watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, watcher); } +#elif JANET_WINDOWS + +static const JanetWatchFlagName watcher_flags_windows[] = { + {"file-name", FILE_NOTIFY_CHANGE_FILE_NAME}, + {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, + {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, + {"size", FILE_NOTIFY_CHANGE_SIZE}, + {"last-write", FILE_NOTIFY_CHANGE_LAST_WRITE}, + {"last-access", FILE_NOTIFY_CHANGE_LAST_ACCESS}, + {"creation", FILE_NOTIFY_CHANGE_CREATION}, + {"security", FILE_NOTIFY_CHANGE_SECURITY} +}; + +static uint32_t decode_watch_flags(const Janet *options, int32_t n) { + uint32_t flags = 0; + for (int32_t i = 0; i < n; i++) { + if (!(janet_checktype(options[i], JANET_KEYWORD))) { + janet_panicf("expected keyword, got %v", options[i]); + } + JanetKeyword keyw = janet_unwrap_keyword(options[i]); + const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows, + sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), + sizeof(JanetWatchFlagName), + keyw); + if (!result) { + janet_panicf("unknown windows filewatch flag %v", options[i]); + } + flags |= result->flag; + } + return flags; +} + +static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { + janet_table_init_raw(&watcher->watch_descriptors, 0); + watcher->channel = channel; + watcher->default_flags = default_flags; +} + +static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { + HANDLE handle = CreateFileA(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, + 0); + if (handle == INVALID_HANDLE_VALUE) { + janet_panicv(janet_ev_lasterr()); + } + Janet pathv = janet_wrap_cstringv(path); + Janet pointer = janet_wrap_pointer((void *) handle); + janet_table_put(&watcher->watch_descriptors, pathv, pointer); + janet_table_put(&watcher->watch_descriptors, pointer, pathv); + /* TODO - if listening, also listen for this new path */ +} + +static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { + Janet pathv = janet_wrap_cstringv(path); + Janet pointer = janet_table_get(&watcher->watch_descriptors, pathv); + if (janet_checktype(pointer, JANET_NIL)) { + janet_panicf("path %v is not being watched", pathv); + } + janet_table_remove(&watcher->watch_descriptors, pathv); + janet_table_remove(&watcher->watch_descriptors, pointer); +} + +static void janet_watcher_listen(JanetWatcher *watcher) { + (void) watcher; + janet_panic("nyi"); +} + #else /* Default implementation */ @@ -270,13 +362,20 @@ static int janet_filewatch_mark(void *p, size_t s) { (void) s; janet_mark(janet_wrap_abstract(watcher->stream)); janet_mark(janet_wrap_abstract(watcher->channel)); - janet_mark(janet_wrap_table(watcher->watch_descriptors)); + janet_mark(janet_wrap_table(&watcher->watch_descriptors)); + return 0; +} + +static int janet_filewatch_gc(void *p, size_t s) { + JanetWatcher *watcher = (JanetWatcher *) p; + (void) s; + janet_table_deinit(&watcher->watch_descriptors); return 0; } static const JanetAbstractType janet_filewatch_at = { "filewatch/watcher", - NULL, + janet_filewatch_gc, janet_filewatch_mark, JANET_ATEND_GCMARK };