add threaded filewatcher

This commit is contained in:
2025-03-27 01:24:57 +02:00
parent 6d0cbbb011
commit 2015894068
20 changed files with 600 additions and 95 deletions

View File

@@ -44,19 +44,21 @@ add_library(hiload SHARED
src/symbols.c
src/files.c
src/memory.c
src/string/string.c
src/filewatcher/filewatcher.c
# dependencies
src/string/hi_string.c
src/logger/sc_log.c
)
set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_libraries(hiload dl)
# Specify the public headers location
target_include_directories(hiload PUBLIC
$<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

View File

@@ -16,18 +16,28 @@ typedef enum {
HI_RELOAD_OPEN_ERROR
} 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();
/* Print a random assortment of information from current state */
/**
* Print a random assortment of information from current state
*/
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);
#pragma GCC visibility pop
#ifdef __cplusplus

13
src/common.h Normal file
View 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_

View File

@@ -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_

View File

@@ -1,11 +1,9 @@
#include "files.h"
#include "logger.h"
#include "types.h"
#include "string/hi_string.h"
const char* hi_file_to_str_dyn(const char *filename) {
#include "string/string.h"
#include "logger/logger.h"
const char *hi_file_to_str_dyn(const char *filename) {
size_t n = 0;
const char *s = hi_string_from_file_dyn(filename, 0, 0);
if (!s) {
@@ -14,3 +12,4 @@ const char* hi_file_to_str_dyn(const char *filename) {
}
return s;
}

View File

@@ -3,6 +3,14 @@
#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_

View File

@@ -0,0 +1,6 @@
#ifndef FILEEVENT_H_
#define FILEEVENT_H_
#endif // FILEEVENT_H_

View 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;
}

View 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_

View File

@@ -1,27 +1,37 @@
#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 "string/string.h"
#include "symbols.h"
#include "types.h"
#include <assert.h>
#include <dlfcn.h>
#include <libdwarf/libdwarf.h>
#include <libelf.h>
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
sc_array_def(uptr, uptr);
typedef struct {
struct sc_array_str names; // Array of library names
struct sc_array_ptr handles; // Array of library handles
struct sc_array_syms symbols; // Symbol info for modules
size_t count; // Number of modules
struct sc_array_str names; // Array of library names
struct sc_array_ptr handles; // Array of library handles
struct sc_array_uptr addresses; // Array of library base addresses
struct sc_array_syms symbols; // Symbol info for modules
size_t count; // Number of modules
} ModuleInfos;
typedef struct {
struct sc_array_memreg memory_regions;
struct sc_array_str enabled_modules;
struct hiFileWatcherContext *filewatcher;
} HiloadContext;
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
static
int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
static int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
void *data) {
ModuleInfos *infos = (ModuleInfos *)data;
// 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);
@@ -61,20 +81,28 @@ int gather_module_infos_callback(struct dl_phdr_info *info, size_t size,
infos->count++;
sc_array_add(&infos->handles, handle);
sc_array_add(&infos->addresses, info->dlpi_addr);
sc_log_debug(" size: %u\n", size);
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_tls_modid: %zu\n", info->dlpi_tls_modid);
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};
sc_array_add(&infos->symbols, symbol_info);
return 0; // Continue iteration
}
static
void free_module_infos(ModuleInfos *modules) {
static void module_infos_free(ModuleInfos *modules) {
if (!modules)
return;
@@ -94,8 +122,7 @@ void free_module_infos(ModuleInfos *modules) {
free(modules);
}
static
ModuleInfos *gather_module_infos(void) {
static ModuleInfos *gather_module_infos(void) {
ModuleInfos *result = NULL;
// Allocate the result structure
@@ -107,16 +134,15 @@ ModuleInfos *gather_module_infos(void) {
// Iterate over all loaded shared objects
if (dl_iterate_phdr(gather_module_infos_callback, result) != 0) {
// Error occurred in callback
free_module_infos(result);
module_infos_free(result);
return NULL;
}
return result;
}
static
ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
void **updated_handle) {
static ReloadResult reload_solib(ModuleInfos *modules, const char *filename,
void **updated_handle) {
if (!modules || !filename) {
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
const char *pathname = sc_array_at(&modules->names, i);
if (pathname && strcmp(pathname, filename) == 0) {
if (hi_path_has_filename(pathname, filename)) {
found = 1;
index = i;
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) {
@@ -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);
if (!new_handle) {
sc_log_error("Error reloading module: %s\n", dlerror());
@@ -200,15 +216,13 @@ void hi_print_module_infos() {
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++) {
// 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_array_at(&modules->handles, i));
sc_log_debug("'%s'\n", sc_array_at(&modules->names, 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);
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);
if (sc_log_init() != 0) {
@@ -229,6 +243,16 @@ int hi_init() {
return 1;
}
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) {
sc_log_error("Could not populate program memory maps.\n");
@@ -243,7 +267,7 @@ int hi_init() {
// Override existing
if (module_infos) {
free_module_infos(module_infos);
module_infos_free(module_infos);
}
module_infos = infos;
@@ -253,6 +277,6 @@ int hi_init() {
}
void hi_deinit() {
free_module_infos(module_infos);
module_infos_free(module_infos);
sc_log_term();
}

View File

@@ -2,7 +2,7 @@
#include "array/sc_array.h"
#include "files.h"
#include "logger.h"
#include "logger/logger.h"
#include "types.h"
static inline int ptr_in_range(uptr ptr, uptr start, uptr end) {

View File

@@ -1,7 +1,7 @@
#ifndef MEMORY_H_
#define MEMORY_H_
#include "array.h"
#include "array/array.h"
#include "types.h"
#include <assert.h>

View File

@@ -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_

View File

@@ -1,11 +1,26 @@
#include "hi_string.h"
#include "string.h"
#include <stdbool.h>
#include <stdio.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,
size_t nmax) {
size_t nmax) {
FILE *f = fopen(filename, "r");
if (!f) {
@@ -36,9 +51,9 @@ const char *hi_string_from_file_dyn(const char *filename, size_t *nread,
chunk_size *= 2;
char *new_buf = realloc(buf, chunk_size);
if (!new_buf) {
perror("Couldn't realloc memory");
free(buf);
return 0;
perror("Couldn't realloc memory");
free(buf);
return 0;
}
buf = new_buf;
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)) {
char *new_buf = realloc(buf, total_read + 1);
if (!new_buf) {
perror("Couldn't realloc memory");
free(buf);
return 0;
perror("Couldn't realloc memory");
free(buf);
return 0;
}
buf = new_buf;
}

31
src/string/string.h Normal file
View 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_

View File

@@ -1,6 +1,6 @@
#include "symbols.h"
#include "logger.h"
#include "logger/logger.h"
#include "memory.h"
#include "types.h"

View File

@@ -18,6 +18,8 @@ typedef double f64;
typedef uintptr_t uptr;
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_

View File

@@ -8,7 +8,8 @@
int main(int argc, char *argv[]) {
hi_init();
const char *reloadable_modules[] = {"", "libmini.so"};
hi_init(2, reloadable_modules);
int modified = -1;
while (modified != 0) {