336 lines
8.3 KiB
C
336 lines
8.3 KiB
C
#include "filewatcher.h"
|
|
|
|
#include "filewatch.h"
|
|
#include "filewatch_context.h"
|
|
#include "filewatch_type.h"
|
|
|
|
#include "common.h"
|
|
#include "logger.h"
|
|
#include "types.h"
|
|
#include "vector.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>
|
|
|
|
vector_def(FileEvent, FileEvent);
|
|
|
|
/**
|
|
* All context and state required to run a file watcher.
|
|
*/
|
|
typedef struct FileWatcher {
|
|
thrd_t thread;
|
|
mtx_t mutex;
|
|
cnd_t cond;
|
|
int running;
|
|
FileWatcherContext context;
|
|
VectorFileEvent events;
|
|
} FileWatcher;
|
|
|
|
/*
|
|
* Watch Params
|
|
*/
|
|
static inline WatchParams watch_params_create(const char *path, u32 mask) {
|
|
return (WatchParams){.path = strdup(path), .mask = mask};
|
|
}
|
|
|
|
static inline void watch_params_destroy(WatchParams *params) {
|
|
free((char *)params->path);
|
|
}
|
|
|
|
static WatchParams *watch_params_find_by_path(const char *path,
|
|
FileWatcherContext *ctx,
|
|
size_t *index) {
|
|
const VectorWatchParams *params = &ctx->params;
|
|
for (size_t i = 0; i < vector_size(params); ++i) {
|
|
WatchParams *param = &vector_at(params, i);
|
|
if (strcmp(path, param->path) == 0) {
|
|
if (index)
|
|
*index = i;
|
|
return param;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* File Events
|
|
*/
|
|
|
|
#define NULL_EVENT (FileEvent){0};
|
|
|
|
static FileEvent file_event_create(const struct inotify_event *inevent,
|
|
FileWatcherContext *ctx) {
|
|
FileEvent event = {0};
|
|
|
|
if (!inevent) {
|
|
return event;
|
|
}
|
|
|
|
const FileWatch *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 < vector_size(&fw->files); ++i) {
|
|
const char *watched_file = vector_at(&fw->files, i);
|
|
if (strcmp(watched_file, filename) == 0) {
|
|
|
|
// check if event mask matches the watcher mask
|
|
WatchParams *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;
|
|
}
|
|
|
|
FileEvent file_event_pop(FileWatcher *fw) {
|
|
mtx_lock(&fw->mutex);
|
|
|
|
FileEvent e = NULL_EVENT;
|
|
if (vector_size(&fw->events) > 0) {
|
|
e = vector_last(&fw->events);
|
|
vector_del_last(&fw->events);
|
|
}
|
|
|
|
mtx_unlock(&fw->mutex);
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
* File Watcher
|
|
*/
|
|
|
|
static HiResult file_watcher_ctx_init(FileWatcherContext *ctx) {
|
|
|
|
ctx->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
|
|
if (ctx->fd == -1) {
|
|
return HI_FAIL;
|
|
}
|
|
|
|
vector_init(&ctx->watches);
|
|
vector_init(&ctx->params);
|
|
return HI_OK;
|
|
}
|
|
|
|
static void file_watcher_ctx_destroy(FileWatcherContext *ctx) {
|
|
if (ctx) {
|
|
file_watch_destroy_watches(ctx);
|
|
}
|
|
}
|
|
|
|
// Declare the thread func
|
|
int file_watcher_watch(void *arg);
|
|
|
|
FileWatcher *file_watcher_create() {
|
|
|
|
// Allocate context
|
|
FileWatcher *fw = malloc(sizeof(FileWatcher));
|
|
memset(fw, 0, sizeof(FileWatcher));
|
|
|
|
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;
|
|
}
|
|
vector_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;
|
|
}
|
|
|
|
HiResult file_watcher_add(FileWatcher *fw, const char *filename, u32 flags) {
|
|
if (!fw) {
|
|
sc_log_error("Attempted to add file watcher for '%s' with null context\n",
|
|
filename);
|
|
return HI_FAIL;
|
|
}
|
|
|
|
mtx_lock(&fw->mutex);
|
|
|
|
if (!HIOK(file_watch_add(&fw->context, flags, filename))) {
|
|
mtx_unlock(&fw->mutex);
|
|
return HI_FAIL;
|
|
}
|
|
{
|
|
// Add watch param as well
|
|
WatchParams *params =
|
|
watch_params_find_by_path(filename, &fw->context, NULL);
|
|
if (!params) {
|
|
WatchParams params = watch_params_create(filename, flags);
|
|
vector_add(&fw->context.params, params);
|
|
} else {
|
|
params->mask |= flags;
|
|
}
|
|
}
|
|
|
|
mtx_unlock(&fw->mutex);
|
|
return HI_OK;
|
|
}
|
|
|
|
HiResult hi_file_watcher_remove(FileWatcher *fw, const char *filename) {
|
|
if (!fw) {
|
|
return HI_FAIL;
|
|
}
|
|
|
|
mtx_lock(&fw->mutex);
|
|
if (!HIOK(file_watch_remove(&fw->context, filename))) {
|
|
mtx_unlock(&fw->mutex);
|
|
return HI_FAIL;
|
|
}
|
|
|
|
{
|
|
// remove watchparam as well
|
|
size_t i = 0;
|
|
WatchParams *params = watch_params_find_by_path(filename, &fw->context, &i);
|
|
watch_params_destroy(params);
|
|
vector_del(&fw->context.params, i);
|
|
}
|
|
|
|
mtx_unlock(&fw->mutex);
|
|
sc_log_info("Removed file: '%s' from watch, filename");
|
|
return HI_OK;
|
|
}
|
|
|
|
void file_watcher_notify(FileWatcher *fw) {
|
|
if (fw && fw->running) {
|
|
cnd_signal(&fw->cond);
|
|
}
|
|
}
|
|
|
|
void file_watcher_destroy(FileWatcher *fw) {
|
|
if (!fw)
|
|
return;
|
|
|
|
if (fw->running) {
|
|
fw->running = false;
|
|
file_watcher_notify(fw);
|
|
thrd_join(fw->thread, NULL);
|
|
}
|
|
|
|
file_watcher_ctx_destroy(&fw->context);
|
|
free(fw);
|
|
}
|
|
|
|
int file_watcher_watch(void *arg) {
|
|
FileWatcher *ctx = (FileWatcher *)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;
|
|
FileEvent e = file_event_create(event, &ctx->context);
|
|
if (e.type != HI_FILE_NONE) {
|
|
vector_add(&ctx->events, e);
|
|
|
|
if (event->len) {
|
|
sc_log_debug("Event created: queue-size: %u, %s %s\n",
|
|
vector_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;
|
|
}
|