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

@@ -50,11 +50,19 @@ namespace {
"Options:\n"
" -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n"
" -m, --module-path PATH Module directory (overrides configuration)\n"
" -M, --metadata-dir PATH Module record directory (overrides configuration)\n"
" -r, --root PATH Target root for package operations (default /)\n"
" -L, --log-level LEVEL FATAL, ERROR, WARN, INFO, or DEBUG\n"
" -l, --list-modules List available modules\n"
" -h, --help Show this help message\n"
"\n"
"Module records:\n"
" --install-module NAME Record what a module reports about itself\n"
" --uninstall-module NAME Remove a module's record and its aliases\n"
" --add-alias MODULE ALIAS Record an alternate name for a module\n"
" --remove-alias ALIAS Remove an alternate name\n"
" --list-aliases [MODULE] List recorded aliases\n"
"\n"
"For module-specific help, use: dpm <module> help\n");
}
@@ -125,7 +133,7 @@ namespace {
dpm_cursor_free(cur);
if (infos.empty()) {
std::printf("No valid modules found in %s\n",
std::printf("No modules found in %s\n",
dpm_get_resolved_module_path(ctx));
return 0;
}
@@ -152,17 +160,70 @@ namespace {
return 0;
}
/**
* @brief Prints the table of recorded aliases
*
* @param ctx The libdpm-core.so context
* @param module NULL for every alias, or a module name to filter by
* @return 0 on success, 1 on failure
*/
int list_aliases(dpm_ctx* ctx, const char* module) {
dpm_alias_cursor* cur = dpm_list_module_aliases(ctx, module);
if (!cur) {
const char* err = dpm_get_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n", err ? err : "alias listing failed");
return 1;
}
std::vector<dpm_alias_info> infos;
dpm_alias_info info;
while (dpm_alias_cursor_next(cur, &info) == 0) {
infos.push_back(info);
}
if (infos.empty()) {
std::printf("No aliases recorded\n");
dpm_alias_cursor_free(cur);
return 0;
}
size_t alias_w = std::strlen("ALIAS");
for (const auto& i : infos) {
alias_w = std::max(alias_w, std::strlen(i.alias));
}
std::printf("%-*s %s\n", static_cast<int>(alias_w), "ALIAS",
"MODULE");
for (const auto& i : infos) {
std::printf("%-*s %s\n", static_cast<int>(alias_w), i.alias,
i.module);
}
dpm_alias_cursor_free(cur);
return 0;
}
} // namespace
int main(int argc, char** argv) {
dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1};
dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1, nullptr};
bool list = false;
/* Holders for --opt=value forms. */
std::string config_dir;
std::string module_path;
std::string metadata_dir;
std::string root;
/* Record operations, each holding what its flag was given. */
const char* install_module = nullptr;
const char* uninstall_module = nullptr;
const char* alias_module = nullptr;
const char* alias_name = nullptr;
const char* remove_alias = nullptr;
const char* list_alias_of = nullptr;
bool aliases_requested = false;
int i = 1;
for (; i < argc; i++) {
const char* arg = argv[i];
@@ -195,6 +256,11 @@ int main(int argc, char** argv) {
if (!v) { return 1; }
module_path = v;
overrides.module_path = module_path.c_str();
} else if (option_matches(arg, "-M", "--metadata-dir", &inline_value)) {
const char* v = take_value();
if (!v) { return 1; }
metadata_dir = v;
overrides.metadata_dir = metadata_dir.c_str();
} else if (option_matches(arg, "-r", "--root", &inline_value)) {
const char* v = take_value();
if (!v) { return 1; }
@@ -214,6 +280,34 @@ int main(int argc, char** argv) {
} else if (std::strcmp(arg, "-l") == 0 ||
std::strcmp(arg, "--list-modules") == 0) {
list = true;
} else if (option_matches(arg, "--install-module", "--install-module",
&inline_value)) {
install_module = take_value();
if (!install_module) { return 1; }
} else if (option_matches(arg, "--uninstall-module",
"--uninstall-module", &inline_value)) {
uninstall_module = take_value();
if (!uninstall_module) { return 1; }
} else if (std::strcmp(arg, "--add-alias") == 0) {
if (i + 2 >= argc) {
std::fprintf(stderr,
"dpm: --add-alias requires MODULE and ALIAS\n");
return 1;
}
alias_module = argv[++i];
alias_name = argv[++i];
} else if (option_matches(arg, "--remove-alias", "--remove-alias",
&inline_value)) {
remove_alias = take_value();
if (!remove_alias) { return 1; }
} else if (std::strcmp(arg, "--list-aliases") == 0) {
aliases_requested = true;
// The module argument is optional, so a following word is
// taken only when it is not another option.
if (i + 1 < argc && argv[i + 1][0] != '-') {
list_alias_of = argv[++i];
}
} else if (std::strcmp(arg, "-h") == 0 ||
std::strcmp(arg, "--help") == 0) {
show_help();
@@ -239,6 +333,56 @@ int main(int argc, char** argv) {
return rc;
}
if (install_module) {
int rc = dpm_install_module(ctx, install_module);
if (rc != 0) {
const char* err = dpm_get_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n",
err ? err : "module could not be installed");
}
dpm_close(ctx);
return rc;
}
if (uninstall_module) {
int rc = dpm_uninstall_module(ctx, uninstall_module);
if (rc != 0) {
const char* err = dpm_get_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n",
err ? err : "module could not be uninstalled");
}
dpm_close(ctx);
return rc;
}
if (alias_module) {
int rc = dpm_add_module_alias(ctx, alias_module, alias_name);
if (rc != 0) {
const char* err = dpm_get_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n",
err ? err : "alias could not be recorded");
}
dpm_close(ctx);
return rc;
}
if (remove_alias) {
int rc = dpm_remove_module_alias(ctx, remove_alias);
if (rc != 0) {
const char* err = dpm_get_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n",
err ? err : "alias could not be removed");
}
dpm_close(ctx);
return rc;
}
if (aliases_requested) {
int rc = list_aliases(ctx, list_alias_of);
dpm_close(ctx);
return rc;
}
if (i >= argc) {
show_help();
dpm_close(ctx);