Modules can report why they failed
A module had one channel back to its caller: the int returned from dpm_module_execute, handed through by dpm_execute. Any detail behind that number could only reach a log, so a caller wanting the reason had to read output rather than ask for it. dpm_set_last_error joins the exported API. A module records its reason on the context handed to its entry point, which belongs to the caller, and the caller reads it back with dpm_get_last_error. The two accessors on the context now say what they do to it: dpm_module_path becomes dpm_get_resolved_module_path, naming the value it reports rather than the setting it came from, and dpm_last_error becomes dpm_get_last_error, pairing with the setter. Path normalization moves to sanitizers.cpp, which holds the conversions that put a value written by a person into the single form the library stores it in.
This commit is contained in:
@@ -25,6 +25,7 @@ add_library(dpm-core SHARED
|
||||
src/core/context.cpp
|
||||
src/core/logging.cpp
|
||||
src/core/modules.cpp
|
||||
src/core/sanitizers.cpp
|
||||
src/core/version.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ The root override is what makes chroot builds, image assembly, and sysroot manag
|
||||
dpm_module* mod = dpm_require(ctx, "mymodule");
|
||||
```
|
||||
|
||||
libdpm-core.so validates the module completely at load; a handle is returned only for a fully valid module. NULL means the module is absent or invalid — `dpm_last_error`(ctx) carries the precise reason. Modules load at most once per context; repeated calls return the same handle.
|
||||
libdpm-core.so validates the module completely at load; a handle is returned only for a fully valid module. NULL means the module is absent or invalid — `dpm_get_last_error`(ctx) carries the precise reason. Modules load at most once per context; repeated calls return the same handle.
|
||||
|
||||
`dpm_module_info_of` reports what the library saw in the loaded module:
|
||||
|
||||
@@ -94,12 +94,13 @@ The cursor covers every valid module in the module path; invalid candidates are
|
||||
- `dpm_module_info_of`(ctx, mod, out)** — the name, version, and description read from a loaded module.
|
||||
- `dpm_config_get`(ctx, module, section, key)** — a value from a module's configuration namespace, or NULL if unset.
|
||||
- `dpm_log`(ctx, level, message)** — writes to the context's configured log targets; levels are `DPM_LOG_FATAL` through `DPM_LOG_DEBUG`.
|
||||
- `dpm_module_path`(ctx)** — the resolved module directory.
|
||||
- `dpm_last_error`(ctx)** — a human-readable description of the most recent failure on the context, or NULL.
|
||||
- `dpm_get_resolved_module_path`(ctx)** — the resolved module directory.
|
||||
- `dpm_set_last_error`(ctx, msg) — records a failure reason on the context; what a module calls to explain a nonzero return.
|
||||
- `dpm_get_last_error`(ctx)** — a human-readable description of the most recent failure on the context, or NULL.
|
||||
|
||||
## Ownership and Errors
|
||||
|
||||
Strings returned by the library are owned by the context (or by the module that produced them) and remain valid until `dpm_close`; callers never free them. Functions returning int use 0 for success. Functions returning pointers use NULL for failure, with detail available from `dpm_last_error`.
|
||||
Strings returned by the library are owned by the context (or by the module that produced them) and remain valid until `dpm_close`; callers never free them. Functions returning int use 0 for success. Functions returning pointers use NULL for failure, with detail available from `dpm_get_last_error`.
|
||||
|
||||
## Complete Example
|
||||
|
||||
@@ -116,7 +117,7 @@ int main(void) {
|
||||
|
||||
dpm_module* mod = dpm_require(ctx, "info");
|
||||
if (!mod) {
|
||||
fprintf(stderr, "%s\n", dpm_last_error(ctx));
|
||||
fprintf(stderr, "%s\n", dpm_get_last_error(ctx));
|
||||
dpm_close(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -92,10 +92,13 @@ Returns the configured value for `key` in `section` of the named module's config
|
||||
`void dpm_log(dpm_ctx* ctx, int level, const char* message)`
|
||||
Writes `message` at `level` (FATAL=0, ERROR=1, WARN=2, INFO=3, DEBUG=4) to the context's configured log targets (console and/or file). Messages above the configured level are dropped. NULL message is a no-op.
|
||||
|
||||
`const char* dpm_module_path(dpm_ctx* ctx)`
|
||||
`const char* dpm_get_resolved_module_path(dpm_ctx* ctx)`
|
||||
Returns the resolved module directory path for this context.
|
||||
|
||||
`const char* dpm_last_error(dpm_ctx* ctx)`
|
||||
`void dpm_set_last_error(dpm_ctx* ctx, const char* msg)`
|
||||
Records `msg` as the most recent failure on this context; the string is copied. A module explains a nonzero return by recording the reason here, using the context handed to its entry point — the caller's own — so that the caller reads back what the module wrote. The context holds one reason at a time.
|
||||
|
||||
`const char* dpm_get_last_error(dpm_ctx* ctx)`
|
||||
Returns a human-readable description of the most recent failure recorded on this context, or NULL if none. Overwritten by the next failing call on the same context.
|
||||
|
||||
## Module Contract
|
||||
@@ -103,7 +106,7 @@ Returns a human-readable description of the most recent failure recorded on this
|
||||
A module is one .so in the module directory. It includes `<dpm/core.h>`, links `-ldpm-core`, and exports the following reserved symbols as extern "C". Returned strings are static or module-owned, non-NULL, and valid for the lifetime of the loaded module; the library and consumers never free them.
|
||||
|
||||
`int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv)`
|
||||
The module's command entry point, and the only entry through which it performs work. `ctx` is the host context that dispatched the call — the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_module_path`, ...) through it, and reaches peer modules through it as well. `command` is the subcommand name (equal to argv[0] when argc > 0); argc/argv are the remaining CLI-style arguments. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup.
|
||||
The module's command entry point, and the only entry through which it performs work. `ctx` is the host context that dispatched the call — the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_get_resolved_module_path`, ...) through it, and reaches peer modules through it as well. `command` is the subcommand name (equal to argv[0] when argc > 0); argc/argv are the remaining CLI-style arguments. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup.
|
||||
|
||||
`const char* dpm_module_version(void)`
|
||||
Returns the module's own version as an X.Y.Z string. Must be constant for the life of the module. This is the value the library reports to consumers, and the value they judge compatibility against.
|
||||
|
||||
@@ -7,7 +7,7 @@ A DPM module is one shared object in the module directory. libdpm-core.so loads
|
||||
A module includes `<dpm/core.h>`, links `-ldpm-core`, and exports the following symbols as extern "C". All returned strings must be non-NULL, static or module-owned, and valid for the lifetime of the loaded module; callers never free them.
|
||||
|
||||
`int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv)`
|
||||
The command entry point, and the only entry through which the module performs work. `ctx` is the host context that dispatched the call; the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_module_path`, ...) through it, and reaches peer modules through it as well. `command` is the subcommand name, equal to argv[0] when argc > 0. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup.
|
||||
The command entry point, and the only entry through which the module performs work. `ctx` is the host context that dispatched the call; the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_get_resolved_module_path`, ...) through it, and reaches peer modules through it as well. `command` is the subcommand name, equal to argv[0] when argc > 0. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. Where a nonzero return needs explaining, record the reason with `dpm_set_last_error` immediately before returning, and the caller reads it back with `dpm_get_last_error`. It must be callable immediately after load with no other setup.
|
||||
|
||||
`const char* dpm_module_version(void)`
|
||||
The module's own version as an X.Y.Z string. libdpm-core.so reports this value to consumers, and each consumer decides for itself whether the version suits it.
|
||||
@@ -48,7 +48,7 @@ int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv)
|
||||
{
|
||||
dpm_module* peer = dpm_require(ctx, "othermodule");
|
||||
if (!peer) {
|
||||
dpm_log(ctx, DPM_LOG_ERROR, dpm_last_error(ctx));
|
||||
dpm_log(ctx, DPM_LOG_ERROR, dpm_get_last_error(ctx));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ The target root override is what makes chroot builds, image assembly, and sysroo
|
||||
|
||||
The context owns everything it hands out. Every string a caller receives stays valid until `dpm_close`, and callers never free anything.
|
||||
|
||||
**Acquiring a module.** `dpm_require` resolves a module by name, validates it completely, and returns a handle — or returns NULL, with `dpm_last_error` carrying the precise reason. Modules load at most once per context, and repeated calls return the same handle.
|
||||
**Acquiring a module.** `dpm_require` resolves a module by name, validates it completely, and returns a handle — or returns NULL, with `dpm_get_last_error` carrying the precise reason. Modules load at most once per context, and repeated calls return the same handle.
|
||||
|
||||
**Reading what the library saw.** `dpm_module_info_of` fills in a module's name, version, and description. Those values are reported, and no conclusion is drawn from them.
|
||||
|
||||
@@ -59,7 +59,7 @@ The context owns everything it hands out. Every string a caller receives stays v
|
||||
|
||||
**Enumerating.** `dpm_list_modules` yields a cursor over every valid module, which is what backs the listing the `dpm` binary prints.
|
||||
|
||||
**Services.** A module reaches the library through the context that dispatched the call: `dpm_log` to write a message, `dpm_config_get` to read a value from its own configuration namespace, `dpm_module_path` to learn where modules live, `dpm_core_version` to learn the library's version. A module needs no file handling and no logging machinery of its own.
|
||||
**Services.** A module reaches the library through the context that dispatched the call: `dpm_log` to write a message, `dpm_config_get` to read a value from its own configuration namespace, `dpm_get_resolved_module_path` to learn where modules live, `dpm_core_version` to learn the library's version, `dpm_set_last_error` to record why it failed. A module needs no file handling and no logging machinery of its own.
|
||||
|
||||
## How a Module Reaches Another Module
|
||||
|
||||
@@ -70,7 +70,7 @@ int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv)
|
||||
{
|
||||
dpm_module* peer = dpm_require(ctx, "othermodule");
|
||||
if (!peer) {
|
||||
dpm_log(ctx, DPM_LOG_ERROR, dpm_last_error(ctx));
|
||||
dpm_log(ctx, DPM_LOG_ERROR, dpm_get_last_error(ctx));
|
||||
return 1;
|
||||
}
|
||||
return dpm_execute(ctx, peer, "somecommand", argc, argv);
|
||||
|
||||
@@ -221,7 +221,7 @@ void dpm_close(dpm_ctx* ctx);
|
||||
* @param ctx The libdpm-core.so context
|
||||
* @param name The module name (its filename minus .so)
|
||||
* @return A module handle owned by the context, or NULL on failure
|
||||
* with the precise reason retrievable via dpm_last_error()
|
||||
* with the precise reason retrievable via dpm_get_last_error()
|
||||
*/
|
||||
DPM_PUBLIC_ABI_EXPORT
|
||||
dpm_module* dpm_require(dpm_ctx* ctx, const char* name);
|
||||
@@ -356,7 +356,24 @@ void dpm_log(dpm_ctx* ctx, int level, const char* message);
|
||||
* @return The module directory path this context resolved
|
||||
*/
|
||||
DPM_PUBLIC_ABI_EXPORT
|
||||
const char* dpm_module_path(dpm_ctx* ctx);
|
||||
const char* dpm_get_resolved_module_path(dpm_ctx* ctx);
|
||||
|
||||
/**
|
||||
* @brief Records a failure reason on the context
|
||||
*
|
||||
* A module reports why it failed by recording the reason here and
|
||||
* returning nonzero, which carries detail its return code cannot.
|
||||
*
|
||||
* The context holds one reason at a time, so the most recent write is
|
||||
* what dpm_get_last_error reports. Record the reason immediately before
|
||||
* returning, so that later work does not replace it.
|
||||
*
|
||||
* @param ctx The libdpm-core.so context; NULL is a no-op
|
||||
* @param msg The failure description, copied into the context; NULL is
|
||||
* a no-op
|
||||
*/
|
||||
DPM_PUBLIC_ABI_EXPORT
|
||||
void dpm_set_last_error(dpm_ctx* ctx, const char* msg);
|
||||
|
||||
/**
|
||||
* @brief Returns the most recent failure recorded on the context
|
||||
@@ -366,7 +383,7 @@ const char* dpm_module_path(dpm_ctx* ctx);
|
||||
* NULL if none; overwritten by the next failing call
|
||||
*/
|
||||
DPM_PUBLIC_ABI_EXPORT
|
||||
const char* dpm_last_error(dpm_ctx* ctx);
|
||||
const char* dpm_get_last_error(dpm_ctx* ctx);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Module contract (implemented by modules, called by libdpm-core.so) */
|
||||
|
||||
@@ -89,5 +89,5 @@ namespace dpm_core {
|
||||
* @param ctx The libdpm-core.so context; NULL is a no-op
|
||||
* @param msg The failure description
|
||||
*/
|
||||
void set_error(dpm_ctx* ctx, const std::string& msg);
|
||||
void set_last_error(dpm_ctx* ctx, const std::string& msg);
|
||||
} // namespace dpm_core
|
||||
|
||||
35
include/internal/sanitizers.hpp
Normal file
35
include/internal/sanitizers.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file sanitizers.hpp
|
||||
* @brief Normalizing a value into the shape the library stores it in
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
* @author Chris Punches <chris.punches@silogroup.org>
|
||||
*
|
||||
* Part of the Dark Horse Linux Package Manager (DPM)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dpm_core {
|
||||
/**
|
||||
* @brief Appends a trailing slash when one is absent
|
||||
*
|
||||
* @param s The path to normalize
|
||||
* @return The path, ending in a slash unless it is empty
|
||||
*/
|
||||
std::string with_trailing_slash(std::string s);
|
||||
} // namespace dpm_core
|
||||
@@ -166,7 +166,7 @@ int cmd_config(dpm_ctx* ctx) {
|
||||
configured_msg += configured_path ? configured_path : "not configured";
|
||||
dpm_log(ctx, DPM_LOG_INFO, configured_msg.c_str());
|
||||
|
||||
const char* active_path = dpm_module_path(ctx);
|
||||
const char* active_path = dpm_get_resolved_module_path(ctx);
|
||||
std::string active_msg = " Active module path: ";
|
||||
active_msg += active_path ? active_path : "unknown";
|
||||
dpm_log(ctx, DPM_LOG_INFO, active_msg.c_str());
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace {
|
||||
int list_modules(dpm_ctx* ctx) {
|
||||
dpm_cursor* cur = dpm_list_modules(ctx);
|
||||
if (!cur) {
|
||||
const char* err = dpm_last_error(ctx);
|
||||
const char* err = dpm_get_last_error(ctx);
|
||||
std::fprintf(stderr, "dpm: %s\n", err ? err : "module listing failed");
|
||||
return 1;
|
||||
}
|
||||
@@ -125,7 +125,8 @@ namespace {
|
||||
dpm_cursor_free(cur);
|
||||
|
||||
if (infos.empty()) {
|
||||
std::printf("No valid modules found in %s\n", dpm_module_path(ctx));
|
||||
std::printf("No valid modules found in %s\n",
|
||||
dpm_get_resolved_module_path(ctx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
dpm_module* mod = dpm_require(ctx, module_name);
|
||||
if (!mod) {
|
||||
const char* err = dpm_last_error(ctx);
|
||||
const char* err = dpm_get_last_error(ctx);
|
||||
std::fprintf(stderr, "dpm: %s\n",
|
||||
err ? err : "module could not be loaded");
|
||||
dpm_close(ctx);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "internal/context.hpp"
|
||||
|
||||
#include "internal/conf.hpp"
|
||||
#include "internal/sanitizers.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <new>
|
||||
@@ -30,12 +31,12 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief Helpers private to this translation unit
|
||||
* @brief Values private to this translation unit
|
||||
*
|
||||
* The built-in defaults and the path normalization applied to the
|
||||
* directories a context resolves. An unnamed namespace gives them
|
||||
* internal linkage, so they are unreachable from the library's other
|
||||
* files and cannot collide with a same-named helper in one of them.
|
||||
* The settings a context falls back on when neither an override nor a
|
||||
* configuration file names one. An unnamed namespace gives them internal
|
||||
* linkage, so they are unreachable from the library's other files and
|
||||
* cannot collide with a same-named value in one of them.
|
||||
*/
|
||||
namespace {
|
||||
/** Configuration directory used when no override is supplied. */
|
||||
@@ -46,24 +47,6 @@ namespace {
|
||||
|
||||
/** Log file used when configuration enables logging without naming a path. */
|
||||
const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log";
|
||||
|
||||
/**
|
||||
* @brief Appends a trailing slash when one is absent
|
||||
*
|
||||
* Directory paths are stored with a trailing slash so that callers
|
||||
* concatenating a filename onto them produce a valid path without
|
||||
* checking the separator themselves.
|
||||
*
|
||||
* @param s The path to normalize
|
||||
* @return The path, ending in a slash unless it is empty
|
||||
*/
|
||||
std::string with_trailing_slash(std::string s) {
|
||||
if (!s.empty() && s.back() != '/') {
|
||||
s += '/';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace dpm_core {
|
||||
@@ -73,7 +56,7 @@ namespace dpm_core {
|
||||
* Overwrites whatever reason was recorded previously, so the context
|
||||
* carries the most recent failure and nothing older.
|
||||
*/
|
||||
void set_error(dpm_ctx* ctx, const std::string& msg) {
|
||||
void set_last_error(dpm_ctx* ctx, const std::string& msg) {
|
||||
if (ctx) {
|
||||
ctx->last_error = msg;
|
||||
}
|
||||
@@ -118,7 +101,7 @@ extern "C" {
|
||||
// Config directory: override, then default.
|
||||
ctx->config_dir = DEFAULT_CONFIG_DIR;
|
||||
if (overrides && overrides->config_dir && *overrides->config_dir) {
|
||||
ctx->config_dir = with_trailing_slash(overrides->config_dir);
|
||||
ctx->config_dir = dpm_core::with_trailing_slash(overrides->config_dir);
|
||||
}
|
||||
|
||||
// Everything below reads configuration, so it loads first.
|
||||
@@ -145,10 +128,10 @@ extern "C" {
|
||||
// Module path: default, then configuration, then the override.
|
||||
ctx->module_path = DEFAULT_MODULE_PATH;
|
||||
if (const char* v = dpm_config_get(ctx, "core", "modules", "path")) {
|
||||
ctx->module_path = with_trailing_slash(v);
|
||||
ctx->module_path = dpm_core::with_trailing_slash(v);
|
||||
}
|
||||
if (overrides && overrides->module_path && *overrides->module_path) {
|
||||
ctx->module_path = with_trailing_slash(overrides->module_path);
|
||||
ctx->module_path = dpm_core::with_trailing_slash(overrides->module_path);
|
||||
}
|
||||
|
||||
// Target root: override, then default.
|
||||
@@ -189,13 +172,28 @@ extern "C" {
|
||||
* The path was fixed when the context opened, so it reflects the
|
||||
* override, configured value, or default that won at that point.
|
||||
*/
|
||||
const char* dpm_module_path(dpm_ctx* ctx) {
|
||||
const char* dpm_get_resolved_module_path(dpm_ctx* ctx) {
|
||||
if (!ctx) {
|
||||
return nullptr;
|
||||
}
|
||||
return ctx->module_path.c_str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Records a failure reason on the context
|
||||
*
|
||||
* A module calls this with the context handed to its entry point,
|
||||
* which is the caller's own, so the reason it records is the one the
|
||||
* caller reads back. The reason overwrites whatever was recorded
|
||||
* before it.
|
||||
*/
|
||||
void dpm_set_last_error(dpm_ctx* ctx, const char* msg) {
|
||||
if (!ctx || !msg) {
|
||||
return;
|
||||
}
|
||||
ctx->last_error = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reports the most recent failure recorded on the context
|
||||
*
|
||||
@@ -203,7 +201,7 @@ extern "C" {
|
||||
* the most recent failure and nothing earlier. An empty reason reads
|
||||
* as no failure recorded.
|
||||
*/
|
||||
const char* dpm_last_error(dpm_ctx* ctx) {
|
||||
const char* dpm_get_last_error(dpm_ctx* ctx) {
|
||||
if (!ctx || ctx->last_error.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace dpm_core {
|
||||
* except through this library.
|
||||
*
|
||||
* Every refusal fills `reason` with a description naming what failed,
|
||||
* which reaches the caller through dpm_last_error or the log.
|
||||
* which reaches the caller through dpm_get_last_error or the log.
|
||||
*/
|
||||
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
const std::string& name,
|
||||
@@ -125,7 +125,7 @@ namespace dpm_core {
|
||||
void* handle = dlopen(so_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle) {
|
||||
const char* err = dlerror();
|
||||
reason = std::string("dlopen failed: ") + (err ? err : "unknown");
|
||||
reason = std::string("could not be loaded: ") + (err ? err : "unknown");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -213,8 +213,8 @@ extern "C" {
|
||||
std::string reason;
|
||||
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
|
||||
if (!loaded) {
|
||||
dpm_core::set_error(ctx, std::string("module '") + name +
|
||||
"': " + reason);
|
||||
dpm_core::set_last_error(ctx, std::string("module '") + name +
|
||||
"': " + reason);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -275,8 +275,8 @@ extern "C" {
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::is_directory(ctx->module_path, ec)) {
|
||||
dpm_core::set_error(ctx, "module path is not a readable directory: " +
|
||||
ctx->module_path);
|
||||
dpm_core::set_last_error(ctx, "module path is not a readable directory: " +
|
||||
ctx->module_path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
48
src/core/sanitizers.cpp
Normal file
48
src/core/sanitizers.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file sanitizers.cpp
|
||||
* @brief Normalizing a value into the shape the library stores it in
|
||||
*
|
||||
* A value arriving from an override or from a configuration file is
|
||||
* written by a person, so it carries whatever spelling that person used.
|
||||
* A sanitizer takes one of those and returns it in the single form the
|
||||
* rest of the library relies on, which is what lets code downstream act
|
||||
* on a value without re-checking its shape at every use.
|
||||
*
|
||||
* Each one is total: it accepts any input of its type and returns a
|
||||
* usable result, so a caller never has to handle a sanitizer failing.
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
* @author Chris Punches <chris.punches@silogroup.org>
|
||||
*
|
||||
* Part of the Dark Horse Linux Package Manager (DPM)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "internal/sanitizers.hpp"
|
||||
|
||||
namespace dpm_core {
|
||||
/**
|
||||
* @brief Appends a trailing slash when one is absent
|
||||
*
|
||||
* Directory paths are stored with a trailing slash so that callers
|
||||
* concatenating a filename onto them produce a valid path without
|
||||
* checking the separator themselves.
|
||||
*/
|
||||
std::string with_trailing_slash(std::string s) {
|
||||
if (!s.empty() && s.back() != '/') {
|
||||
s += '/';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
} // namespace dpm_core
|
||||
@@ -51,7 +51,7 @@ static int g_checks = 0;
|
||||
} while (0)
|
||||
|
||||
static bool error_contains(dpm_ctx* ctx, const char* needle) {
|
||||
const char* err = dpm_last_error(ctx);
|
||||
const char* err = dpm_get_last_error(ctx);
|
||||
return err != nullptr && std::strstr(err, needle) != nullptr;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ int main(void) {
|
||||
CHECK(dpm_config_get(ctx, "nomodule", "main", "key") == nullptr);
|
||||
|
||||
/* module-path override wins over the config value */
|
||||
const char* path = dpm_module_path(ctx);
|
||||
const char* path = dpm_get_resolved_module_path(ctx);
|
||||
CHECK(path != nullptr &&
|
||||
std::strncmp(path, TEST_FIXTURE_MODULES,
|
||||
std::strlen(TEST_FIXTURE_MODULES)) == 0);
|
||||
|
||||
Reference in New Issue
Block a user