Module records and aliases

Installation records what a module reports about itself. dpm_install_module
opens a module once, writes its version and description to a .meta file in
/var/lib/dpm/metadata/, and records the alternate names it declared in
modules.aliases beside it. dpm_uninstall_module removes both, leaving the
module file in place.

dpm_list_modules reads those records and opens no module. A module with no
record lists with a version of <uninstalled> and still loads when a caller
names it. An unreadable or absent metadata directory costs the listing its
detail and costs nothing else.

Aliases give a module alternate names, declared through the new
dpm_module_aliases contract symbol or added with dpm_add_module_alias. A
name is recorded once: one already serving as an alias, or belonging to an
installed module, is refused rather than repointed. dpm_require matches a
name against the installed modules, then the alias table, then the module
path.

The metadata directory is a fifth override field and the -M flag, and
[modules] metadata in core.conf.

The test suite is four binaries covering context, modules, records, and
aliases, each a ctest case of its own, alongside the CLI cases.
This commit is contained in:
2026-08-24 03:51:39 -04:00
parent 71d409b5c7
commit 0291e61fd8
32 changed files with 2147 additions and 290 deletions

View File

@@ -96,6 +96,18 @@ typedef struct dpm_module dpm_module;
*/
typedef struct dpm_cursor dpm_cursor;
/**
* @brief A cursor over recorded module aliases
*
* Obtained from dpm_list_module_aliases(), advanced with
* dpm_alias_cursor_next(), and released with dpm_alias_cursor_free().
*
* The cursor holds its own copy of what it reports, so it stays readable
* after the alias table is changed, and releasing it leaves the table
* untouched.
*/
typedef struct dpm_alias_cursor dpm_alias_cursor;
/* ------------------------------------------------------------------ */
/* Log levels */
/* ------------------------------------------------------------------ */
@@ -145,6 +157,9 @@ typedef struct dpm_open_overrides {
/** -1 selects the configured value; otherwise a DPM_LOG_* level. */
int log_level;
/** NULL selects the configured value, then the built-in default. */
const char* metadata_dir;
} dpm_open_overrides;
/* ------------------------------------------------------------------ */
@@ -168,6 +183,20 @@ typedef struct dpm_module_info {
const char* description;
} dpm_module_info;
/**
* @brief One recorded alias
*
* Filled by dpm_alias_cursor_next(). The string pointers remain valid
* until the cursor is released.
*/
typedef struct dpm_alias_info {
/** The alternate name. */
const char* alias;
/** The module that name resolves to. */
const char* module;
} dpm_alias_info;
/* ------------------------------------------------------------------ */
/* Context lifecycle */
/* ------------------------------------------------------------------ */
@@ -209,10 +238,15 @@ void dpm_close(dpm_ctx* ctx);
/**
* @brief Loads and returns a validated module
*
* Resolves the named module in the module path and runs the full
* load-time validation sequence if it is not already loaded in this
* context. Modules are loaded at most once per context; repeated
* calls return the same handle.
* Resolves the name to a module file and runs the full load-time
* validation sequence if it is not already loaded in this context.
* Modules are loaded at most once per context; repeated calls return
* the same handle.
*
* The name is resolved against the installed modules first, then the
* alias table, then the module path directly. Reaching the module path
* means the module is not installed, which is logged and does not stop
* the load.
*
* Version compatibility is the caller's judgement: read the loaded
* module's version with dpm_get_module_info() and decide whether it is
@@ -267,18 +301,119 @@ DPM_PUBLIC_ABI_EXPORT
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
int argc, char** argv);
/* ------------------------------------------------------------------ */
/* Installation */
/* ------------------------------------------------------------------ */
/**
* @brief Records what a module reports about itself
*
* Loads the named module once, reads its version, description, and
* declared aliases, and writes them into the metadata directory. From
* then on dpm_list_modules() reports that module from the record and
* opens nothing.
*
* Each declared alias is added when the name is free. A name already
* taken keeps its existing meaning and the installation continues.
*
* @param ctx The libdpm-core.so context
* @param name The module name, its filename minus .so
* @return 0 on success, nonzero on failure with the reason available
* from dpm_get_last_error()
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_install_module(dpm_ctx* ctx, const char* name);
/**
* @brief Removes a module's record and every alias resolving to it
*
* Leaves the module file itself in place. The module is thereafter
* reported as uninstalled by dpm_list_modules(), and dpm_require() still
* loads it by its own name.
*
* @param ctx The libdpm-core.so context
* @param name The module name
* @return 0 on success, nonzero on failure with the reason available
* from dpm_get_last_error()
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_uninstall_module(dpm_ctx* ctx, const char* name);
/* ------------------------------------------------------------------ */
/* Aliases */
/* ------------------------------------------------------------------ */
/**
* @brief Records an alternate name for a module
*
* A name is recorded once. A name already serving as an alias, or
* already belonging to an installed module, is refused, so an existing
* route to a module is never replaced by a later one. Changing where an
* alias points is a removal followed by an addition.
*
* @param ctx The libdpm-core.so context
* @param module The module the name resolves to
* @param alias The alternate name
* @return 0 on success, nonzero on failure with the reason available
* from dpm_get_last_error()
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_add_module_alias(dpm_ctx* ctx, const char* module, const char* alias);
/**
* @brief Removes an alternate name
*
* @param ctx The libdpm-core.so context
* @param alias The alternate name, which identifies the entry on its own
* @return 0 on success, nonzero on failure with the reason available
* from dpm_get_last_error()
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_remove_module_alias(dpm_ctx* ctx, const char* alias);
/**
* @brief Enumerates recorded aliases
*
* @param ctx The libdpm-core.so context
* @param module NULL for every alias on the system, or a module name for
* the aliases resolving to it
* @return A cursor over the matching aliases, or NULL on failure
*/
DPM_PUBLIC_ABI_EXPORT
dpm_alias_cursor* dpm_list_module_aliases(dpm_ctx* ctx, const char* module);
/**
* @brief Advances an alias cursor
*
* @param cur The cursor from dpm_list_module_aliases()
* @param out Receives the next alias and the module it resolves to
* @return 0 while entries remain; nonzero at end
*/
DPM_PUBLIC_ABI_EXPORT
int dpm_alias_cursor_next(dpm_alias_cursor* cur, dpm_alias_info* out);
/**
* @brief Releases an alias cursor
*
* @param cur The cursor to release; NULL is a no-op
*/
DPM_PUBLIC_ABI_EXPORT
void dpm_alias_cursor_free(dpm_alias_cursor* cur);
/* ------------------------------------------------------------------ */
/* Enumeration */
/* ------------------------------------------------------------------ */
/**
* @brief Enumerates the valid modules in the module path
* @brief Enumerates the modules in the module path
*
* Scans the module path and validates each candidate .so; failures
* are logged and excluded from the results.
* Reports each installed module from its record, and each module
* without one with a version of "&lt;uninstalled&gt;" and an empty
* description. No module is opened, so nothing in the module path is
* executed to produce a listing.
*
* @param ctx The libdpm-core.so context
* @return A cursor over all valid modules, or NULL on an unreadable
* @return A cursor over the modules present, or NULL on an unreadable
* module path
*/
DPM_PUBLIC_ABI_EXPORT
@@ -396,10 +531,16 @@ const char* dpm_get_last_error(dpm_ctx* ctx);
* int argc, char** argv);
* const char* dpm_module_version(void);
* const char* dpm_module_description(void);
* const char* dpm_module_aliases(void);
*
* dpm_module_execute is the only entry through which a module performs
* work. The other two are what it reports about itself; the library
* reads both at load and serves them through dpm_get_module_info.
* work. The other three are what it reports about itself; the library
* reads them at load, serves the version and description through
* dpm_get_module_info, and records the aliases at installation.
*
* dpm_module_aliases returns a comma-separated list of alternate names
* the module answers to, or NULL to declare none. NULL is an answer; the
* symbol's absence is a contract violation and the module is refused.
*/
#ifdef __cplusplus