From d1972f7893a64d049157c67601428011db895a04 Mon Sep 17 00:00:00 2001 From: anth64 Date: Thu, 22 Jan 2026 00:24:04 +0100 Subject: [PATCH] refactor(platform): Unify watch logic and fix manual snapshot leaks Unlike inotify, Windows and BSD require manual state snapshots to detect specific file changes. This refactor standardizes that manual handling to ensure it is resource-safe and easy to follow. - Fix memory leaks in platform_directory_watch_check where temporary buffers (new_snaps, file_list) were not reliably freed on 'phantom' triggers or error paths. - Unify control flow using a 'goto' cleanup pattern to ensure deterministic resource deallocation. - Synchronizes the manual snapshot comparison logic between Windows and BSD to ensure identical LOAD/RELOAD/UNLOAD event behavior. - Simplifies the platform_directory_init_scan logic by removing redundant directory rewinds and nested checks. - Add WIN32_LEAN_AND_MEAN to optimize Windows header inclusion. - Align internal API signatures (is_module_loaded) for consistency. --- src/platform.c | 1134 +++++++++++++++++++++++------------------------- 1 file changed, 536 insertions(+), 598 deletions(-) diff --git a/src/platform.c b/src/platform.c index 02fe1a9..97e6f40 100644 --- a/src/platform.c +++ b/src/platform.c @@ -11,6 +11,7 @@ #include #include #elif defined(_WIN32) +#define WIN32_LEAN_AND_MEAN #include #else #include @@ -24,10 +25,38 @@ #endif int is_module_loaded(const char *filename, - char (*loaded_module_ids)[STK_MOD_ID_BUFFER], - size_t loaded_count); + char (*loaded_ids)[STK_MOD_ID_BUFFER], size_t count); uint8_t is_valid_module_file(const char *filename); +#ifndef __linux__ +typedef struct { + char filename[STK_PATH_MAX]; +#ifdef _WIN32 + FILETIME mtime; +#else + time_t mtime; +#endif +} platform_snapshot_t; + +typedef struct { + char path[STK_PATH_MAX]; + platform_snapshot_t *snaps; + size_t count; + union { +#ifdef _WIN32 + HANDLE change_handle; +#else + struct { + int kq; + int dir_fd; + int *file_fds; + size_t file_fd_count; + } k; +#endif + } watch; +} platform_watch_context_t; +#endif + int platform_mkdir(const char *path) { #ifdef _WIN32 @@ -51,65 +80,57 @@ int platform_copy_file(const char *from, const char *to) #ifdef _WIN32 return CopyFileA(from, to, FALSE) ? 0 : -1; #else - FILE *src, *dst; - char buffer[STK_PATH_MAX]; - size_t bytes; - int result = 0; + FILE *src = NULL, *dst = NULL; + char buf[STK_PATH_MAX_OS]; + size_t n; + int ret = -1; src = fopen(from, "rb"); if (!src) - return -1; + goto cleanup; dst = fopen(to, "wb"); - if (!dst) { + if (!dst) + goto cleanup; + + while ((n = fread(buf, 1, sizeof(buf), src)) > 0) + fwrite(buf, 1, n, dst); + + ret = 0; + +cleanup: + if (src) fclose(src); - return -1; - } - - while ((bytes = fread(buffer, 1, sizeof(buffer), src)) > 0) { - if (fwrite(buffer, 1, bytes, dst) != bytes) { - result = -1; - break; - } - } - - fclose(src); - fclose(dst); - return result; + if (dst) + fclose(dst); + return ret; #endif } int platform_remove_dir(const char *path) { #ifdef _WIN32 - WIN32_FIND_DATAW find_data; - HANDLE find_handle; - WCHAR search_path[STK_PATH_MAX_OS]; - char temp_filename[STK_PATH_MAX]; - int len; + WIN32_FIND_DATAA fd; + HANDLE h; + char s[STK_PATH_MAX_OS], f[STK_PATH_MAX_OS]; + sprintf(s, "%s\\*", path); - swprintf(search_path, STK_PATH_MAX_OS, L"%S\\*", path); - find_handle = FindFirstFileW(search_path, &find_data); + h = FindFirstFileA(s, &fd); + if (h == INVALID_HANDLE_VALUE) + goto remove_dir; - if (find_handle != INVALID_HANDLE_VALUE) { - do { - if (find_data.dwFileAttributes & - FILE_ATTRIBUTE_DIRECTORY) - continue; + do { + if (strcmp(fd.cFileName, ".") == 0 || + strcmp(fd.cFileName, "..") == 0) + continue; - len = WideCharToMultiByte( - CP_UTF8, 0, find_data.cFileName, -1, temp_filename, - STK_PATH_MAX - 1, NULL, NULL); - if (len > 0) { - char full_path[STK_PATH_MAX_OS]; - snprintf(full_path, sizeof(full_path), "%s\\%s", - path, temp_filename); - DeleteFileA(full_path); - } - } while (FindNextFileW(find_handle, &find_data)); - FindClose(find_handle); - } + sprintf(f, "%s\\%s", path, fd.cFileName); + DeleteFileA(f); + } while (FindNextFileA(h, &fd)); + FindClose(h); + +remove_dir: return RemoveDirectoryA(path) ? 0 : -1; #else DIR *dir; @@ -120,207 +141,24 @@ int platform_remove_dir(const char *path) if (!dir) return -1; - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || - strcmp(entry->d_name, "..") == 0) - continue; +loop: + entry = readdir(dir); + if (!entry) + goto loop_end; - sprintf(filepath, "%s/%s", path, entry->d_name); - unlink(filepath); - } + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + goto loop; + sprintf(filepath, "%s/%s", path, entry->d_name); + unlink(filepath); + goto loop; + +loop_end: closedir(dir); return rmdir(path); #endif } -#if !defined(__linux__) && !defined(_WIN32) -typedef struct { - char filename[STK_PATH_MAX]; - time_t mtime; -} file_snapshot_t; - -typedef struct { - int kq; - int dir_fd; - char path[STK_PATH_MAX]; - int *file_fds; - size_t file_fd_count; - file_snapshot_t *snapshots; - size_t snapshot_count; - size_t snapshot_capacity; -} kqueue_watch_context_t; - -static file_snapshot_t *create_snapshot(const char *path, size_t *out_count, - size_t *out_capacity) -{ - DIR *dir; - struct dirent *entry; - struct stat file_stat; - char work_path[STK_PATH_MAX_OS]; - file_snapshot_t *snapshots; - size_t count, capacity, index; - - snapshots = NULL; - count = 0; - capacity = 0; - index = 0; - - dir = opendir(path); - if (!dir) { - *out_count = 0; - *out_capacity = 0; - return NULL; - } - - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || - strcmp(entry->d_name, "..") == 0) - continue; - - if (!is_valid_module_file(entry->d_name)) - continue; - - sprintf(work_path, "%s/%s", path, entry->d_name); - if (stat(work_path, &file_stat) == 0 && - S_ISREG(file_stat.st_mode)) - count++; - } - - if (count == 0) { - closedir(dir); - *out_count = 0; - *out_capacity = 0; - return NULL; - } - - capacity = count; - snapshots = malloc(capacity * sizeof(file_snapshot_t)); - if (!snapshots) { - closedir(dir); - *out_count = 0; - *out_capacity = 0; - return NULL; - } - - rewinddir(dir); - - while ((entry = readdir(dir)) != NULL && index < count) { - if (strcmp(entry->d_name, ".") == 0 || - strcmp(entry->d_name, "..") == 0) - continue; - - if (!is_valid_module_file(entry->d_name)) - continue; - - sprintf(work_path, "%s/%s", path, entry->d_name); - if (stat(work_path, &file_stat) != 0 || - !S_ISREG(file_stat.st_mode)) - continue; - - strncpy(snapshots[index].filename, entry->d_name, - STK_PATH_MAX - 1); - snapshots[index].filename[STK_PATH_MAX - 1] = '\0'; - snapshots[index].mtime = file_stat.st_mtime; - index++; - } - - closedir(dir); - *out_count = index; - *out_capacity = capacity; - return snapshots; -} - -static file_snapshot_t *find_in_snapshot(file_snapshot_t *snapshots, - size_t count, const char *filename) -{ - size_t i; - for (i = 0; i < count; i++) { - if (strcmp(snapshots[i].filename, filename) == 0) { - return &snapshots[i]; - } - } - return NULL; -} - -static void setup_file_watches(kqueue_watch_context_t *ctx) -{ - DIR *dir; - struct dirent *entry; - char filepath[STK_PATH_MAX_OS]; - struct kevent event; - size_t idx; - int fd; - - dir = opendir(ctx->path); - if (!dir) - return; - - ctx->file_fd_count = 0; - while ((entry = readdir(dir)) != NULL) { - if (is_valid_module_file(entry->d_name)) - ctx->file_fd_count++; - } - - if (ctx->file_fd_count == 0) { - closedir(dir); - ctx->file_fds = NULL; - return; - } - - ctx->file_fds = malloc(ctx->file_fd_count * sizeof(int)); - if (!ctx->file_fds) { - closedir(dir); - ctx->file_fd_count = 0; - return; - } - - rewinddir(dir); - idx = 0; - - while ((entry = readdir(dir)) != NULL && idx < ctx->file_fd_count) { - if (!is_valid_module_file(entry->d_name)) - continue; - - sprintf(filepath, "%s/%s", ctx->path, entry->d_name); - fd = open(filepath, O_RDONLY); - if (fd >= 0) { - ctx->file_fds[idx++] = fd; - EV_SET(&event, fd, EVFILT_VNODE, - EV_ADD | EV_ENABLE | EV_CLEAR, - NOTE_WRITE | NOTE_DELETE | NOTE_ATTRIB, 0, NULL); - kevent(ctx->kq, &event, 1, NULL, 0, NULL); - } - } - - ctx->file_fd_count = idx; - closedir(dir); -} - -static void cleanup_file_watches(kqueue_watch_context_t *ctx) -{ - size_t i; - - if (!ctx->file_fds) - return; - - for (i = 0; i < ctx->file_fd_count; i++) { - if (ctx->file_fds[i] >= 0) - close(ctx->file_fds[i]); - } - - free(ctx->file_fds); - ctx->file_fds = NULL; - ctx->file_fd_count = 0; -} - -static void refresh_file_watches(kqueue_watch_context_t *ctx) -{ - cleanup_file_watches(ctx); - setup_file_watches(ctx); -} -#endif - void *platform_load_library(const char *path) { #ifdef _WIN32 @@ -330,433 +168,533 @@ void *platform_load_library(const char *path) #endif } -void platform_unload_library(void *handle) +void platform_unload_library(void *h) { #ifdef _WIN32 - FreeLibrary((HMODULE)handle); + FreeLibrary((HMODULE)h); #else - dlclose(handle); + dlclose(h); #endif } -void *platform_get_symbol(void *handle, const char *symbol) +void *platform_get_symbol(void *h, const char *s) { #ifdef _WIN32 - return (void *)(intptr_t)GetProcAddress((HMODULE)handle, symbol); + return (void *)(intptr_t)GetProcAddress((HMODULE)h, s); #else - return dlsym(handle, symbol); + return dlsym(h, s); #endif } +char (*platform_directory_init_scan(const char *dir_path, size_t *out_count)) + [STK_PATH_MAX] { + size_t count = 0, i = 0; + char (*list)[STK_PATH_MAX] = NULL; +#ifdef _WIN32 + WIN32_FIND_DATAA fd; + HANDLE h; + char s[STK_PATH_MAX_OS]; + + sprintf(s, "%s\\*", dir_path); + h = FindFirstFileA(s, &fd); + if (h == INVALID_HANDLE_VALUE) + goto create_and_exit; + + do { + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + if (is_valid_module_file(fd.cFileName)) + count++; + } while (FindNextFileA(h, &fd)); + + FindClose(h); + + if (count == 0) + goto exit; + + list = malloc(count * sizeof(*list)); + if (!list) + goto exit; + + h = FindFirstFileA(s, &fd); + if (h == INVALID_HANDLE_VALUE) + goto exit; + + while (FindNextFileA(h, &fd) && i < count) { + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + continue; + if (is_valid_module_file(fd.cFileName)) + strncpy(list[i++], fd.cFileName, STK_PATH_MAX - 1); + } + FindClose(h); + goto exit; + + create_and_exit: + platform_mkdir(dir_path); + exit: + *out_count = i; + return list; +#else + DIR *d; + struct dirent *e; + struct stat st; + char f[STK_PATH_MAX_OS]; + + d = opendir(dir_path); + if (!d) + goto create_and_exit; + + count_loop: + e = readdir(d); + if (!e) + goto count_done; + + sprintf(f, "%s/%s", dir_path, e->d_name); + if (!is_valid_module_file(e->d_name)) + goto count_loop; + if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) + goto count_loop; + + count++; + goto count_loop; + + count_done: + if (count == 0) + goto close_and_exit; + + rewinddir(d); + list = malloc(count * sizeof(*list)); + if (!list) + goto close_and_exit; + + fill_loop: + e = readdir(d); + if (!e || i >= count) + goto close_and_exit; + + sprintf(f, "%s/%s", dir_path, e->d_name); + if (!is_valid_module_file(e->d_name)) + goto fill_loop; + if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) + goto fill_loop; + + strncpy(list[i++], e->d_name, STK_PATH_MAX - 1); + goto fill_loop; + + create_and_exit: + platform_mkdir(dir_path); + *out_count = 0; + return NULL; + + close_and_exit: + closedir(d); + *out_count = i; + return list; +#endif + } + +#if !defined(__linux__) && !defined(_WIN32) +static void update_watches(platform_watch_context_t *ctx) +{ + struct kevent ev; + DIR *d; + struct dirent *e; + char f[STK_PATH_MAX_OS]; + size_t i; + int fd; + int *tmp_fds; + + for (i = 0; i < ctx->watch.k.file_fd_count; i++) { + close(ctx->watch.k.file_fds[i]); + } + free(ctx->watch.k.file_fds); + ctx->watch.k.file_fds = NULL; + ctx->watch.k.file_fd_count = 0; + + EV_SET(&ev, ctx->watch.k.dir_fd, EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_WRITE | NOTE_DELETE | NOTE_RENAME, 0, NULL); + kevent(ctx->watch.k.kq, &ev, 1, NULL, 0, NULL); + + d = opendir(ctx->path); + if (!d) + return; + +scan_loop: + e = readdir(d); + if (!e) + goto scan_done; + + if (!is_valid_module_file(e->d_name)) + goto scan_loop; + + sprintf(f, "%s/%s", ctx->path, e->d_name); + fd = open(f, O_RDONLY); + if (fd < 0) + goto scan_loop; + + tmp_fds = realloc(ctx->watch.k.file_fds, + sizeof(int) * (ctx->watch.k.file_fd_count + 1)); + if (!tmp_fds) { + close(fd); + goto scan_loop; + } + + ctx->watch.k.file_fds = tmp_fds; + ctx->watch.k.file_fds[ctx->watch.k.file_fd_count++] = fd; + + EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_WRITE | NOTE_ATTRIB, 0, NULL); + kevent(ctx->watch.k.kq, &ev, 1, NULL, 0, NULL); + + goto scan_loop; + +scan_done: + closedir(d); +} +#endif + void *platform_directory_watch_start(const char *path) { -#ifdef __linux__ - int fd, wd; - fd = inotify_init1(IN_NONBLOCK); +#if defined(__linux__) + int fd = inotify_init1(IN_NONBLOCK); if (fd < 0) return NULL; - wd = inotify_add_watch( + inotify_add_watch( fd, path, IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM); - if (wd < 0) { - close(fd); - return NULL; - } return (void *)(long)fd; -#elif defined(_WIN32) - HANDLE handle = - CreateFileA(path, FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - return (handle == INVALID_HANDLE_VALUE) ? NULL : (void *)handle; #else - struct kevent event; - kqueue_watch_context_t *ctx; - - ctx = malloc(sizeof(kqueue_watch_context_t)); + platform_watch_context_t *ctx = + calloc(1, sizeof(platform_watch_context_t)); if (!ctx) return NULL; - - ctx->kq = kqueue(); - ctx->dir_fd = open(path, O_RDONLY); strncpy(ctx->path, path, STK_PATH_MAX - 1); - ctx->path[STK_PATH_MAX - 1] = '\0'; - ctx->file_fds = NULL; - ctx->file_fd_count = 0; - ctx->snapshots = create_snapshot(path, &ctx->snapshot_count, - &ctx->snapshot_capacity); - if (ctx->kq < 0 || ctx->dir_fd < 0) { - if (ctx->kq >= 0) - close(ctx->kq); - if (ctx->dir_fd >= 0) - close(ctx->dir_fd); - free(ctx->snapshots); - free(ctx); - return NULL; +#ifdef _WIN32 + { + WIN32_FIND_DATAA fd; + HANDLE h; + char s[STK_PATH_MAX_OS]; + void *tmp_snaps; + + ctx->watch.change_handle = FindFirstChangeNotificationA( + path, FALSE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_LAST_WRITE); + + sprintf(s, "%s\\*", path); + h = FindFirstFileA(s, &fd); + if (h == INVALID_HANDLE_VALUE) + goto done; + + win_scan_loop: + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + is_valid_module_file(fd.cFileName)) { + tmp_snaps = realloc(ctx->snaps, + (ctx->count + 1) * + sizeof(platform_snapshot_t)); + if (tmp_snaps) { + ctx->snaps = tmp_snaps; + strncpy(ctx->snaps[ctx->count].filename, + fd.cFileName, STK_PATH_MAX - 1); + ctx->snaps[ctx->count++].mtime = + fd.ftLastWriteTime; + } + } + if (FindNextFileA(h, &fd)) + goto win_scan_loop; + + FindClose(h); } +#else + { + DIR *d; + struct dirent *e; + struct stat st; + char f[STK_PATH_MAX_OS]; + void *tmp_snaps; - EV_SET(&event, ctx->dir_fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, - NOTE_WRITE | NOTE_DELETE | NOTE_RENAME, 0, NULL); - kevent(ctx->kq, &event, 1, NULL, 0, NULL); + ctx->watch.k.kq = kqueue(); + ctx->watch.k.dir_fd = open(path, O_RDONLY); - setup_file_watches(ctx); + d = opendir(path); + if (!d) + goto bsd_setup; - return (void *)ctx; + bsd_scan_loop: + e = readdir(d); + if (!e) + goto bsd_scan_done; + + sprintf(f, "%s/%s", path, e->d_name); + if (!is_valid_module_file(e->d_name)) + goto bsd_scan_loop; + if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) + goto bsd_scan_loop; + + tmp_snaps = realloc( + ctx->snaps, (ctx->count + 1) * sizeof(platform_snapshot_t)); + if (!tmp_snaps) + goto bsd_scan_loop; + + ctx->snaps = tmp_snaps; + strncpy(ctx->snaps[ctx->count].filename, e->d_name, + STK_PATH_MAX - 1); + ctx->snaps[ctx->count++].mtime = st.st_mtime; + + goto bsd_scan_loop; + + bsd_scan_done: + closedir(d); + + bsd_setup: + update_watches(ctx); + } +#endif + +#ifdef _WIN32 +done: +#endif + return ctx; #endif } void platform_directory_watch_stop(void *handle) { -#ifdef __linux__ +#if defined(__linux__) if (handle) close((int)(long)handle); -#elif defined(_WIN32) - if (handle) - CloseHandle((HANDLE)handle); #else - kqueue_watch_context_t *ctx; - ctx = (kqueue_watch_context_t *)handle; +#ifndef _WIN32 + size_t i; +#endif + platform_watch_context_t *ctx = (platform_watch_context_t *)handle; if (!ctx) return; - cleanup_file_watches(ctx); - if (ctx->dir_fd >= 0) - close(ctx->dir_fd); - if (ctx->kq >= 0) - close(ctx->kq); - free(ctx->snapshots); + +#ifdef _WIN32 + FindCloseChangeNotification(ctx->watch.change_handle); +#else + for (i = 0; i < ctx->watch.k.file_fd_count; i++) { + close(ctx->watch.k.file_fds[i]); + } + free(ctx->watch.k.file_fds); + close(ctx->watch.k.kq); + close(ctx->watch.k.dir_fd); +#endif + free(ctx->snaps); free(ctx); #endif } stk_module_event_t *platform_directory_watch_check( void *handle, char (**file_list)[STK_PATH_MAX], size_t *out_count, - char (*loaded_module_ids)[STK_MOD_ID_BUFFER], const size_t loaded_count) + char (*loaded_ids)[STK_MOD_ID_BUFFER], const size_t loaded_count) { - size_t index = 0; - stk_module_event_t *events = NULL; +#if defined(__linux__) + int fd = (int)(long)handle; + char buf[STK_EVENT_BUFFER]; + ssize_t len; + size_t idx = 0; + stk_module_event_t *evs; + char *ptr; + struct inotify_event *e; + int event_type; -#ifdef __linux__ - char buffer[STK_EVENT_BUFFER]; - ssize_t bytes_read; - struct inotify_event *event; - char *event_ptr; - int fd; - size_t file_count; - - fd = (int)(long)handle; - file_count = 0; - - bytes_read = read(fd, buffer, sizeof(buffer)); - if (bytes_read <= 0) { + len = read(fd, buf, sizeof(buf)); + if (len <= 0) { *out_count = 0; return NULL; } - event_ptr = buffer; - while (event_ptr < buffer + bytes_read) { - event = (struct inotify_event *)event_ptr; - if (event->len > 0 && is_valid_module_file(event->name)) - file_count++; - event_ptr += sizeof(struct inotify_event) + event->len; - } + evs = malloc(8 * sizeof(stk_module_event_t)); + *file_list = malloc(8 * sizeof(**file_list)); + ptr = buf; - if (file_count == 0) { - *out_count = 0; - return NULL; - } +process_inotify_loop: + if (ptr >= buf + len) + goto linux_done; - events = malloc(file_count * sizeof(stk_module_event_t)); - *file_list = malloc(file_count * sizeof(**file_list)); + e = (struct inotify_event *)ptr; + if (!e->len || !is_valid_module_file(e->name)) + goto next_event; - index = 0; - event_ptr = buffer; - while (event_ptr < buffer + bytes_read) { - event = (struct inotify_event *)event_ptr; - if (event->len > 0 && is_valid_module_file(event->name)) { - events[index] = - (event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) - ? (is_module_loaded(event->name, - loaded_module_ids, - loaded_count) >= 0 - ? STK_MOD_RELOAD - : STK_MOD_LOAD) - : STK_MOD_UNLOAD; - strncpy((*file_list)[index], event->name, - STK_PATH_MAX - 1); - (*file_list)[index][STK_PATH_MAX - 1] = '\0'; - index++; - } - event_ptr += sizeof(struct inotify_event) + event->len; + strncpy((*file_list)[idx], e->name, STK_PATH_MAX - 1); + + event_type = STK_MOD_UNLOAD; + if (e->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) { + if (is_module_loaded(e->name, loaded_ids, loaded_count) >= 0) + event_type = STK_MOD_RELOAD; + else + event_type = STK_MOD_LOAD; } -#elif defined(_WIN32) + evs[idx++] = event_type; + +next_event: + ptr += sizeof(struct inotify_event) + e->len; + goto process_inotify_loop; + +linux_done: + *out_count = idx; + return evs; + +#else + platform_watch_context_t *ctx = (platform_watch_context_t *)handle; + platform_snapshot_t *new_snaps = NULL; + size_t new_count = 0, i, j, ev_idx = 0; + stk_module_event_t *evs = NULL; + void *tmp_ptr; + int found; + +#ifdef _WIN32 + WIN32_FIND_DATAA fd; HANDLE h; - BYTE buffer[STK_EVENT_BUFFER]; - DWORD bytes_returned; - FILE_NOTIFY_INFORMATION *info; - BYTE *event_ptr; - size_t file_count; - char temp_filename[STK_PATH_MAX]; - int len; - BOOL result; + char s[STK_PATH_MAX_OS]; - h = (HANDLE)handle; - file_count = 0; + if (WaitForSingleObject(ctx->watch.change_handle, 0) != WAIT_OBJECT_0) + goto no_change; - result = ReadDirectoryChangesW(h, buffer, sizeof(buffer), FALSE, - FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_LAST_WRITE, - &bytes_returned, NULL, NULL); - if (!result || bytes_returned == 0) { - *out_count = 0; - return NULL; + FindNextChangeNotification(ctx->watch.change_handle); + + sprintf(s, "%s\\*", ctx->path); + h = FindFirstFileA(s, &fd); + if (h == INVALID_HANDLE_VALUE) + goto build_diff; + +win_snap_loop: + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + is_valid_module_file(fd.cFileName)) { + tmp_ptr = realloc(new_snaps, (new_count + 1) * + sizeof(platform_snapshot_t)); + if (!tmp_ptr) + goto win_next; + new_snaps = tmp_ptr; + strncpy(new_snaps[new_count].filename, fd.cFileName, + STK_PATH_MAX - 1); + new_snaps[new_count++].mtime = fd.ftLastWriteTime; } +win_next: + if (FindNextFileA(h, &fd)) + goto win_snap_loop; + FindClose(h); + goto build_diff; - event_ptr = buffer; - while (1) { - info = (FILE_NOTIFY_INFORMATION *)event_ptr; - len = WideCharToMultiByte(CP_UTF8, 0, info->FileName, - info->FileNameLength / sizeof(WCHAR), - temp_filename, STK_PATH_MAX - 1, NULL, - NULL); - if (len > 0) { - temp_filename[len] = '\0'; - if (is_valid_module_file(temp_filename)) - file_count++; - } - if (info->NextEntryOffset == 0) +#else + struct kevent kev; + struct timespec ts = {0, 0}; + DIR *d; + struct dirent *e; + struct stat st; + char f[STK_PATH_MAX_OS]; + + if (kevent(ctx->watch.k.kq, NULL, 0, &kev, 1, &ts) <= 0) + goto no_change; + + d = opendir(ctx->path); + if (!d) + goto bsd_update; + +bsd_snap_loop: + e = readdir(d); + if (!e) + goto bsd_snap_done; + + if (!is_valid_module_file(e->d_name)) + goto bsd_snap_loop; + + sprintf(f, "%s/%s", ctx->path, e->d_name); + if (stat(f, &st) != 0 || !S_ISREG(st.st_mode)) + goto bsd_snap_loop; + + tmp_ptr = + realloc(new_snaps, (new_count + 1) * sizeof(platform_snapshot_t)); + if (!tmp_ptr) + goto bsd_snap_loop; + + new_snaps = tmp_ptr; + strncpy(new_snaps[new_count].filename, e->d_name, STK_PATH_MAX - 1); + new_snaps[new_count++].mtime = st.st_mtime; + + goto bsd_snap_loop; + +bsd_snap_done: + closedir(d); + +bsd_update: + update_watches(ctx); + goto build_diff; +#endif + +build_diff: + evs = malloc((ctx->count + new_count + 1) * sizeof(stk_module_event_t)); + *file_list = malloc((ctx->count + new_count + 1) * sizeof(**file_list)); + if (!evs || !*file_list) + goto cleanup_error; + + for (i = 0; i < ctx->count; i++) { + found = 0; + for (j = 0; j < new_count; j++) { + if (strcmp(ctx->snaps[i].filename, + new_snaps[j].filename) != 0) + continue; + + found = 1; +#ifdef _WIN32 + if (CompareFileTime(&ctx->snaps[i].mtime, + &new_snaps[j].mtime) != 0) { +#else + if (ctx->snaps[i].mtime != new_snaps[j].mtime) { +#endif + strncpy((*file_list)[ev_idx], + new_snaps[j].filename, + STK_PATH_MAX - 1); + evs[ev_idx++] = STK_MOD_RELOAD; + } break; - event_ptr += info->NextEntryOffset; + } + if (!found) { + strncpy((*file_list)[ev_idx], ctx->snaps[i].filename, + STK_PATH_MAX - 1); + evs[ev_idx++] = STK_MOD_UNLOAD; + } } - if (file_count == 0) { - *out_count = 0; - return NULL; - } - - events = malloc(file_count * sizeof(stk_module_event_t)); - *file_list = malloc(file_count * sizeof(**file_list)); - - index = 0; - event_ptr = buffer; - while (1) { - info = (FILE_NOTIFY_INFORMATION *)event_ptr; - len = WideCharToMultiByte(CP_UTF8, 0, info->FileName, - info->FileNameLength / sizeof(WCHAR), - (*file_list)[index], STK_PATH_MAX - 1, - NULL, NULL); - if (len > 0) { - (*file_list)[index][len] = '\0'; - if (is_valid_module_file((*file_list)[index])) { - events[index] = - (info->Action != FILE_ACTION_REMOVED && - info->Action != - FILE_ACTION_RENAMED_OLD_NAME) - ? (is_module_loaded((*file_list)[index], - loaded_module_ids, - loaded_count) >= 0 - ? STK_MOD_RELOAD - : STK_MOD_LOAD) - : STK_MOD_UNLOAD; - index++; + for (j = 0; j < new_count; j++) { + found = 0; + for (i = 0; i < ctx->count; i++) { + if (strcmp(new_snaps[j].filename, + ctx->snaps[i].filename) == 0) { + found = 1; + break; } } - if (info->NextEntryOffset == 0) - break; - event_ptr += info->NextEntryOffset; - } -#else - kqueue_watch_context_t *ctx; - struct kevent events_buf[64]; - struct timespec timeout; - file_snapshot_t *new_snapshots, *old_snap; - size_t new_count, new_capacity, i, change_count; - int nevents; - - ctx = (kqueue_watch_context_t *)handle; - timeout.tv_sec = 0; - timeout.tv_nsec = 0; - change_count = 0; - - nevents = kevent(ctx->kq, NULL, 0, events_buf, 64, &timeout); - if (nevents <= 0) { - *out_count = 0; - return NULL; - } - - new_snapshots = create_snapshot(ctx->path, &new_count, &new_capacity); - - for (i = 0; i < ctx->snapshot_count; i++) - if (!find_in_snapshot(new_snapshots, new_count, - ctx->snapshots[i].filename)) - change_count++; - - for (i = 0; i < new_count; i++) { - old_snap = find_in_snapshot(ctx->snapshots, ctx->snapshot_count, - new_snapshots[i].filename); - if (!old_snap || old_snap->mtime != new_snapshots[i].mtime) - change_count++; - } - - if (change_count == 0) { - free(new_snapshots); - *out_count = 0; - return NULL; - } - - events = malloc(change_count * sizeof(stk_module_event_t)); - *file_list = malloc(change_count * sizeof(**file_list)); - - index = 0; - - for (i = 0; i < ctx->snapshot_count && index < change_count; i++) { - if (!find_in_snapshot(new_snapshots, new_count, - ctx->snapshots[i].filename)) { - events[index] = STK_MOD_UNLOAD; - strncpy((*file_list)[index], ctx->snapshots[i].filename, + if (!found) { + strncpy((*file_list)[ev_idx], new_snaps[j].filename, STK_PATH_MAX - 1); - (*file_list)[index][STK_PATH_MAX - 1] = '\0'; - index++; + evs[ev_idx++] = STK_MOD_LOAD; } } - for (i = 0; i < new_count && index < change_count; i++) { - old_snap = find_in_snapshot(ctx->snapshots, ctx->snapshot_count, - new_snapshots[i].filename); - if (!old_snap || old_snap->mtime != new_snapshots[i].mtime) { - events[index] = - is_module_loaded(new_snapshots[i].filename, - loaded_module_ids, - loaded_count) >= 0 - ? STK_MOD_RELOAD - : STK_MOD_LOAD; - strncpy((*file_list)[index], new_snapshots[i].filename, - STK_PATH_MAX - 1); - (*file_list)[index][STK_PATH_MAX - 1] = '\0'; - index++; - } - } + if (ev_idx == 0) + goto cleanup_empty; - free(ctx->snapshots); - ctx->snapshots = new_snapshots; - ctx->snapshot_count = new_count; - ctx->snapshot_capacity = new_capacity; + free(ctx->snaps); + ctx->snaps = new_snaps; + ctx->count = new_count; + *out_count = ev_idx; + return evs; - refresh_file_watches(ctx); +cleanup_error: + if (evs) + free(evs); + if (*file_list) + free(*file_list); + +cleanup_empty: + free(new_snaps); + +no_change: + *out_count = 0; + return NULL; #endif - *out_count = index; - return events; } - -char (*platform_directory_init_scan(const char *mod_dir, size_t *out_count)) - [STK_PATH_MAX] { - char (*file_list)[STK_PATH_MAX] = NULL; - size_t count = 0, index = 0; - -#if defined(_WIN32) - WIN32_FIND_DATAW find_data; - HANDLE find_handle; - WCHAR search_path[STK_PATH_MAX_OS]; - char temp_filename[STK_PATH_MAX]; - int len; - - swprintf(search_path, STK_PATH_MAX_OS, L"%S\\*", mod_dir); - find_handle = FindFirstFileW(search_path, &find_data); - - if (find_handle == INVALID_HANDLE_VALUE) { - CreateDirectoryA(mod_dir, NULL); - *out_count = 0; - return NULL; - } - - do { - if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - continue; - - len = WideCharToMultiByte(CP_UTF8, 0, find_data.cFileName, - -1, temp_filename, - STK_PATH_MAX - 1, NULL, NULL); - if (len > 0) { - temp_filename[len] = '\0'; - if (is_valid_module_file(temp_filename)) - count++; - } - } while (FindNextFileW(find_handle, &find_data)); - FindClose(find_handle); - - if (count == 0) { - *out_count = 0; - return NULL; - } - - find_handle = FindFirstFileW(search_path, &find_data); - file_list = malloc(count * sizeof(*file_list)); - - do { - if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - continue; - - len = WideCharToMultiByte(CP_UTF8, 0, find_data.cFileName, - -1, file_list[index], - STK_PATH_MAX - 1, NULL, NULL); - if (len > 0 && index < count) { - file_list[index][len] = '\0'; - if (is_valid_module_file(file_list[index])) - index++; - } - } while (FindNextFileW(find_handle, &find_data) && index < count); - FindClose(find_handle); -#else - char work_path[STK_PATH_MAX_OS]; - DIR *dir; - struct dirent *entry; - struct stat file_stat; - - dir = opendir(mod_dir); - if (!dir) { - mkdir(mod_dir, 0755); - *out_count = 0; - return NULL; - } - - while ((entry = readdir(dir)) != NULL) { - if (!is_valid_module_file(entry->d_name)) - continue; - - sprintf(work_path, "%s/%s", mod_dir, entry->d_name); - if (stat(work_path, &file_stat) == 0 && - S_ISREG(file_stat.st_mode)) - count++; - } - - if (count == 0) { - closedir(dir); - *out_count = 0; - return NULL; - } - - rewinddir(dir); - file_list = malloc(count * sizeof(*file_list)); - if (!file_list) { - closedir(dir); - *out_count = 0; - return NULL; - } - - while ((entry = readdir(dir)) != NULL && index < count) { - if (!is_valid_module_file(entry->d_name)) - continue; - - sprintf(work_path, "%s/%s", mod_dir, entry->d_name); - if (stat(work_path, &file_stat) != 0 || - !S_ISREG(file_stat.st_mode)) - continue; - - strncpy(file_list[index], entry->d_name, STK_PATH_MAX - 1); - file_list[index][STK_PATH_MAX - 1] = '\0'; - index++; - } - closedir(dir); -#endif - *out_count = index; - return file_list; - }