add threaded filewatcher
This commit is contained in:
@@ -44,19 +44,21 @@ add_library(hiload SHARED
|
|||||||
src/symbols.c
|
src/symbols.c
|
||||||
src/files.c
|
src/files.c
|
||||||
src/memory.c
|
src/memory.c
|
||||||
|
src/string/string.c
|
||||||
|
src/filewatcher/filewatcher.c
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
src/string/hi_string.c
|
|
||||||
src/logger/sc_log.c
|
src/logger/sc_log.c
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON)
|
|
||||||
target_link_libraries(hiload dl)
|
target_link_libraries(hiload dl)
|
||||||
|
|
||||||
# Specify the public headers location
|
# Specify the public headers location
|
||||||
target_include_directories(hiload PUBLIC
|
target_include_directories(hiload PUBLIC
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # During build
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # During build
|
||||||
$<INSTALL_INTERFACE:include> # When installed
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> # During build
|
||||||
|
$<INSTALL_INTERFACE:include> # When installed
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS hiload
|
install(TARGETS hiload
|
||||||
|
|||||||
@@ -16,18 +16,28 @@ typedef enum {
|
|||||||
HI_RELOAD_OPEN_ERROR
|
HI_RELOAD_OPEN_ERROR
|
||||||
} ReloadResult;
|
} ReloadResult;
|
||||||
|
|
||||||
/* Initialiez the module. Must be called before anything else */
|
/**
|
||||||
int hi_init();
|
* Initialiez the module. Must be called before anything else
|
||||||
|
*/
|
||||||
|
int hi_init(unsigned n, const char **enabled_modules);
|
||||||
|
|
||||||
/* Frees memory and cleans up */
|
/**
|
||||||
|
* Frees memory and cleans up
|
||||||
|
*/
|
||||||
void hi_deinit();
|
void hi_deinit();
|
||||||
|
|
||||||
/* Print a random assortment of information from current state */
|
/**
|
||||||
|
* Print a random assortment of information from current state
|
||||||
|
*/
|
||||||
void hi_print_module_infos();
|
void hi_print_module_infos();
|
||||||
|
|
||||||
/* Reload shared library if it has changed since last reload or init was called */
|
/**
|
||||||
|
* Reload shared library if it has changed since last reload or init was called
|
||||||
|
*/
|
||||||
ReloadResult hi_reload_solib(const char *module_name);
|
ReloadResult hi_reload_solib(const char *module_name);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pragma GCC visibility pop
|
#pragma GCC visibility pop
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
13
src/common.h
Normal file
13
src/common.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef COMMON_H_
|
||||||
|
#define COMMON_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
static inline
|
||||||
|
u32 has_mask(u32 flags, u32 mask) {
|
||||||
|
return flags & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ARRLEN(A) (sizeof((A))/(sizeof ((A)[0])))
|
||||||
|
|
||||||
|
#endif // COMMON_H_
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#ifndef COMPILATION_H_
|
|
||||||
#define COMPILATION_H_
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char *directory;
|
|
||||||
const char *file;
|
|
||||||
const char *command;
|
|
||||||
} CompileCommand;
|
|
||||||
|
|
||||||
#endif // COMPILATION_H_
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
#include "files.h"
|
#include "files.h"
|
||||||
|
|
||||||
#include "logger.h"
|
#include "string/string.h"
|
||||||
#include "types.h"
|
#include "logger/logger.h"
|
||||||
#include "string/hi_string.h"
|
|
||||||
|
|
||||||
const char* hi_file_to_str_dyn(const char *filename) {
|
|
||||||
|
|
||||||
|
const char *hi_file_to_str_dyn(const char *filename) {
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
const char *s = hi_string_from_file_dyn(filename, 0, 0);
|
const char *s = hi_string_from_file_dyn(filename, 0, 0);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
@@ -14,3 +12,4 @@ const char* hi_file_to_str_dyn(const char *filename) {
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/files.h
10
src/files.h
@@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
const char* hi_file_to_str_dyn(const char *filename);
|
/**
|
||||||
|
* Read file dynamically to a string
|
||||||
|
*
|
||||||
|
* Reads the file until no more bytes are read. Can be used for virtual files
|
||||||
|
* with no size. The realistic optimum case contains two allocations, one for
|
||||||
|
* the initial memory and a reallocation to match the string size.
|
||||||
|
*/
|
||||||
|
const char *hi_file_to_str_dyn(const char *filename);
|
||||||
|
|
||||||
|
|
||||||
#endif // FILES_H_
|
#endif // FILES_H_
|
||||||
|
|||||||
6
src/filewatcher/fileevent.h
Normal file
6
src/filewatcher/fileevent.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef FILEEVENT_H_
|
||||||
|
#define FILEEVENT_H_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // FILEEVENT_H_
|
||||||
348
src/filewatcher/filewatcher.c
Normal file
348
src/filewatcher/filewatcher.c
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
#include "filewatcher.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdatomic.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 "common.h"
|
||||||
|
#include "logger/logger.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;
|
||||||
|
|
||||||
|
typedef struct hiFileWatcherContext {
|
||||||
|
thrd_t thread;
|
||||||
|
mtx_t mutex;
|
||||||
|
cnd_t cond;
|
||||||
|
int running;
|
||||||
|
struct hiWatchPaths watchpaths;
|
||||||
|
struct hiFileEvents events;
|
||||||
|
} hiFileWatcherContext;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File Events
|
||||||
|
*/
|
||||||
|
|
||||||
|
static hiFileEvent create_file_event(const struct inotify_event *event) {
|
||||||
|
hiFileEvent hievent = {0};
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return hievent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->len)
|
||||||
|
hievent.file = strdup(event->name);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return hievent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hiFileEvent copy_file_event(const hiFileEvent *event) {
|
||||||
|
hiFileEvent cpy = *event;
|
||||||
|
// Copy the file string, so this can be destroyed without destroying the
|
||||||
|
// original
|
||||||
|
cpy.file = strdup(event->file);
|
||||||
|
return cpy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hi_file_event_free(hiFileEvent *event) {
|
||||||
|
if (event) {
|
||||||
|
free((void *)event->file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hi_file_event_destroy(hiFileEvent *event) {
|
||||||
|
if (event) {
|
||||||
|
free((void *)event->file);
|
||||||
|
memset(event, 0, sizeof(*event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hiFileEvent hi_file_event_pop(hiFileWatcherContext *ctx) {
|
||||||
|
mtx_lock(&ctx->mutex);
|
||||||
|
hiFileEvent *last = &sc_array_last(&ctx->events.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);
|
||||||
|
|
||||||
|
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 (int 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 (int 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 (int 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Declare the thread func
|
||||||
|
int file_watcher_watch(void *arg);
|
||||||
|
|
||||||
|
hiFileWatcherContext *hi_file_watcher_create() {
|
||||||
|
|
||||||
|
// Allocate context
|
||||||
|
hiFileWatcherContext *context = malloc(sizeof(hiFileWatcherContext));
|
||||||
|
memset(context, 0, sizeof(hiFileWatcherContext));
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (!HILOADRES(watch_paths_init(&context->watchpaths))) {
|
||||||
|
sc_log_error("Failed to initialize watch paths\n");
|
||||||
|
if (context->watchpaths.fd == -1) {
|
||||||
|
sc_log_error("Couldn't initialize inotify\n");
|
||||||
|
}
|
||||||
|
free(context);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
free(context);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiloadResult hi_file_watcher_add(struct hiFileWatcherContext *ctx,
|
||||||
|
const char *filename, u32 flags) {
|
||||||
|
if (!ctx) {
|
||||||
|
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);
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
mtx_unlock(&ctx->mutex);
|
||||||
|
|
||||||
|
sc_log_info("Watching file: %s\n", filename);
|
||||||
|
return HILOAD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiloadResult hi_file_watcher_remove(struct hiFileWatcherContext *ctx,
|
||||||
|
const char *filename) {
|
||||||
|
if (!ctx) {
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx_lock(&ctx->mutex);
|
||||||
|
if (!HILOADRES(watch_path_remove(&ctx->watchpaths, filename))) {
|
||||||
|
mtx_unlock(&ctx->mutex);
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx_unlock(&ctx->mutex);
|
||||||
|
sc_log_info("Removed file: '%s' from watch, filename");
|
||||||
|
return HILOAD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hi_file_watcher_notify(hiFileWatcherContext *ctx) {
|
||||||
|
if (ctx && ctx->running) {
|
||||||
|
cnd_signal(&ctx->cond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hi_file_watcher_destroy(hiFileWatcherContext *ctx) {
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ctx->running) {
|
||||||
|
ctx->running = false;
|
||||||
|
hi_file_watcher_notify(ctx);
|
||||||
|
thrd_join(ctx->thread, NULL);
|
||||||
|
}
|
||||||
|
file_events_destroy(&ctx->events);
|
||||||
|
watch_paths_destroy(&ctx->watchpaths);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int file_watcher_watch(void *arg) {
|
||||||
|
hiFileWatcherContext *ctx = (hiFileWatcherContext *)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->watchpaths.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 = create_file_event(event);
|
||||||
|
sc_array_add(&ctx->events.events, e);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
77
src/filewatcher/filewatcher.h
Normal file
77
src/filewatcher/filewatcher.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#ifndef HI_FILEWATCHER_H_
|
||||||
|
#define HI_FILEWATCHER_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File event watching related functionality and types
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HI_FILE_NONE = 0,
|
||||||
|
HI_FILE_ACCESS = 1 << 0,
|
||||||
|
HI_FILE_MODIFY = 1 << 1,
|
||||||
|
HI_FILE_CREATE = 1 << 2,
|
||||||
|
HI_FILE_DELETE = 1 << 3,
|
||||||
|
HI_FILE_ALL =
|
||||||
|
(HI_FILE_ACCESS | HI_FILE_MODIFY | HI_FILE_CREATE | HI_FILE_DELETE)
|
||||||
|
} HiFileEventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *watcher; // Path given to the corresponding wd. Not owning.
|
||||||
|
const char *file; // Owning, should be freed by the event handler by calling
|
||||||
|
// `hi_file_event_destroy`)
|
||||||
|
HiFileEventType type;
|
||||||
|
} hiFileEvent;
|
||||||
|
|
||||||
|
struct hiFileWatcherContext;
|
||||||
|
/**
|
||||||
|
* FileEvents is a thread safe list of events.
|
||||||
|
*
|
||||||
|
* It is implemented as a stack, so the events will be popped
|
||||||
|
* in reverse chronological order.
|
||||||
|
* */
|
||||||
|
struct hiFileEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create watcher and necessary data to run it.
|
||||||
|
*
|
||||||
|
* Will start the watcher thread immediately.
|
||||||
|
*/
|
||||||
|
struct hiFileWatcherContext *hi_file_watcher_create();
|
||||||
|
/**
|
||||||
|
* Destroy a previously created file watcher context.
|
||||||
|
*
|
||||||
|
* Will join the thread and destroy event stack.
|
||||||
|
*/
|
||||||
|
void hi_file_watcher_destroy(struct hiFileWatcherContext *context);
|
||||||
|
/**
|
||||||
|
* Add a file to the watch list of a file watcher.
|
||||||
|
*
|
||||||
|
* @param context Previously created watcher context
|
||||||
|
* @param pathname Absolute path to the file or directory
|
||||||
|
* @param flags The events that will be watched for
|
||||||
|
*/
|
||||||
|
HiloadResult hi_file_watcher_add(struct hiFileWatcherContext *context,
|
||||||
|
const char *pathname, u32 flags);
|
||||||
|
/**
|
||||||
|
* Can be used to poke file watcher thread to make sure it empties
|
||||||
|
* events before doing something, e.g. for shutting it down.
|
||||||
|
*/
|
||||||
|
void hi_file_watcher_notify(struct hiFileWatcherContext *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees held memory but doesn't zero fields
|
||||||
|
*/
|
||||||
|
void hi_file_event_free(hiFileEvent *event);
|
||||||
|
/**
|
||||||
|
* Pop an event from event stack. Call `hi_file_event_destroy` when done with
|
||||||
|
* it.
|
||||||
|
*/
|
||||||
|
hiFileEvent hi_file_event_pop(struct hiFileWatcherContext *ctx);
|
||||||
|
/**
|
||||||
|
* Free and zero held memory. Call after pop.
|
||||||
|
*/
|
||||||
|
void hi_file_event_destroy(hiFileEvent *event);
|
||||||
|
|
||||||
|
#endif // HI_FILEWATCHER_H_
|
||||||
98
src/hiload.c
98
src/hiload.c
@@ -1,27 +1,37 @@
|
|||||||
#include "hiload/hiload.h"
|
#include "hiload/hiload.h"
|
||||||
|
|
||||||
#include "logger.h"
|
#include "array/sc_array.h"
|
||||||
|
#include "files.h"
|
||||||
|
#include "filewatcher/filewatcher.h"
|
||||||
|
#include "logger/logger.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "string/string.h"
|
||||||
#include "symbols.h"
|
#include "symbols.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
#include <libdwarf/libdwarf.h>
|
||||||
#include <libelf.h>
|
#include <libelf.h>
|
||||||
#include <link.h>
|
#include <link.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
sc_array_def(uptr, uptr);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
struct sc_array_str names; // Array of library names
|
struct sc_array_str names; // Array of library names
|
||||||
struct sc_array_ptr handles; // Array of library handles
|
struct sc_array_ptr handles; // Array of library handles
|
||||||
struct sc_array_syms symbols; // Symbol info for modules
|
struct sc_array_uptr addresses; // Array of library base addresses
|
||||||
size_t count; // Number of modules
|
struct sc_array_syms symbols; // Symbol info for modules
|
||||||
|
size_t count; // Number of modules
|
||||||
} ModuleInfos;
|
} ModuleInfos;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
struct sc_array_memreg memory_regions;
|
struct sc_array_memreg memory_regions;
|
||||||
|
struct sc_array_str enabled_modules;
|
||||||
|
struct hiFileWatcherContext *filewatcher;
|
||||||
} HiloadContext;
|
} HiloadContext;
|
||||||
|
|
||||||
static HiloadContext context = {0};
|
static HiloadContext context = {0};
|
||||||
@@ -44,14 +54,24 @@ static inline int if_load_symbols_for_module(struct dl_phdr_info *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Callback function for dl_iterate_phdr
|
// Callback function for dl_iterate_phdr
|
||||||
static
|
static int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
|
||||||
int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
|
|
||||||
void *data) {
|
void *data) {
|
||||||
|
|
||||||
ModuleInfos *infos = (ModuleInfos *)data;
|
ModuleInfos *infos = (ModuleInfos *)data;
|
||||||
|
|
||||||
// Store the module name
|
// Store the module name
|
||||||
sc_array_add(&infos->names, strdup(info->dlpi_name));
|
const char *modname = info->dlpi_name;
|
||||||
|
sc_array_add(&infos->names, strdup(modname));
|
||||||
|
for (int i = 0; i < sc_array_size(&context.enabled_modules); i++) {
|
||||||
|
|
||||||
|
if (hi_path_has_filename(modname,
|
||||||
|
sc_array_at(&context.enabled_modules, i))) {
|
||||||
|
if (!HILOADRES(
|
||||||
|
hi_file_watcher_add(context.filewatcher, modname, HI_FILE_ALL))) {
|
||||||
|
sc_log_error("Failed to set filewatcher for: '%s'\n", modname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sc_log_info("Processing: '%s'\n", info->dlpi_name);
|
sc_log_info("Processing: '%s'\n", info->dlpi_name);
|
||||||
|
|
||||||
@@ -61,20 +81,28 @@ int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
|
|||||||
infos->count++;
|
infos->count++;
|
||||||
|
|
||||||
sc_array_add(&infos->handles, handle);
|
sc_array_add(&infos->handles, handle);
|
||||||
|
sc_array_add(&infos->addresses, info->dlpi_addr);
|
||||||
sc_log_debug(" size: %u\n", size);
|
sc_log_debug(" size: %u\n", size);
|
||||||
sc_log_debug(" handle: %p\n", sc_array_last(&infos->handles));
|
sc_log_debug(" handle: %p\n", sc_array_last(&infos->handles));
|
||||||
sc_log_debug(" dlpi_addr: %p\n", info->dlpi_addr);
|
sc_log_debug(" dlpi_addr: %p\n", info->dlpi_addr);
|
||||||
sc_log_debug(" dlpi_tls_modid: %zu\n", info->dlpi_tls_modid);
|
sc_log_debug(" dlpi_tls_modid: %zu\n", info->dlpi_tls_modid);
|
||||||
sc_log_debug(" dlpi_tls_data: %p\n", info->dlpi_tls_data);
|
sc_log_debug(" dlpi_tls_data: %p\n", info->dlpi_tls_data);
|
||||||
|
|
||||||
|
Dl_info dl_info = {0};
|
||||||
|
if (!dladdr((void *)sc_array_last(&infos->addresses), &dl_info)) {
|
||||||
|
sc_log_error("Couldn't load dlinfo for %s: %s\n",
|
||||||
|
(sc_array_last(&infos->names)), dlerror());
|
||||||
|
}
|
||||||
|
sc_log_debug(" dli_fname: %s\n", dl_info.dli_fname);
|
||||||
|
sc_log_debug(" dli_fbase: %p\n", dl_info.dli_fbase);
|
||||||
|
|
||||||
SymbolInfos symbol_info = {0};
|
SymbolInfos symbol_info = {0};
|
||||||
sc_array_add(&infos->symbols, symbol_info);
|
sc_array_add(&infos->symbols, symbol_info);
|
||||||
|
|
||||||
return 0; // Continue iteration
|
return 0; // Continue iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void module_infos_free(ModuleInfos *modules) {
|
||||||
void free_module_infos(ModuleInfos *modules) {
|
|
||||||
if (!modules)
|
if (!modules)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -94,8 +122,7 @@ void free_module_infos(ModuleInfos *modules) {
|
|||||||
free(modules);
|
free(modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static ModuleInfos *gather_module_infos(void) {
|
||||||
ModuleInfos *gather_module_infos(void) {
|
|
||||||
ModuleInfos *result = NULL;
|
ModuleInfos *result = NULL;
|
||||||
|
|
||||||
// Allocate the result structure
|
// Allocate the result structure
|
||||||
@@ -107,16 +134,15 @@ ModuleInfos *gather_module_infos(void) {
|
|||||||
// Iterate over all loaded shared objects
|
// Iterate over all loaded shared objects
|
||||||
if (dl_iterate_phdr(gather_module_infos_callback, result) != 0) {
|
if (dl_iterate_phdr(gather_module_infos_callback, result) != 0) {
|
||||||
// Error occurred in callback
|
// Error occurred in callback
|
||||||
free_module_infos(result);
|
module_infos_free(result);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
|
||||||
ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
|
void **updated_handle) {
|
||||||
void **updated_handle) {
|
|
||||||
if (!modules || !filename) {
|
if (!modules || !filename) {
|
||||||
return HI_RELOAD_NOT_FOUND;
|
return HI_RELOAD_NOT_FOUND;
|
||||||
}
|
}
|
||||||
@@ -129,21 +155,11 @@ ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
|
|||||||
// Check if this is the module we're looking for
|
// Check if this is the module we're looking for
|
||||||
const char *pathname = sc_array_at(&modules->names, i);
|
const char *pathname = sc_array_at(&modules->names, i);
|
||||||
|
|
||||||
if (pathname && strcmp(pathname, filename) == 0) {
|
if (hi_path_has_filename(pathname, filename)) {
|
||||||
found = 1;
|
found = 1;
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check if the filename is at the end of the path
|
|
||||||
if (pathname) {
|
|
||||||
const char *basename = strrchr(pathname, '/');
|
|
||||||
if (basename && strcmp(basename + 1, filename) == 0) {
|
|
||||||
found = 1;
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
@@ -164,7 +180,7 @@ ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the module again with RTLD_NOW
|
// Open the module with RTLD_NOW
|
||||||
void *new_handle = dlopen(fullpath, RTLD_NOW);
|
void *new_handle = dlopen(fullpath, RTLD_NOW);
|
||||||
if (!new_handle) {
|
if (!new_handle) {
|
||||||
sc_log_error("Error reloading module: %s\n", dlerror());
|
sc_log_error("Error reloading module: %s\n", dlerror());
|
||||||
@@ -200,15 +216,13 @@ void hi_print_module_infos() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_log_info("Found %zu loaded modules:\n\n", modules->count);
|
sc_log_info("Found %zu loaded modules:\n", modules->count);
|
||||||
|
|
||||||
for (size_t i = 0; i < modules->count; i++) {
|
for (size_t i = 0; i < modules->count; i++) {
|
||||||
// Get handle information where possible
|
|
||||||
Dl_info info = {0};
|
|
||||||
int has_info = 0;
|
|
||||||
|
|
||||||
sc_log_debug("'%s': %p\n", sc_array_at(&modules->names, i),
|
sc_log_debug("'%s'\n", sc_array_at(&modules->names, i));
|
||||||
sc_array_at(&modules->handles, i));
|
sc_log_debug(" handle: %p\n", sc_array_at(&modules->handles, i));
|
||||||
|
sc_log_debug(" address: %p\n", sc_array_at(&modules->addresses, i));
|
||||||
|
|
||||||
const SymbolInfos *symbols = &sc_array_at(&modules->symbols, i);
|
const SymbolInfos *symbols = &sc_array_at(&modules->symbols, i);
|
||||||
for (int j = 0; j < sc_array_size(&symbols->names); j++) {
|
for (int j = 0; j < sc_array_size(&symbols->names); j++) {
|
||||||
@@ -221,7 +235,7 @@ void hi_print_module_infos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int hi_init() {
|
int hi_init(unsigned n, const char *enabled_modules[n]) {
|
||||||
assert(!module_infos);
|
assert(!module_infos);
|
||||||
|
|
||||||
if (sc_log_init() != 0) {
|
if (sc_log_init() != 0) {
|
||||||
@@ -229,6 +243,16 @@ int hi_init() {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
sc_log_set_level("DEBUG");
|
sc_log_set_level("DEBUG");
|
||||||
|
sc_log_set_thread_name("Main");
|
||||||
|
|
||||||
|
// Start the filewatcher
|
||||||
|
context.filewatcher = hi_file_watcher_create();
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < n; i++) {
|
||||||
|
const char *module_name = enabled_modules[i];
|
||||||
|
sc_log_info("Enabling module: '%s'\n", module_name);
|
||||||
|
sc_array_add(&context.enabled_modules, module_name);
|
||||||
|
}
|
||||||
|
|
||||||
if (read_memory_maps_self(&context.memory_regions) != HILOAD_OK) {
|
if (read_memory_maps_self(&context.memory_regions) != HILOAD_OK) {
|
||||||
sc_log_error("Could not populate program memory maps.\n");
|
sc_log_error("Could not populate program memory maps.\n");
|
||||||
@@ -243,7 +267,7 @@ int hi_init() {
|
|||||||
|
|
||||||
// Override existing
|
// Override existing
|
||||||
if (module_infos) {
|
if (module_infos) {
|
||||||
free_module_infos(module_infos);
|
module_infos_free(module_infos);
|
||||||
}
|
}
|
||||||
|
|
||||||
module_infos = infos;
|
module_infos = infos;
|
||||||
@@ -253,6 +277,6 @@ int hi_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void hi_deinit() {
|
void hi_deinit() {
|
||||||
free_module_infos(module_infos);
|
module_infos_free(module_infos);
|
||||||
sc_log_term();
|
sc_log_term();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "array/sc_array.h"
|
#include "array/sc_array.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "logger.h"
|
#include "logger/logger.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static inline int ptr_in_range(uptr ptr, uptr start, uptr end) {
|
static inline int ptr_in_range(uptr ptr, uptr start, uptr end) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef MEMORY_H_
|
#ifndef MEMORY_H_
|
||||||
#define MEMORY_H_
|
#define MEMORY_H_
|
||||||
|
|
||||||
#include "array.h"
|
#include "array/array.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#ifndef HI_STRING_H_
|
|
||||||
#define HI_STRING_H_
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/***
|
|
||||||
* @brief Copy file content to a null terminated string, allocating memory while reading.
|
|
||||||
*
|
|
||||||
* This doesn't assume it can read file size, so it allocates memory in chunks
|
|
||||||
* (default 4096 bytes) and keeps reading until 0 bytes is read. If nmax is non-zero, instead that amount of bytes is allocated
|
|
||||||
* and that is read.
|
|
||||||
*
|
|
||||||
* In either case, the string is reallocated to match the length before returning.
|
|
||||||
* @param filename
|
|
||||||
* @param nread if not null, this will have the total amount read in bytes
|
|
||||||
* @param nmax if not 0, this amount of memory in bytes is read ant allocated
|
|
||||||
***/
|
|
||||||
const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
|
||||||
size_t nmax);
|
|
||||||
|
|
||||||
#endif // HI_STRING_H_
|
|
||||||
@@ -1,11 +1,26 @@
|
|||||||
#include "hi_string.h"
|
#include "string.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int hi_path_has_filename(const char *path, const char *filename) {
|
||||||
|
|
||||||
|
const char *compared_filename = path;
|
||||||
|
const char *basename = strrchr(path, '/');
|
||||||
|
|
||||||
|
if (basename)
|
||||||
|
compared_filename = basename + 1;
|
||||||
|
|
||||||
|
if (strcmp(compared_filename, filename) == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
||||||
size_t nmax) {
|
size_t nmax) {
|
||||||
|
|
||||||
FILE *f = fopen(filename, "r");
|
FILE *f = fopen(filename, "r");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
@@ -36,9 +51,9 @@ const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
|||||||
chunk_size *= 2;
|
chunk_size *= 2;
|
||||||
char *new_buf = realloc(buf, chunk_size);
|
char *new_buf = realloc(buf, chunk_size);
|
||||||
if (!new_buf) {
|
if (!new_buf) {
|
||||||
perror("Couldn't realloc memory");
|
perror("Couldn't realloc memory");
|
||||||
free(buf);
|
free(buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
buf = new_buf;
|
buf = new_buf;
|
||||||
p = buf + total_read;
|
p = buf + total_read;
|
||||||
@@ -51,9 +66,9 @@ const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
|||||||
if (p != (end - 1)) {
|
if (p != (end - 1)) {
|
||||||
char *new_buf = realloc(buf, total_read + 1);
|
char *new_buf = realloc(buf, total_read + 1);
|
||||||
if (!new_buf) {
|
if (!new_buf) {
|
||||||
perror("Couldn't realloc memory");
|
perror("Couldn't realloc memory");
|
||||||
free(buf);
|
free(buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
buf = new_buf;
|
buf = new_buf;
|
||||||
}
|
}
|
||||||
31
src/string/string.h
Normal file
31
src/string/string.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef HI_STRING_H_
|
||||||
|
#define HI_STRING_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy file content to a null terminated string, allocating memory while
|
||||||
|
* reading.
|
||||||
|
*
|
||||||
|
* This doesn't assume it can read file size, so it allocates memory in chunks
|
||||||
|
* (default 4096 bytes) and keeps reading until 0 bytes is read. If nmax is
|
||||||
|
* non-zero, instead that amount of bytes is allocated and that is read.
|
||||||
|
*
|
||||||
|
* In either case, the string is reallocated to match the length before
|
||||||
|
* returning.
|
||||||
|
* @param filename
|
||||||
|
* @param nread if not null, this will have the total amount bytes read
|
||||||
|
* @param nmax if not 0, this amount of memory in bytes is read and used as
|
||||||
|
* initial allocation
|
||||||
|
*/
|
||||||
|
const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
||||||
|
size_t nmax);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find if filename exists at the end of path.
|
||||||
|
*
|
||||||
|
* @return 0 if not found, positive if found
|
||||||
|
*/
|
||||||
|
int hi_path_has_filename(const char *path, const char *filename);
|
||||||
|
|
||||||
|
#endif // HI_STRING_H_
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "symbols.h"
|
#include "symbols.h"
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger/logger.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ typedef double f64;
|
|||||||
typedef uintptr_t uptr;
|
typedef uintptr_t uptr;
|
||||||
typedef ptrdiff_t ptrdiff;
|
typedef ptrdiff_t ptrdiff;
|
||||||
|
|
||||||
typedef enum { HILOAD_OK = 0, HILOAD_FAIL } HiloadResult;
|
typedef enum { HILOAD_FAIL = 0, HILOAD_OK } HiloadResult;
|
||||||
|
#define HILOADRES(res) ((res) == HILOAD_OK)
|
||||||
|
|
||||||
|
|
||||||
#endif // TYPES_H_
|
#endif // TYPES_H_
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
hi_init();
|
const char *reloadable_modules[] = {"", "libmini.so"};
|
||||||
|
hi_init(2, reloadable_modules);
|
||||||
|
|
||||||
int modified = -1;
|
int modified = -1;
|
||||||
while (modified != 0) {
|
while (modified != 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user