DVX_GUI/loader/loaderMain.c

479 lines
13 KiB
C

// loaderMain.c -- DVX bootstrap loader entry point
//
// Loads all DXE modules from two directories:
// libs/ *.lib -- core libraries (libtasks, libdvx, dvxshell)
// widgets/ *.wgt -- widget type plugins (box, button, listview, etc.)
//
// Each module may have a .dep file (same base name, .dep extension)
// listing base names of modules that must be loaded before it.
// The loader resolves the dependency graph and loads in topological
// order. After loading, any module that exports wgtRegister() has
// it called. Finally, the loader finds and calls shellMain().
#include "dvxPlatform.h"
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
// Route stb_ds allocations through the tracking wrappers so that
// arrput/arrfree in DXE code is tracked per-app.
extern void *dvxRealloc(void *ptr, size_t size);
extern void dvxFree(void *ptr);
#define STBDS_REALLOC(c, p, s) dvxRealloc((p), (s))
#define STBDS_FREE(c, p) dvxFree(p)
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"
// ============================================================
// Constants
// ============================================================
#define LIBS_DIR "LIBS"
#define WIDGET_DIR "WIDGETS"
#define LOG_PATH "dvx.log"
// ============================================================
// dvxLog -- append a line to dvx.log
// ============================================================
//
// Global logging function exported to all DXE modules.
// Opens/closes the file per-write so it's never held open.
void dvxLog(const char *fmt, ...) {
FILE *f = fopen(LOG_PATH, "a");
if (!f) {
return;
}
va_list ap;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fprintf(f, "\n");
fclose(f);
}
// ============================================================
// Module entry for dependency resolution
// ============================================================
typedef struct {
char path[260];
char baseName[16];
char **deps;
bool loaded;
void *handle;
} ModuleT;
// ============================================================
// Prototypes
// ============================================================
static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods);
static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize);
static void *findSymbol(void **handles, const char *symbol);
static void freeMods(ModuleT *mods);
static void **loadAllModules(void);
static void loadInOrder(ModuleT *mods);
static void logAndReadDeps(ModuleT *mods);
static void readDeps(ModuleT *mod);
static void scanDir(const char *dirPath, const char *ext, ModuleT **mods);
// ============================================================
// extractBaseName -- strip directory and extension, lowercase
// ============================================================
static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize) {
// Find last directory separator
const char *start = path;
const char *p = path;
while (*p) {
if (*p == '/' || *p == '\\') {
start = p + 1;
}
p++;
}
// Copy up to the extension
int32_t extLen = strlen(ext);
int32_t len = strlen(start);
if (len > extLen && strcasecmp(start + len - extLen, ext) == 0) {
len -= extLen;
}
if (len >= outSize) {
len = outSize - 1;
}
for (int32_t i = 0; i < len; i++) {
out[i] = tolower((unsigned char)start[i]);
}
out[len] = '\0';
}
// ============================================================
// readDeps -- parse .dep file for a module
// ============================================================
//
// The .dep file has the same path as the module but with a .dep
// extension. Each line is a dependency base name. Empty lines
// and lines starting with # are ignored.
static void readDeps(ModuleT *mod) {
// Build dep file path: replace extension with .dep
char depPath[260];
strncpy(depPath, mod->path, sizeof(depPath) - 1);
depPath[sizeof(depPath) - 1] = '\0';
char *dot = strrchr(depPath, '.');
if (!dot) {
return;
}
strcpy(dot, ".dep");
FILE *f = fopen(depPath, "r");
if (!f) {
return;
}
char line[64];
while (fgets(line, sizeof(line), f)) {
// Strip \r and \n
int32_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
// Skip empty lines and comments
if (len == 0 || line[0] == '#') {
continue;
}
// Lowercase the dep name for case-insensitive matching
for (int32_t i = 0; i < len; i++) {
line[i] = tolower((unsigned char)line[i]);
}
arrput(mod->deps, strdup(line));
}
fclose(f);
}
// ============================================================
// scanDir -- recursively find modules with a given extension
// ============================================================
static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) {
DIR *dir = opendir(dirPath);
if (!dir) {
return;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
const char *name = ent->d_name;
// Skip . and ..
if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) {
continue;
}
char path[260];
snprintf(path, sizeof(path), "%s/%s", dirPath, name);
// Check for matching extension
int32_t nameLen = strlen(name);
int32_t extLen = strlen(ext);
if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) {
ModuleT mod;
memset(&mod, 0, sizeof(mod));
snprintf(mod.path, sizeof(mod.path), "%s", path);
extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName));
arrput(*mods, mod);
continue;
}
// Recurse into subdirectories
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
scanDir(path, ext, mods);
}
}
closedir(dir);
}
// ============================================================
// allDepsLoaded -- check if a module's dependencies are satisfied
// ============================================================
//
// A dep is satisfied if either:
// 1. No module with that base name exists (external, assumed OK)
// 2. The module with that base name is already loaded
static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods) {
for (int32_t d = 0; d < arrlen(mod->deps); d++) {
for (int32_t j = 0; j < arrlen(mods); j++) {
if (strcasecmp(mods[j].baseName, mod->deps[d]) == 0) {
if (!mods[j].loaded) {
return false;
}
break;
}
}
}
return true;
}
// ============================================================
// loadInOrder -- topological sort and load
// ============================================================
//
// Repeatedly scans the module list for entries whose dependencies
// are all satisfied, loads them, and marks them done. Stops when
// all modules are loaded or no progress can be made (circular dep).
static void loadInOrder(ModuleT *mods) {
typedef void (*RegFnT)(void);
int32_t total = arrlen(mods);
int32_t loaded = 0;
bool progress;
do {
progress = false;
for (int32_t i = 0; i < total; i++) {
if (mods[i].loaded) {
continue;
}
if (!allDepsLoaded(&mods[i], mods)) {
continue;
}
dvxLog("Loading: %s", mods[i].path);
mods[i].handle = dlopen(mods[i].path, RTLD_GLOBAL);
if (!mods[i].handle) {
const char *err = dlerror();
dvxLog(" FAILED: %s", err ? err : "(unknown)");
mods[i].loaded = true;
loaded++;
progress = true;
continue;
}
RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister");
if (regFn) {
regFn();
}
mods[i].loaded = true;
loaded++;
progress = true;
}
} while (progress && loaded < total);
if (loaded < total) {
fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", (int)(total - loaded), (int)total);
for (int32_t i = 0; i < total; i++) {
if (!mods[i].loaded) {
fprintf(stderr, " %s\n", mods[i].path);
}
}
}
}
// ============================================================
// loadAllModules -- scan libs/ and widgets/, load in dep order
// ============================================================
//
// Returns a stb_ds dynamic array of dlopen handles.
static void logAndReadDeps(ModuleT *mods) {
dvxLog("Discovered %d modules:", arrlen(mods));
for (int32_t i = 0; i < arrlen(mods); i++) {
dvxLog(" [%d] %s (base: %s)", i, mods[i].path, mods[i].baseName);
}
for (int32_t i = 0; i < arrlen(mods); i++) {
readDeps(&mods[i]);
if (arrlen(mods[i].deps) > 0) {
dvxLog(" %s deps:", mods[i].baseName);
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
dvxLog(" %s", mods[i].deps[d]);
}
}
}
}
static void freeMods(ModuleT *mods) {
for (int32_t i = 0; i < arrlen(mods); i++) {
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
free(mods[i].deps[d]);
}
arrfree(mods[i].deps);
}
arrfree(mods);
}
static void **loadAllModules(void) {
void **handles = NULL;
// Phase 1: load libraries in dependency order (libs before widgets)
ModuleT *libs = NULL;
scanDir(LIBS_DIR, ".lib", &libs);
logAndReadDeps(libs);
loadInOrder(libs);
for (int32_t i = 0; i < arrlen(libs); i++) {
if (libs[i].handle) {
arrput(handles, libs[i].handle);
}
}
freeMods(libs);
// Phase 2: load widgets in dependency order (all libs already loaded)
ModuleT *widgets = NULL;
scanDir(WIDGET_DIR, ".wgt", &widgets);
logAndReadDeps(widgets);
loadInOrder(widgets);
for (int32_t i = 0; i < arrlen(widgets); i++) {
if (widgets[i].handle) {
arrput(handles, widgets[i].handle);
}
}
freeMods(widgets);
return handles;
}
// ============================================================
// findSymbol -- search loaded handles for a symbol
// ============================================================
static void *findSymbol(void **handles, const char *symbol) {
for (int32_t i = 0; i < arrlen(handles); i++) {
void *sym = dlsym(handles[i], symbol);
if (sym) {
return sym;
}
}
return NULL;
}
// ============================================================
// main
// ============================================================
int main(int argc, char *argv[]) {
// Change to the directory containing the executable so relative
// paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
char exeDir[260];
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
exeDir[sizeof(exeDir) - 1] = '\0';
char *sep = platformPathDirEnd(exeDir);
if (sep) {
*sep = '\0';
platformChdir(exeDir);
}
// Truncate log, then use append-per-write
FILE *logInit = fopen(LOG_PATH, "w");
if (logInit) {
fclose(logInit);
}
// Suppress Ctrl+C before anything else
platformInit();
dvxLog("DVX Loader starting...");
// Register platform + libc/libm/runtime symbols for DXE resolution
platformRegisterDxeExports();
dvxLog("Platform exports registered.");
// Load all modules from libs/ and widgets/ in dependency order.
// Each module may have a .dep file specifying load-before deps.
// Widget modules that export wgtRegister() get it called.
void **handles = loadAllModules();
if (!handles || arrlen(handles) == 0) {
fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR);
arrfree(handles);
return 1;
}
// Find and call shellMain from whichever module exports it
typedef int (*ShellMainFnT)(int, char **);
ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain");
if (!shellMain) {
dvxLog("ERROR: No module exports shellMain");
for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
dlclose(handles[i]);
}
arrfree(handles);
return 1;
}
int result = shellMain(argc, argv);
// Clean up in reverse load order
for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
dlclose(handles[i]);
}
arrfree(handles);
return result;
}