Route all module interaction through dispatch
A module is addressed by name and command string, and nothing else. Typed API access handed a caller a pointer into the callee's function table, which meant compiling against that module's struct layout — a build-time dependency between modules that the design does not permit. Removing it also removes the manifest, the table magic constant, and the table size field, which existed only to describe and validate those tables. Load validation is now three steps: reserved contract symbols resolve, the minimum-version handshake passes, and the version and description probes return well-formed values. The contract is four reserved symbols, and a module's interface is the command vocabulary it documents. Documentation is brought in line, and artifacts are named exactly: libdpm-core.so for the library, <dpm/core.h> for the header, the dpm binary for the command-line tool.
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
* @file modules.cpp
|
||||
* @brief Module discovery, load-time validation, routing, enumeration
|
||||
*
|
||||
* Implements the load-time enforcement sequence: libdpm-core is the sole
|
||||
* authority on module validity. A module is either fully valid or not
|
||||
* loaded — no partial states.
|
||||
* Implements the load-time enforcement sequence: libdpm-core.so is the
|
||||
* sole authority on module validity. A module is either fully valid or
|
||||
* not loaded — no partial states.
|
||||
*
|
||||
* Dispatch through dpm_execute is the only path into module code, so a
|
||||
* caller never holds compile-time knowledge of what it is calling.
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
* @author Chris Punches <chris.punches@silogroup.org>
|
||||
@@ -51,9 +54,8 @@ void dpm_internal_unload(void* handle)
|
||||
|
||||
namespace {
|
||||
|
||||
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**);
|
||||
using string_fn = const char* (*)(void);
|
||||
using manifest_fn = const dpm_manifest* (*)(void);
|
||||
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**);
|
||||
using string_fn = const char* (*)(void);
|
||||
|
||||
/** dlsym with dlerror() discipline; returns nullptr on any error. */
|
||||
void* resolve(void* handle, const char* symbol)
|
||||
@@ -95,7 +97,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
"dpm_module_version",
|
||||
"dpm_module_description",
|
||||
"dpm_module_core_min",
|
||||
"dpm_module_manifest",
|
||||
};
|
||||
|
||||
std::string missing;
|
||||
@@ -117,7 +118,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version"));
|
||||
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description"));
|
||||
auto core_min_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_core_min"));
|
||||
auto manifest_f = reinterpret_cast<manifest_fn>(resolve(handle, "dpm_module_manifest"));
|
||||
|
||||
/* Step 2: minimum-version handshake. */
|
||||
const char* core_min = core_min_f();
|
||||
@@ -149,62 +149,12 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Step 4: cross-check the manifest. */
|
||||
const dpm_manifest* manifest = manifest_f();
|
||||
if (!manifest) {
|
||||
reason = "dpm_module_manifest() returned NULL";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
if (manifest->count > 0 && !manifest->entries) {
|
||||
reason = "manifest declares entries but the entry table is NULL";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < manifest->count; i++) {
|
||||
const dpm_manifest_entry& entry = manifest->entries[i];
|
||||
|
||||
if (!entry.api_name || !*entry.api_name ||
|
||||
!entry.symbol || !*entry.symbol || entry.table_version < 1) {
|
||||
reason = "manifest entry " + std::to_string(i) + " is malformed";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* table = resolve(handle, entry.symbol);
|
||||
if (!table) {
|
||||
reason = std::string("manifest declares API '") + entry.api_name +
|
||||
"' v" + std::to_string(entry.table_version) +
|
||||
"' at symbol '" + entry.symbol +
|
||||
"' but the symbol does not resolve";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Step 5: table sanity. */
|
||||
auto* header = static_cast<const dpm_api_table_header*>(table);
|
||||
if (header->magic != DPM_API_TABLE_MAGIC) {
|
||||
reason = std::string("API table '") + entry.symbol +
|
||||
"' has a bad magic constant";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
if (header->size < sizeof(dpm_api_table_header)) {
|
||||
reason = std::string("API table '") + entry.symbol +
|
||||
"' reports an impossible size";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto mod = std::make_unique<dpm_module>();
|
||||
mod->name = name;
|
||||
mod->handle = handle;
|
||||
mod->version = version;
|
||||
mod->description = description;
|
||||
mod->core_min = core_min;
|
||||
mod->manifest = manifest;
|
||||
mod->execute = exec_f;
|
||||
return mod;
|
||||
}
|
||||
@@ -250,34 +200,6 @@ int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void* dpm_get_api(dpm_ctx* ctx, dpm_module* mod,
|
||||
const char* api_name, int table_version)
|
||||
{
|
||||
if (!ctx || !mod || !api_name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < mod->manifest->count; i++) {
|
||||
const dpm_manifest_entry& entry = mod->manifest->entries[i];
|
||||
if (entry.table_version == table_version &&
|
||||
std::string(entry.api_name) == api_name) {
|
||||
dlerror();
|
||||
void* table = dlsym(mod->handle, entry.symbol);
|
||||
if (dlerror() != nullptr || !table) {
|
||||
dpmcore::set_error(ctx, std::string("API table symbol '") +
|
||||
entry.symbol + "' vanished after load");
|
||||
return nullptr;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
dpmcore::set_error(ctx, std::string("module '") + mod->name +
|
||||
"' does not provide API '" + api_name +
|
||||
"' v" + std::to_string(table_version));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
|
||||
int argc, char** argv)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user