Skip to content

Commit

Permalink
Begin stubbing out win32 abstraction.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bakpakin committed Aug 15, 2024
1 parent af2eb06 commit 2aedc6b
Showing 1 changed file with 112 additions and 13 deletions.
125 changes: 112 additions & 13 deletions src/core/filewatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,42 @@
#ifdef JANET_EV
#ifdef JANET_FILEWATCH

#ifdef JANET_WINDOWS
#include <windows.h>
#endif

typedef struct {
const char *name;
uint32_t flag;
} JanetWatchFlagName;

typedef struct {
JanetTable *watch_descriptors;
#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 <sys/inotify.h>
#include <unistd.h>

typedef struct {
const char *name;
uint32_t flag;
} JanetWatchFlagName;

static const JanetWatchFlagName watcher_flags_linux[] = {
{"access", IN_ACCESS},
{"all", IN_ALL_EVENTS},
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
};
Expand Down

0 comments on commit 2aedc6b

Please sign in to comment.