filewatch: add filewatch module for basic file monitoring

Currently only an inotify-based backend is implemented.
This commit is contained in:
Andrei Alexeyev 2021-11-29 22:04:22 +02:00
parent b39c9ba78e
commit e629d927ba
No known key found for this signature in database
GPG key ID: 72D26128040B9690
7 changed files with 312 additions and 0 deletions

View file

@ -47,6 +47,8 @@ typedef enum {
TE_AUDIO_BGM_STARTED,
TE_FILEWATCH,
NUM_TAISEI_EVENTS
} TaiseiEvent;

23
src/filewatch/filewatch.h Normal file
View file

@ -0,0 +1,23 @@
/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#pragma once
#include "taisei.h"
typedef enum FileWatchEvent {
FILEWATCH_FILE_UPDATED,
FILEWATCH_FILE_DELETED,
} FileWatchEvent;
typedef struct FileWatch FileWatch;
void filewatch_init(void);
void filewatch_shutdown(void);
FileWatch *filewatch_watch(const char *syspath);
void filewatch_unwatch(FileWatch *watch);

View file

@ -0,0 +1,230 @@
/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "taisei.h"
#include "filewatch.h"
#include "util.h"
#include "events.h"
#include <sys/inotify.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#define INVALID_WD (-1)
#define WATCHED_EVENTS ( \
IN_ATTRIB | \
IN_CLOSE_WRITE | \
IN_DELETE_SELF | \
IN_MODIFY | \
IN_MOVE_SELF | \
0)
#define DELETION_EVENTS ( \
IN_ATTRIB | /* could be an inode refcount update… */ \
IN_DELETE_SELF | \
IN_IGNORED | \
IN_MOVE_SELF | \
0)
#define FW (*_fw_globals)
#define EVENTS_BUF_SIZE 4096
struct FileWatch {
int wd;
SDL_atomic_t refs;
bool updated;
bool deleted;
};
struct {
int inotify;
ht_int2ptr_ts_t wd_to_watch;
SDL_mutex *modify_mtx;
DYNAMIC_ARRAY(FileWatch*) deactivate_list;
} *_fw_globals;
static bool filewatch_frame_event(SDL_Event *e, void *a);
void filewatch_init(void) {
assert(_fw_globals == NULL);
int inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if(UNLIKELY(inotify < 0)) {
log_error("Failed to initialize inotify: %s", strerror(errno));
return;
}
_fw_globals = calloc(1, sizeof(*_fw_globals));
FW.inotify = inotify;
ht_create(&FW.wd_to_watch);
FW.modify_mtx = SDL_CreateMutex();
if(UNLIKELY(FW.modify_mtx == NULL)) {
log_sdl_error(LOG_WARN, "SDL_CreateMutex");
}
events_register_handler(&(EventHandler) {
.proc = filewatch_frame_event,
.priority = EPRIO_SYSTEM,
.event_type = MAKE_TAISEI_EVENT(TE_FRAME),
});
}
void filewatch_shutdown(void) {
if(_fw_globals) {
events_unregister_handler(filewatch_frame_event);
close(FW.inotify);
ht_destroy(&FW.wd_to_watch);
dynarray_free_data(&FW.deactivate_list);
SDL_DestroyMutex(FW.modify_mtx);
free(_fw_globals);
_fw_globals = NULL;
}
}
FileWatch *filewatch_watch(const char *syspath) {
if(!&FW) {
log_error("Subsystem not initialized");
return NULL;
}
SDL_LockMutex(FW.modify_mtx);
int wd = inotify_add_watch(FW.inotify, syspath, WATCHED_EVENTS);
if(UNLIKELY(wd == INVALID_WD)) {
SDL_UnlockMutex(FW.modify_mtx);
log_error("Failed to watch '%s': %s", syspath, strerror(errno));
return NULL;
}
FileWatch *w;
if(ht_lookup(&FW.wd_to_watch, wd, (void**)&w)) {
assert(w->wd == wd);
} else {
w = calloc(1, sizeof(*w));
w->wd = wd;
ht_set(&FW.wd_to_watch, wd, w);
}
SDL_AtomicIncRef(&w->refs);
SDL_UnlockMutex(FW.modify_mtx);
log_debug("Watching '%s'; wd = %i", syspath, w->wd);
return NOT_NULL(w);
}
static void filewatch_deactivate(FileWatch *watch) {
SDL_LockMutex(FW.modify_mtx);
if(watch->wd != INVALID_WD) {
assert(ht_get(&FW.wd_to_watch, watch->wd, NULL) == watch);
if(UNLIKELY(inotify_rm_watch(FW.inotify, watch->wd) == -1)) {
log_warn("Failed to remove inotify watch: %s", strerror(errno));
}
ht_unset(&FW.wd_to_watch, watch->wd);
watch->wd = INVALID_WD;
}
SDL_UnlockMutex(FW.modify_mtx);
}
void filewatch_unwatch(FileWatch *watch) {
if(SDL_AtomicDecRef(&watch->refs)) {
filewatch_deactivate(watch);
free(watch);
}
}
static void filewatch_process_events(ssize_t bufsize, char buf[bufsize]) {
struct inotify_event *e;
for(ssize_t i = 0; i < bufsize;) {
e = CASTPTR_ASSUME_ALIGNED(buf + i, struct inotify_event);
FileWatch *w = ht_get(&FW.wd_to_watch, e->wd, NULL);
if(w == NULL) {
log_debug("inotify event wd=%i mask=0x%08x (ignore)", e->wd, e->mask);
goto skip;
}
if(e->mask & DELETION_EVENTS) {
log_debug("inotify event wd=%i mask=0x%08x (delete)", e->wd, e->mask);
w->deleted = true;
} else {
log_debug("inotify event wd=%i mask=0x%08x (update)", e->wd, e->mask);
w->updated = true;
}
skip:
i += sizeof(*e) + e->len;
}
}
static void filewatch_process(void) {
SDL_LockMutex(FW.modify_mtx);
alignas(alignof(struct inotify_event)) char buf[EVENTS_BUF_SIZE];
for(;;) {
ssize_t r = read(FW.inotify, buf, sizeof(buf));
if(r == -1) {
if(errno != EAGAIN) {
log_error("%s", strerror(errno));
}
break;
}
filewatch_process_events(r, buf);
}
ht_lock(&FW.wd_to_watch);
ht_int2ptr_ts_iter_t iter;
ht_iter_begin(&FW.wd_to_watch, &iter);
for(;iter.has_data; ht_iter_next(&iter)) {
FileWatch *w = NOT_NULL(iter.value);
if(w->deleted) {
log_debug("Emitting delete event for wd %i", w->wd);
events_emit(TE_FILEWATCH, FILEWATCH_FILE_DELETED, w, NULL);
w->deleted = w->updated = false;
// defer filewatch_deactivate(w) because it modifies this hashtable
*dynarray_append(&FW.deactivate_list) = w;
} else if(w->updated) {
log_debug("Emitting update event for wd %i", w->wd);
events_emit(TE_FILEWATCH, FILEWATCH_FILE_UPDATED, w, NULL);
w->updated = false;
}
}
ht_iter_end(&iter);
ht_unlock(&FW.wd_to_watch);
dynarray_foreach_elem(&FW.deactivate_list, FileWatch **w, {
filewatch_deactivate(*w);
});
FW.deactivate_list.num_elements = 0;
SDL_UnlockMutex(FW.modify_mtx);
}
static bool filewatch_frame_event(SDL_Event *e, void *a) {
filewatch_process();
return false;
}

View file

@ -0,0 +1,24 @@
/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "taisei.h"
#include "filewatch.h"
void filewatch_init(void) {
}
void filewatch_shutdown(void) {
}
FileWatch *filewatch_watch(const char *syspath) {
return NULL;
}
void filewatch_unwatch(FileWatch *watch) {
}

28
src/filewatch/meson.build Normal file
View file

@ -0,0 +1,28 @@
filewatch_enabled = true
filewatch_src = []
if filewatch_enabled
# For shims like inotify-kqueue
inotify_dep = dependency('libinotify', required : false)
if not inotify_dep.found()
inotify_dep = cc.find_library('inotify', required : false)
endif
filewatch_enabled = (
have_posix and
cc.has_function('inotify_init1', dependencies : inotify_dep) and
cc.has_header_symbol('sys/inotify.h', 'struct inotify_event', dependencies : inotify_dep)
)
if not filewatch_enabled
warning('No inotify support; live file monitoring not available')
endif
endif
if filewatch_enabled
taisei_deps += inotify_dep
filewatch_src += files('filewatch_inotify.c')
else
filewatch_src += files('filewatch_null.c')
endif

View file

@ -30,6 +30,7 @@
#include "util/gamemode.h"
#include "cutscenes/cutscene.h"
#include "replay/struct.h"
#include "filewatch/filewatch.h"
attr_unused
static void taisei_shutdown(void) {
@ -45,6 +46,7 @@ static void taisei_shutdown(void) {
gamemode_shutdown();
free_all_refs();
free_resources(true);
filewatch_shutdown();
taskmgr_global_shutdown();
audio_shutdown();
video_shutdown();
@ -337,6 +339,7 @@ static void main_post_vfsinit(CallChainResult ccr) {
init_global(&ctx->cli);
events_init();
video_init();
filewatch_init();
init_resources();
r_post_init();
draw_loading_screen();

View file

@ -127,6 +127,7 @@ subdir('audio')
subdir('cutscenes')
subdir('dialog')
subdir('eventloop')
subdir('filewatch')
subdir('lasers')
subdir('menu')
subdir('pixmap')
@ -146,6 +147,7 @@ taisei_src += [
cutscenes_src,
dialog_src,
eventloop_src,
filewatch_src,
lasers_src,
menu_src,
pixmap_src,