Added kqueue directory behavior and huge refactor

- Added kqueue directory watching code for other unix like OSes
  (FreeBSD, OpenBSD, MacOS, etc)
- Since much of the code for Linux and other *nix OSes was the same,
  some refactoring was done to not have duplicate code.
This commit is contained in:
2026-01-11 15:31:14 +01:00
parent 503bd21fae
commit 888e6d5f72
+310 -83
View File
@@ -11,10 +11,180 @@
#include <unistd.h>
#elif defined(_WIN32)
#include <windows.h>
#else
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define EVENT_BUFFER_SIZE 4096
#if !defined(_WIN32)
static char **scan_directory(const char *path, size_t *out_count)
{
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char full_path[PATH_BUFFER_SIZE];
char **file_list = NULL;
size_t count = 0, index = 0;
dir = opendir(path);
if (!dir) {
mkdir(path, 0755);
*out_count = 0;
return NULL;
}
while ((entry = readdir(dir)) != NULL) {
sprintf(full_path, "%s/%s", path, entry->d_name);
if (stat(full_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 = (char **)malloc(count * sizeof(char *));
if (!file_list) {
closedir(dir);
*out_count = 0;
return NULL;
}
while ((entry = readdir(dir)) != NULL && index < count) {
sprintf(full_path, "%s/%s", path, entry->d_name);
if (stat(full_path, &file_stat) != 0 ||
!S_ISREG(file_stat.st_mode))
continue;
file_list[index] = (char *)malloc(strlen(entry->d_name) + 1);
if (file_list[index]) {
strcpy(file_list[index], entry->d_name);
index++;
}
}
closedir(dir);
*out_count = index;
return file_list;
}
#endif
#if !defined(__linux__) && !defined(_WIN32)
typedef struct {
char *filename;
time_t mtime;
} file_snapshot_t;
typedef struct {
int kq;
int dir_fd;
char path[PATH_BUFFER_SIZE];
file_snapshot_t *snapshots;
size_t snapshot_count;
} kqueue_watch_context_t;
static file_snapshot_t *create_snapshot(const char *path, size_t *out_count)
{
DIR *dir;
struct dirent *entry;
struct stat file_stat;
char full_path[PATH_BUFFER_SIZE];
file_snapshot_t *snapshots = NULL;
size_t count = 0, index = 0;
dir = opendir(path);
if (!dir) {
*out_count = 0;
return NULL;
}
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0)
continue;
snprintf(full_path, sizeof(full_path), "%s/%s", path,
entry->d_name);
if (stat(full_path, &file_stat) == 0 &&
S_ISREG(file_stat.st_mode))
count++;
}
if (count == 0) {
closedir(dir);
*out_count = 0;
return NULL;
}
snapshots = malloc(count * sizeof(file_snapshot_t));
if (!snapshots) {
closedir(dir);
*out_count = 0;
return NULL;
}
rewinddir(dir);
while ((entry = readdir(dir)) != NULL && index < count) {
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0)
continue;
snprintf(full_path, sizeof(full_path), "%s/%s", path,
entry->d_name);
if (stat(full_path, &file_stat) != 0 ||
!S_ISREG(file_stat.st_mode))
continue;
snapshots[index].filename = malloc(strlen(entry->d_name) + 1);
if (snapshots[index].filename) {
strcpy(snapshots[index].filename, entry->d_name);
snapshots[index].mtime = file_stat.st_mtime;
index++;
}
}
closedir(dir);
*out_count = index;
return snapshots;
}
static void free_snapshot(file_snapshot_t *snapshots, size_t count)
{
size_t i;
if (!snapshots)
return;
for (i = 0; i < count; i++) {
free(snapshots[i].filename);
}
free(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;
}
#endif
void *platform_load_library(const char *path)
{
#ifdef _WIN32
@@ -45,8 +215,7 @@ void *platform_get_symbol(void *handle, const char *symbol)
void *platform_directory_watch_start(const char *path)
{
#ifdef __linux__
int fd;
int wd;
int fd, wd;
fd = inotify_init1(IN_NONBLOCK);
if (fd < 0)
@@ -61,9 +230,9 @@ void *platform_directory_watch_start(const char *path)
}
return (void *)(long)fd;
#elif defined(_WIN32)
HANDLE handle;
handle =
HANDLE handle =
CreateFileA(path, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
@@ -72,26 +241,60 @@ void *platform_directory_watch_start(const char *path)
return NULL;
return (void *)handle;
#else
struct kevent event;
kqueue_watch_context_t *ctx = malloc(sizeof(kqueue_watch_context_t));
if (!ctx)
return NULL;
ctx->kq = kqueue();
ctx->dir_fd = open(path, O_RDONLY);
strncpy(ctx->path, path, PATH_BUFFER_SIZE - 1);
ctx->path[PATH_BUFFER_SIZE - 1] = '\0';
ctx->snapshots = create_snapshot(path, &ctx->snapshot_count);
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_snapshot(ctx->snapshots, ctx->snapshot_count);
free(ctx);
return NULL;
}
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);
return (void *)ctx;
#endif
}
void platform_directory_watch_stop(void *handle)
{
#ifdef __linux__
int fd;
if (!handle)
return;
close((int)(long)handle);
fd = (int)(long)handle;
close(fd);
#elif defined(_WIN32)
HANDLE h;
h = (HANDLE)handle;
CloseHandle(h);
CloseHandle((HANDLE)handle);
#else
(void)handle;
kqueue_watch_context_t *ctx = (kqueue_watch_context_t *)handle;
if (!ctx)
return;
if (ctx->dir_fd >= 0)
close(ctx->dir_fd);
if (ctx->kq >= 0)
close(ctx->kq);
free_snapshot(ctx->snapshots, ctx->snapshot_count);
free(ctx);
#endif
}
@@ -99,29 +302,30 @@ stk_module_event_t *platform_directory_watch_check(void *handle,
char ***file_list,
size_t *out_count)
{
size_t file_count = 0, index = 0;
#if defined(__linux__) || defined(_WIN32)
size_t file_count = 0;
#endif
size_t index = 0;
stk_module_event_t *events = NULL;
#ifdef __linux__
int fd;
char buffer[EVENT_BUFFER_SIZE];
ssize_t bytes_read;
struct inotify_event *event;
char *event_ptr;
int fd = (int)(long)handle;
fd = (int)(long)handle;
bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read <= 0) {
*out_count = 0;
return NULL;
}
file_count = 0;
event_ptr = buffer;
while (event_ptr < buffer + bytes_read) {
event = (struct inotify_event *)event_ptr;
if (event->len > 0)
++file_count;
file_count++;
event_ptr += sizeof(struct inotify_event) + event->len;
}
@@ -154,21 +358,17 @@ stk_module_event_t *platform_directory_watch_check(void *handle,
index++;
}
}
event_ptr += sizeof(struct inotify_event) + event->len;
}
#elif defined(_WIN32)
HANDLE h;
HANDLE h = (HANDLE)handle;
BYTE buffer[EVENT_BUFFER_SIZE];
DWORD bytes_returned;
FILE_NOTIFY_INFORMATION *info;
BYTE *event_ptr;
int char_count;
BOOL result;
h = (HANDLE)handle;
result = ReadDirectoryChangesW(h, buffer, sizeof(buffer), FALSE,
BOOL result = ReadDirectoryChangesW(h, buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
&bytes_returned, NULL, NULL);
@@ -177,15 +377,12 @@ stk_module_event_t *platform_directory_watch_check(void *handle,
return NULL;
}
file_count = 0;
event_ptr = buffer;
while (1) {
info = (FILE_NOTIFY_INFORMATION *)event_ptr;
file_count++;
if (info->NextEntryOffset == 0)
break;
event_ptr += info->NextEntryOffset;
}
@@ -231,9 +428,89 @@ stk_module_event_t *platform_directory_watch_check(void *handle,
if (info->NextEntryOffset == 0)
break;
event_ptr += info->NextEntryOffset;
}
#else
kqueue_watch_context_t *ctx = (kqueue_watch_context_t *)handle;
struct kevent event;
struct timespec timeout = {0, 0};
int nevents;
file_snapshot_t *new_snapshots, *old_snap, *new_snap;
size_t new_count, i;
size_t change_count = 0;
nevents = kevent(ctx->kq, NULL, 0, &event, 1, &timeout);
if (nevents <= 0) {
*out_count = 0;
return NULL;
}
new_snapshots = create_snapshot(ctx->path, &new_count);
for (i = 0; i < ctx->snapshot_count; i++) {
new_snap = find_in_snapshot(new_snapshots, new_count,
ctx->snapshots[i].filename);
if (!new_snap)
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_snapshot(new_snapshots, new_count);
*out_count = 0;
return NULL;
}
events = malloc(change_count * sizeof(stk_module_event_t));
*file_list = malloc(change_count * sizeof(char *));
if (!events || !*file_list) {
free(events);
free(*file_list);
free_snapshot(new_snapshots, new_count);
*out_count = 0;
return NULL;
}
index = 0;
for (i = 0; i < ctx->snapshot_count && index < change_count; i++) {
new_snap = find_in_snapshot(new_snapshots, new_count,
ctx->snapshots[i].filename);
if (!new_snap) {
events[index] = STK_MOD_UNLOAD;
(*file_list)[index] =
malloc(strlen(ctx->snapshots[i].filename) + 1);
if ((*file_list)[index]) {
strcpy((*file_list)[index],
ctx->snapshots[i].filename);
index++;
}
}
}
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] = STK_MOD_LOAD;
(*file_list)[index] =
malloc(strlen(new_snapshots[i].filename) + 1);
if ((*file_list)[index]) {
strcpy((*file_list)[index],
new_snapshots[i].filename);
index++;
}
}
}
free_snapshot(ctx->snapshots, ctx->snapshot_count);
ctx->snapshots = new_snapshots;
ctx->snapshot_count = new_count;
#endif
*out_count = index;
return events;
@@ -241,62 +518,12 @@ stk_module_event_t *platform_directory_watch_check(void *handle,
char **platform_directory_init_scan(const char *path, size_t *out_count)
{
char **file_list = NULL;
size_t count = 0, index = 0;
char full_path[PATH_BUFFER_SIZE];
#ifdef __linux__
DIR *dir;
struct dirent *entry;
struct stat file_stat;
dir = opendir(path);
if (!dir) {
mkdir(path, 0755);
*out_count = 0;
return NULL;
}
while ((entry = readdir(dir)) != NULL) {
sprintf(full_path, "%s/%s", path, entry->d_name);
if (stat(full_path, &file_stat) == 0 &&
S_ISREG(file_stat.st_mode))
++count;
}
closedir(dir);
dir = opendir(path);
if (!dir) {
*out_count = 0;
return NULL;
}
file_list = (char **)malloc(count * sizeof(char *));
if (!file_list) {
closedir(dir);
*out_count = 0;
return NULL;
}
while ((entry = readdir(dir)) != NULL && index < count) {
sprintf(full_path, "%s/%s", path, entry->d_name);
if (stat(full_path, &file_stat) != 0)
continue;
if (!S_ISREG(file_stat.st_mode))
continue;
file_list[index] = (char *)malloc(strlen(entry->d_name) + 1);
if (!file_list[index])
continue;
strcpy(file_list[index], entry->d_name);
++index;
}
closedir(dir);
#elif defined(_WIN32)
#if defined(_WIN32)
WIN32_FIND_DATAW find_data;
HANDLE find_handle;
WCHAR search_path[PATH_BUFFER_SIZE];
char **file_list = NULL;
size_t count = 0, index = 0;
int utf8_len;
swprintf(search_path, PATH_BUFFER_SIZE, L"%S\\*", path);
@@ -353,9 +580,9 @@ char **platform_directory_init_scan(const char *path, size_t *out_count)
} while (FindNextFileW(find_handle, &find_data) && index < count);
FindClose(find_handle);
#else
#endif
*out_count = index;
return file_list;
#else
return scan_directory(path, out_count);
#endif
}