make filewatcher more robust
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/select.h>
|
||||
@@ -9,287 +10,253 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "array/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, fevent);
|
||||
|
||||
typedef struct hiFileEvents {
|
||||
struct sc_array_fevent events;
|
||||
} hiFileEvents;
|
||||
|
||||
typedef struct {
|
||||
int wd; // Watch descriptor
|
||||
u32 mask; // watch mask used
|
||||
const char *path; // Owning
|
||||
} WatchPath;
|
||||
sc_array_def(WatchPath, watch);
|
||||
|
||||
typedef struct hiWatchPaths {
|
||||
int fd;
|
||||
struct sc_array_watch watches;
|
||||
} hiWatchPaths;
|
||||
sc_array_def(hiFileEvent, fwevent);
|
||||
|
||||
typedef struct hiFileWatcher {
|
||||
thrd_t thread;
|
||||
mtx_t mutex;
|
||||
cnd_t cond;
|
||||
int running;
|
||||
struct hiWatchPaths watchpaths;
|
||||
struct hiFileEvents events;
|
||||
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
|
||||
*/
|
||||
|
||||
static hiFileEvent create_file_event(const struct inotify_event *event) {
|
||||
hiFileEvent hievent = {0};
|
||||
#define NULL_EVENT (hiFileEvent){0};
|
||||
|
||||
if (!event) {
|
||||
return hievent;
|
||||
static hiFileEvent file_event_create(const struct inotify_event *inevent,
|
||||
hiFileWatcherContext *ctx) {
|
||||
hiFileEvent event = {0};
|
||||
|
||||
if (!inevent) {
|
||||
return event;
|
||||
}
|
||||
|
||||
if (event->len)
|
||||
hievent.file = strdup(event->name);
|
||||
const hiFileWatch *fw = file_watch_find_by_wd(ctx, inevent->wd, NULL);
|
||||
|
||||
if (has_mask(event->mask, IN_ACCESS)) {
|
||||
hievent.type = HI_FILE_ACCESS;
|
||||
} else if (has_mask(event->mask, IN_MODIFY)) {
|
||||
hievent.type = HI_FILE_MODIFY;
|
||||
} else if (has_mask(event->mask, IN_CREATE)) {
|
||||
hievent.type = HI_FILE_CREATE;
|
||||
} else if (has_mask(event->mask, IN_DELETE)) {
|
||||
hievent.type = HI_FILE_DELETE;
|
||||
if (has_mask(inevent->mask, IN_IGNORED)) {
|
||||
sc_log_debug("IN_IGNORED received on %s\n", fw->path);
|
||||
return NULL_EVENT;
|
||||
}
|
||||
return hievent;
|
||||
}
|
||||
|
||||
void hi_file_event_free(hiFileEvent *event) {
|
||||
if (event) {
|
||||
free((void *)event->file);
|
||||
}
|
||||
}
|
||||
if (inevent->len) {
|
||||
// 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, inevent->name)) {
|
||||
|
||||
void hi_file_event_destroy(hiFileEvent *event) {
|
||||
if (event) {
|
||||
free((void *)event->file);
|
||||
memset(event, 0, sizeof(*event));
|
||||
// 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;
|
||||
}
|
||||
|
||||
event.pathname = watched_file;
|
||||
|
||||
// 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 *ctx) {
|
||||
mtx_lock(&ctx->mutex);
|
||||
hiFileEvent *last = &sc_array_last(&ctx->events.events);
|
||||
hiFileEvent *last = &sc_array_last(&ctx->events);
|
||||
hiFileEvent e = *last;
|
||||
|
||||
// prevent destroy from freeing the file string
|
||||
last->file = 0;
|
||||
hi_file_event_destroy(last);
|
||||
sc_array_del_last(&ctx->events.events);
|
||||
sc_array_del_last(&ctx->events);
|
||||
|
||||
mtx_unlock(&ctx->mutex);
|
||||
return e;
|
||||
}
|
||||
|
||||
static HiloadResult file_events_init(hiFileEvents *events) {
|
||||
sc_array_init(&events->events);
|
||||
return HILOAD_OK;
|
||||
}
|
||||
|
||||
static void file_events_destroy(hiFileEvents *events) {
|
||||
if (events) {
|
||||
for (size_t i = 0; i < sc_array_size(&events->events); ++i) {
|
||||
hi_file_event_free(&sc_array_at(&events->events, i));
|
||||
}
|
||||
sc_array_term(&events->events);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Watch paths
|
||||
*/
|
||||
|
||||
static inline u32 create_watch_mask(u32 flags) {
|
||||
u32 inmask = 0;
|
||||
if (has_mask(flags, HI_FILE_ACCESS)) {
|
||||
inmask |= IN_ACCESS;
|
||||
}
|
||||
if (has_mask(flags, HI_FILE_MODIFY)) {
|
||||
inmask |= IN_MODIFY;
|
||||
}
|
||||
if (has_mask(flags, HI_FILE_CREATE)) {
|
||||
inmask |= IN_CREATE;
|
||||
}
|
||||
if (has_mask(flags, HI_FILE_DELETE)) {
|
||||
inmask |= IN_DELETE;
|
||||
}
|
||||
return inmask;
|
||||
}
|
||||
|
||||
static HiloadResult watch_path_add(hiWatchPaths *paths, u32 mask,
|
||||
const char *path) {
|
||||
|
||||
if (!paths || paths->fd == -1) {
|
||||
sc_log_error("Invalid inotify context\n");
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
|
||||
int wd = inotify_add_watch(paths->fd, path, create_watch_mask(mask));
|
||||
if (wd == -1) {
|
||||
sc_log_error("Couldn't watch: %s: %s\n", path, strerror(errno));
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
|
||||
{
|
||||
WatchPath wp = {.wd = wd, .mask = mask, .path = strdup(path)};
|
||||
sc_array_add(&paths->watches, wp);
|
||||
}
|
||||
|
||||
return HILOAD_OK;
|
||||
}
|
||||
|
||||
static void watch_path_destroy(int fd, WatchPath *wf) {
|
||||
inotify_rm_watch(fd, wf->wd);
|
||||
free((void *)wf->path);
|
||||
|
||||
wf->wd = -1;
|
||||
wf->path = NULL;
|
||||
}
|
||||
|
||||
static HiloadResult watch_path_remove(hiWatchPaths *paths, const char *path) {
|
||||
|
||||
bool found = false;
|
||||
{
|
||||
for (size_t i = 0; i < sc_array_size(&paths->watches); ++i) {
|
||||
WatchPath *wp = &sc_array_at(&paths->watches, i);
|
||||
if (strcmp(wp->path, path) == 0) {
|
||||
watch_path_destroy(paths->fd, wp);
|
||||
sc_array_del(&paths->watches, i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found ? HILOAD_OK : HILOAD_FAIL;
|
||||
}
|
||||
|
||||
static HiloadResult watch_paths_init(hiWatchPaths *paths) {
|
||||
|
||||
paths->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
|
||||
if (paths->fd == -1) {
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
|
||||
sc_array_init(&paths->watches);
|
||||
return HILOAD_OK;
|
||||
}
|
||||
|
||||
static void watch_paths_destroy(hiWatchPaths *paths) {
|
||||
if (paths) {
|
||||
for (size_t i = 0; i < sc_array_size(&paths->watches); i++) {
|
||||
WatchPath *wp = &sc_array_at(&paths->watches, i);
|
||||
watch_path_destroy(paths->fd, wp);
|
||||
}
|
||||
sc_array_term(&paths->watches);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 *context = malloc(sizeof(hiFileWatcher));
|
||||
memset(context, 0, sizeof(hiFileWatcher));
|
||||
hiFileWatcher *fw = malloc(sizeof(hiFileWatcher));
|
||||
memset(fw, 0, sizeof(hiFileWatcher));
|
||||
|
||||
if (context) {
|
||||
if (!HILOADRES(watch_paths_init(&context->watchpaths))) {
|
||||
if (fw) {
|
||||
if (!HILOADRES(file_watcher_ctx_init(&fw->context))) {
|
||||
sc_log_error("Failed to initialize watch paths\n");
|
||||
if (context->watchpaths.fd == -1) {
|
||||
if (fw->context.fd == -1) {
|
||||
sc_log_error("Couldn't initialize inotify\n");
|
||||
}
|
||||
free(context);
|
||||
free(fw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!HILOADRES(file_events_init(&context->events))) {
|
||||
sc_log_error("Failed to initialize file events\n");
|
||||
free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mtx_init(&context->mutex, mtx_plain);
|
||||
cnd_init(&context->cond);
|
||||
context->running = true;
|
||||
thrd_create(&context->thread, file_watcher_watch, context);
|
||||
return context;
|
||||
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(context);
|
||||
free(fw);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HiloadResult hi_file_watcher_add(struct hiFileWatcher *ctx,
|
||||
const char *filename, u32 flags) {
|
||||
if (!ctx) {
|
||||
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(&ctx->mutex);
|
||||
if (!HILOADRES(watch_path_add(&ctx->watchpaths, flags, filename))) {
|
||||
mtx_unlock(&ctx->mutex);
|
||||
mtx_lock(&fw->mutex);
|
||||
|
||||
if (!HILOADRES(file_watch_add(&fw->context, flags, filename))) {
|
||||
mtx_unlock(&fw->mutex);
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
mtx_unlock(&ctx->mutex);
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
sc_log_info("Watching file: %s\n", filename);
|
||||
mtx_unlock(&fw->mutex);
|
||||
return HILOAD_OK;
|
||||
}
|
||||
|
||||
HiloadResult hi_file_watcher_remove(struct hiFileWatcher *ctx,
|
||||
HiloadResult hi_file_watcher_remove(struct hiFileWatcher *fw,
|
||||
const char *filename) {
|
||||
if (!ctx) {
|
||||
if (!fw) {
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
|
||||
mtx_lock(&ctx->mutex);
|
||||
if (!HILOADRES(watch_path_remove(&ctx->watchpaths, filename))) {
|
||||
mtx_unlock(&ctx->mutex);
|
||||
mtx_lock(&fw->mutex);
|
||||
if (!HILOADRES(file_watch_remove(&fw->context, filename))) {
|
||||
mtx_unlock(&fw->mutex);
|
||||
return HILOAD_FAIL;
|
||||
}
|
||||
|
||||
mtx_unlock(&ctx->mutex);
|
||||
{
|
||||
// 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 *ctx) {
|
||||
if (ctx && ctx->running) {
|
||||
cnd_signal(&ctx->cond);
|
||||
void hi_file_watcher_notify(hiFileWatcher *fw) {
|
||||
if (fw && fw->running) {
|
||||
cnd_signal(&fw->cond);
|
||||
}
|
||||
}
|
||||
|
||||
void hi_file_watcher_destroy(hiFileWatcher *ctx) {
|
||||
if (!ctx)
|
||||
void hi_file_watcher_destroy(hiFileWatcher *fw) {
|
||||
if (!fw)
|
||||
return;
|
||||
|
||||
if (ctx->running) {
|
||||
ctx->running = false;
|
||||
hi_file_watcher_notify(ctx);
|
||||
thrd_join(ctx->thread, NULL);
|
||||
if (fw->running) {
|
||||
fw->running = false;
|
||||
hi_file_watcher_notify(fw);
|
||||
thrd_join(fw->thread, NULL);
|
||||
}
|
||||
file_events_destroy(&ctx->events);
|
||||
watch_paths_destroy(&ctx->watchpaths);
|
||||
free(ctx);
|
||||
|
||||
file_watcher_ctx_destroy(&fw->context);
|
||||
free(fw);
|
||||
}
|
||||
|
||||
int file_watcher_watch(void *arg) {
|
||||
@@ -304,7 +271,7 @@ int file_watcher_watch(void *arg) {
|
||||
while (ctx->running) {
|
||||
ssize_t n;
|
||||
|
||||
n = read(ctx->watchpaths.fd, buf, sizeof(buf));
|
||||
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));
|
||||
@@ -316,8 +283,13 @@ int file_watcher_watch(void *arg) {
|
||||
ptr += sizeof(struct inotify_event) + event->len) {
|
||||
|
||||
event = (const struct inotify_event *)ptr;
|
||||
hiFileEvent e = create_file_event(event);
|
||||
sc_array_add(&ctx->events.events, e);
|
||||
hiFileEvent e = file_event_create(event, &ctx->context);
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user