Modules determine their own compatibility with the library

A module is built against the system-installed libdpm-core.so and is
responsible for being correct against it. Where it needs to act on the
version it is running under, dpm_core_version() reports that and the
module decides for itself.

dpm_module_core_min() is removed. It was a declaration handed to the
library to enforce on the module's behalf, and enforcement of that kind
belongs nowhere in a library that routes and hosts. The contract is now
three reserved symbols and load validation is two steps: the reserved
symbols resolve, and the version and description probes return
well-formed values.

compare_versions had no remaining caller and is removed; parse_version
stays for the well-formedness probe. The core_too_new fixture went with
the handshake it existed to exercise.
This commit is contained in:
2026-08-15 04:21:58 -04:00
parent 97b39cac6c
commit 17acad2b02
17 changed files with 101 additions and 185 deletions

View File

@@ -4,7 +4,7 @@
*
* Bundles with the dpm binary and libdpm-core.so; used for testing and
* reporting functionality of libdpm-core.so. Implements the full DPM
* module contract: the four reserved symbols, with every capability
* module contract: the three reserved symbols, with every capability
* reached by command string through the entry point.
*
* @copyright Copyright (c) 2026 SILO GROUP LLC
@@ -28,7 +28,6 @@
#include "commands.hpp"
#define INFO_MODULE_VERSION "0.1.0"
#define INFO_CORE_MIN "0.1.0"
/* ------------------------------------------------------------------ */
/* Reserved contract symbols */
@@ -50,14 +49,6 @@ extern "C" const char* dpm_module_description(void)
return "Reports and tests libdpm-core functionality.";
}
/**
* @brief Returns the minimum libdpm-core version this module supports
*/
extern "C" const char* dpm_module_core_min(void)
{
return INFO_CORE_MIN;
}
/**
* @brief Command entry point
*

View File

@@ -1,18 +1,46 @@
/*
* libdpm-core.map — the export list for libdpm-core.so
* libdpm-core.map — the linker version script for libdpm-core.so
*
* Passed to the linker with --version-script. Names under `global` are
* exported; `local: *` hides everything else, so the public C API is the
* only surface a consumer or a loaded module can bind to.
* Passed to the linker with --version-script; see CMakeLists.txt, which
* also lists it as a link dependency so edits force a relink. It does
* two separate jobs.
*
* `global` is every function in include/dpm/core.h and nothing else. A
* new public function is added here in the commit that adds it to the
* header.
*
* The node name stamps each symbol (dpm_open@@DPM_CORE_1.0), so a break
* can ship as a new node while already-linked binaries keep resolving
* the old one. Newest node first. DPM_CORE_0.1 was never released; it is
* the shape a retired node takes.
* 1. It limits the export set.
*
* `global` names every function declared in <dpm/core.h>. `local: *`
* hides everything else, including the template instantiations that
* libstdc++ headers emit with default visibility.
*
*
* 2. It versions the exports.
*
* Every symbol here is stamped with the node name: dpm_open becomes
* dpm_open@@DPM_CORE_1.0. The linker reads that node off the library a
* consumer links against and records it in the consumer's own binary,
* and at startup the dynamic linker verifies the library still provides
* it.
*
* That keeps an already-installed module working after the library
* changes underneath it, which is what makes updating libdpm-core.so
* safe mid-bootstrap.
*
* A breaking change ships as a new node while the old node keeps its
* original definitions, so binaries built against the old node keep
* resolving them. Both live in one .so under one soname, preserving the
* invariant the routing model depends on: exactly one instance of the
* library mapped per process.
*
*
* Changing this file:
*
* - A new public function goes into `global` in the same commit that
* adds it to <dpm/core.h>.
* - A breaking change to an existing function adds a new node above
* DPM_CORE_1.0, leaving this node and its definitions intact.
*
* Newest node first. DPM_CORE_0.1 was never released; it is here as the
* shape a retired node takes.
*/
DPM_CORE_1.0 {
global:

View File

@@ -96,7 +96,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
"dpm_module_execute",
"dpm_module_version",
"dpm_module_description",
"dpm_module_core_min",
};
std::string missing;
@@ -114,28 +113,12 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
return nullptr;
}
auto exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute"));
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 exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute"));
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version"));
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description"));
/* Step 2: minimum-version handshake. */
const char* core_min = core_min_f();
/* Step 2: probe the cheap calls. */
long parsed[3];
if (!core_min || !parse_version(core_min, parsed)) {
reason = "dpm_module_core_min() returned a malformed version";
dlclose(handle);
return nullptr;
}
if (compare_versions(core_min, DPM_CORE_VERSION_STR) > 0) {
reason = std::string("requires libdpm-core >= ") + core_min +
", running libdpm-core is " DPM_CORE_VERSION_STR
" — update libdpm-core";
dlclose(handle);
return nullptr;
}
/* Step 3: probe the cheap calls. */
const char* version = version_f();
if (!version || !parse_version(version, parsed)) {
reason = "dpm_module_version() returned a malformed version";
@@ -154,7 +137,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
mod->handle = handle;
mod->version = version;
mod->description = description;
mod->core_min = core_min;
mod->execute = exec_f;
return mod;
}
@@ -196,7 +178,6 @@ int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)
out->name = mod->name.c_str();
out->version = mod->version.c_str();
out->description = mod->description.c_str();
out->core_min = mod->core_min.c_str();
return 0;
}
@@ -264,7 +245,6 @@ dpm_cursor* dpm_list_modules(dpm_ctx* ctx)
info.name = mod->name.c_str();
info.version = mod->version.c_str();
info.description = mod->description.c_str();
info.core_min = mod->core_min.c_str();
cur->infos.push_back(info);
}

View File

@@ -1,6 +1,6 @@
/**
* @file version.cpp
* @brief X.Y.Z version parsing and comparison
* @brief X.Y.Z version parsing
*
* @copyright Copyright (c) 2026 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
@@ -60,24 +60,4 @@ bool parse_version(const char* s, long out[3])
return true;
}
int compare_versions(const char* a, const char* b)
{
long va[3] = {0, 0, 0};
long vb[3] = {0, 0, 0};
parse_version(a, va);
parse_version(b, vb);
for (int i = 0; i < 3; i++) {
if (va[i] < vb[i]) {
return -1;
}
if (va[i] > vb[i]) {
return 1;
}
}
return 0;
}
} // namespace dpmcore