Files
hiload/src/filewatcher/filewatcher.c

337 lines
8.5 KiB
C

#include "filewatcher.h"
#include <errno.h>
#include <stdatomic.h>
#include <stddef.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include "array/array.h"
#include "array/sc_array.h"
#include "common.h"
#include "filewatch.h"
#include "filewatch_context.h"
#include "filewatch_type.h"
#include "logger/logger.h"
#include "types.h"
sc_array_def(hiFileEvent, fwevent);
typedef struct hiFileWatcher {
thrd_t thread;
mtx_t mutex;
cnd_t cond;
int running;
struct hiFileWatcherContext context;
struct sc_array_fwevent events;
} hiFileWatcher;
/*
* Watch Params
*/
static inline hiWatchParams watch_params_create(const char *path, u32 mask) {
return (hiWatchParams){.path = strdup(path), .mask = mask};
}
static inline void watch_params_destroy(hiWatchParams *params) {
free((char *)params->path);
}
static hiWatchParams *watch_params_find_by_path(const char *path,
hiFileWatcherContext *ctx,
size_t *index) {
const struct sc_array_fwparam *params = &ctx->params;
for (size_t i = 0; i < sc_array_size(params); ++i) {
hiWatchParams *param = &sc_array_at(params, i);
if (strcmp(path, param->path) == 0) {
if (index)
*index = i;
return param;
}
}
return NULL;
}
/*
* File Events
*/
#define NULL_EVENT (hiFileEvent){0};
static hiFileEvent file_event_create(const struct inotify_event *inevent,
hiFileWatcherContext *ctx) {
hiFileEvent event = {0};
if (!inevent) {
return event;
}
const hiFileWatch *fw = file_watch_find_by_wd(ctx, inevent->wd, NULL);
if (has_mask(inevent->mask, IN_IGNORED)) {
sc_log_debug("IN_IGNORED received on %s\n", fw->path);
return NULL_EVENT;
}
if (inevent->len) {
#define BUFSIZE 1024
char filename[BUFSIZE];
size_t parent_name_size = strlen(fw->path);
size_t event_path_size = strlen(inevent->name);
size_t fullpath_size = parent_name_size + event_path_size;
if (fullpath_size > BUFSIZE - 1) {
sc_log_error("Pathname length over %u, please compile with smaller "
"paths. Event discarded for %s\n.",
BUFSIZE - 1, inevent->name);
return NULL_EVENT;
}
#undef BUFSIZE
strncpy(filename, fw->path, parent_name_size);
strncpy(filename + parent_name_size, inevent->name, event_path_size);
filename[fullpath_size] = '\0';
// find if file is associated with any path
for (size_t i = 0; i < sc_array_size(&fw->files); ++i) {
const char *watched_file = sc_array_at(&fw->files, i);
if (strcmp(watched_file, filename) == 0) {
// check if event mask matches the watcher mask
hiWatchParams *params =
watch_params_find_by_path(watched_file, ctx, NULL);
if (!params) {
return NULL_EVENT;
}
if (has_mask(inevent->mask, IN_MODIFY)) {
event.type = HI_FILE_MODIFY;
} else if (has_mask(inevent->mask, IN_CREATE)) {
event.type = HI_FILE_CREATE;
} else if (has_mask(inevent->mask, IN_DELETE)) {
event.type = HI_FILE_DELETE;
} else if (has_mask(inevent->mask, IN_DELETE_SELF)) {
event.type = HI_FILE_DELETE;
} else if (has_mask(inevent->mask, IN_MOVE)) {
event.type = HI_FILE_MOVE;
}
// We aren't watching for this event on the file, discard
if (!has_mask(params->mask, event.type)) {
return NULL_EVENT;
}
// Use params path pointer, because that's what user has control over.
event.pathname = params->path;
// Event matched and we're watching for it
return event;
}
}
// Something happened with a file we're not watching
return NULL_EVENT;
}
// Event on the folder itself
return NULL_EVENT;
}
hiFileEvent hi_file_event_pop(hiFileWatcher *fw) {
mtx_lock(&fw->mutex);
hiFileEvent e = NULL_EVENT;
if (sc_array_size(&fw->events) > 0) {
e = sc_array_last(&fw->events);
sc_array_del_last(&fw->events);
}
mtx_unlock(&fw->mutex);
return e;
}
/*
* File Watcher
*/
static HiloadResult file_watcher_ctx_init(hiFileWatcherContext *ctx) {
ctx->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
if (ctx->fd == -1) {
return HILOAD_FAIL;
}
sc_array_init(&ctx->watches);
sc_array_init(&ctx->params);
return HILOAD_OK;
}
static void file_watcher_ctx_destroy(hiFileWatcherContext *ctx) {
if (ctx) {
file_watch_destroy_watches(ctx);
}
}
// Declare the thread func
int file_watcher_watch(void *arg);
hiFileWatcher *hi_file_watcher_create() {
// Allocate context
hiFileWatcher *fw = malloc(sizeof(hiFileWatcher));
memset(fw, 0, sizeof(hiFileWatcher));
if (fw) {
if (!HIOK(file_watcher_ctx_init(&fw->context))) {
sc_log_error("Failed to initialize watch paths\n");
if (fw->context.fd == -1) {
sc_log_error("Couldn't initialize inotify\n");
}
free(fw);
return NULL;
}
sc_array_init(&fw->events);
mtx_init(&fw->mutex, mtx_plain);
cnd_init(&fw->cond);
fw->running = true;
thrd_create(&fw->thread, file_watcher_watch, fw);
return fw;
}
free(fw);
return NULL;
}
HiloadResult hi_file_watcher_add(struct hiFileWatcher *fw, const char *filename,
u32 flags) {
if (!fw) {
sc_log_error("Attempted to add file watcher for '%s' with null context\n",
filename);
return HILOAD_FAIL;
}
mtx_lock(&fw->mutex);
if (!HIOK(file_watch_add(&fw->context, flags, filename))) {
mtx_unlock(&fw->mutex);
return HILOAD_FAIL;
}
{
// Add watch param as well
hiWatchParams *params =
watch_params_find_by_path(filename, &fw->context, NULL);
if (!params) {
hiWatchParams params = watch_params_create(filename, flags);
sc_array_add(&fw->context.params, params);
} else {
params->mask |= flags;
}
}
mtx_unlock(&fw->mutex);
return HILOAD_OK;
}
HiloadResult hi_file_watcher_remove(struct hiFileWatcher *fw,
const char *filename) {
if (!fw) {
return HILOAD_FAIL;
}
mtx_lock(&fw->mutex);
if (!HIOK(file_watch_remove(&fw->context, filename))) {
mtx_unlock(&fw->mutex);
return HILOAD_FAIL;
}
{
// remove watchparam as well
size_t i = 0;
hiWatchParams *params =
watch_params_find_by_path(filename, &fw->context, &i);
watch_params_destroy(params);
sc_array_del(&fw->context.params, i);
}
mtx_unlock(&fw->mutex);
sc_log_info("Removed file: '%s' from watch, filename");
return HILOAD_OK;
}
void hi_file_watcher_notify(hiFileWatcher *fw) {
if (fw && fw->running) {
cnd_signal(&fw->cond);
}
}
void hi_file_watcher_destroy(hiFileWatcher *fw) {
if (!fw)
return;
if (fw->running) {
fw->running = false;
hi_file_watcher_notify(fw);
thrd_join(fw->thread, NULL);
}
file_watcher_ctx_destroy(&fw->context);
free(fw);
}
int file_watcher_watch(void *arg) {
hiFileWatcher *ctx = (hiFileWatcher *)arg;
sc_log_set_thread_name("File Watcher");
char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
const struct inotify_event *event = NULL;
mtx_lock(&ctx->mutex);
while (ctx->running) {
ssize_t n;
n = read(ctx->context.fd, buf, sizeof(buf));
if (n == -1 && errno != EAGAIN) {
sc_log_error("Failed to read inotify: %s. Exiting thread.\n",
strerror(errno));
return 1;
}
if (n > 0) {
for (char *ptr = buf; ptr < buf + n;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *)ptr;
hiFileEvent e = file_event_create(event, &ctx->context);
if (e.type != HI_FILE_NONE) {
sc_array_add(&ctx->events, e);
if (event->len) {
sc_log_debug("Event created: queue-size: %u, %s %s\n",
sc_array_size(&ctx->events), e.pathname,
hi_file_watch_type_to_str(e.type));
}
}
}
continue; // read again without waiting
}
// wait for 100ms
#define SEC_IN_NS 1000000000
struct timespec timeout;
i64 wait_time_ns = 1000 * 1000 * 100;
timespec_get(&timeout, TIME_UTC);
timeout.tv_nsec += wait_time_ns;
if (timeout.tv_nsec > SEC_IN_NS) {
timeout.tv_sec += timeout.tv_nsec / SEC_IN_NS;
timeout.tv_nsec = timeout.tv_nsec % SEC_IN_NS;
}
#undef SEC_IN_NS
cnd_timedwait(&ctx->cond, &ctx->mutex, &timeout);
}
mtx_unlock(&ctx->mutex);
return 0;
}