472 lines
13 KiB
C
472 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 <string.h>
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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));
|
|
strncpy(mod.path, path, sizeof(mod.path) - 1);
|
|
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", total - loaded, 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;
|
|
}
|