Indent every scope and put opening braces on the signature line

Namespace and extern "C" bodies are indented a level, so nesting is
visible from the indentation rather than only from the braces. Opening
braces stay on the line that opens the scope, including function
definitions, which previously carried theirs on a line of their own.
This commit is contained in:
2026-08-15 23:47:43 -04:00
parent 9fd7d2f433
commit 12a4f68025
13 changed files with 634 additions and 709 deletions

View File

@@ -63,23 +63,21 @@ struct dpm_ctx {
}; };
namespace dpm_core { namespace dpm_core {
/**
* @brief Records a failure reason on the context
*
* @param ctx The libdpm-core context; NULL is a no-op
* @param msg The failure description
*/
void set_error(dpm_ctx* ctx, const std::string& msg);
/** /**
* @brief Records a failure reason on the context * @brief Loads all configuration files into the context
* *
* @param ctx The libdpm-core context; NULL is a no-op * Parses every .conf file in the context's configuration directory
* @param msg The failure description * into the context's configuration store.
*/ *
void set_error(dpm_ctx* ctx, const std::string& msg); * @param ctx The libdpm-core context
*/
/** void load_config_dir(dpm_ctx* ctx);
* @brief Loads all configuration files into the context
*
* Parses every .conf file in the context's configuration directory
* into the context's configuration store.
*
* @param ctx The libdpm-core context
*/
void load_config_dir(dpm_ctx* ctx);
} // namespace dpm_core } // namespace dpm_core

View File

@@ -65,20 +65,18 @@ struct dpm_cursor {
void dpm_internal_unload(void* handle); void dpm_internal_unload(void* handle);
namespace dpm_core { namespace dpm_core {
/**
/** * @brief Runs the full load-time validation sequence against a module
* @brief Runs the full load-time validation sequence against a module *
* * Loads the named module's .so from the context's module path and
* Loads the named module's .so from the context's module path and * verifies the complete contract.
* verifies the complete contract. *
* * @param ctx The libdpm-core context
* @param ctx The libdpm-core context * @param name The module name
* @param name The module name * @param reason Receives the refusal reason on failure
* @param reason Receives the refusal reason on failure * @return The validated module (caller owns), or nullptr on failure
* @return The validated module (caller owns), or nullptr on failure */
*/ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx, const std::string& name,
const std::string& name, std::string& reason);
std::string& reason);
} // namespace dpm_core } // namespace dpm_core

View File

@@ -23,14 +23,12 @@
#pragma once #pragma once
namespace dpm_core { namespace dpm_core {
/**
/** * @brief Parses a strict X.Y.Z version string
* @brief Parses a strict X.Y.Z version string *
* * @param s The version string
* @param s The version string * @param out Receives the three parsed components
* @param out Receives the three parsed components * @return true on success; false on any malformation
* @return true on success; false on any malformation */
*/ bool parse_version(const char* s, long out[3]);
bool parse_version(const char* s, long out[3]);
} // namespace dpm_core } // namespace dpm_core

View File

@@ -36,16 +36,14 @@
/** /**
* @brief Returns the module's own version * @brief Returns the module's own version
*/ */
extern "C" const char* dpm_module_version(void) extern "C" const char* dpm_module_version(void) {
{
return INFO_MODULE_VERSION; return INFO_MODULE_VERSION;
} }
/** /**
* @brief Returns the module's one-line description * @brief Returns the module's one-line description
*/ */
extern "C" const char* dpm_module_description(void) extern "C" const char* dpm_module_description(void) {
{
return "Reports and tests libdpm-core functionality."; return "Reports and tests libdpm-core functionality.";
} }
@@ -62,8 +60,7 @@ extern "C" const char* dpm_module_description(void)
* @return 0 on success, non-zero on failure * @return 0 on success, non-zero on failure
*/ */
extern "C" int dpm_module_execute(dpm_ctx* ctx, const char* command, extern "C" int dpm_module_execute(dpm_ctx* ctx, const char* command,
int argc, char** argv) int argc, char** argv) {
{
(void)argc; (void)argc;
(void)argv; (void)argv;

View File

@@ -33,71 +33,66 @@
#define INFO_VERSION "0.1.0" #define INFO_VERSION "0.1.0"
namespace { namespace {
std::string detect_architecture() {
std::string detect_architecture() struct utsname system_info;
{ if (uname(&system_info) == -1) {
struct utsname system_info; return "Unknown";
if (uname(&system_info) == -1) { }
return "Unknown"; return system_info.machine;
}
return system_info.machine;
}
std::string detect_os()
{
struct utsname system_info;
if (uname(&system_info) == -1) {
return "Unknown";
} }
std::string os = system_info.sysname; std::string detect_os() {
struct utsname system_info;
if (uname(&system_info) == -1) {
return "Unknown";
}
if (os == "Linux") { std::string os = system_info.sysname;
std::ifstream os_release("/etc/os-release");
if (os_release.is_open()) {
std::string line;
std::string distro_name;
std::string distro_version;
while (std::getline(os_release, line)) { if (os == "Linux") {
if (line.rfind("NAME=", 0) == 0) { std::ifstream os_release("/etc/os-release");
distro_name = line.substr(5); if (os_release.is_open()) {
if (distro_name.size() >= 2 && std::string line;
distro_name.front() == '"' && std::string distro_name;
distro_name.back() == '"') { std::string distro_version;
distro_name =
distro_name.substr(1, distro_name.length() - 2); while (std::getline(os_release, line)) {
if (line.rfind("NAME=", 0) == 0) {
distro_name = line.substr(5);
if (distro_name.size() >= 2 &&
distro_name.front() == '"' &&
distro_name.back() == '"') {
distro_name =
distro_name.substr(1, distro_name.length() - 2);
}
}
if (line.rfind("VERSION_ID=", 0) == 0) {
distro_version = line.substr(11);
if (distro_version.size() >= 2 &&
distro_version.front() == '"' &&
distro_version.back() == '"') {
distro_version =
distro_version.substr(1,
distro_version.length() - 2);
}
} }
} }
if (line.rfind("VERSION_ID=", 0) == 0) {
distro_version = line.substr(11);
if (distro_version.size() >= 2 &&
distro_version.front() == '"' &&
distro_version.back() == '"') {
distro_version =
distro_version.substr(1,
distro_version.length() - 2);
}
}
}
if (!distro_name.empty()) { if (!distro_name.empty()) {
os += " (" + distro_name; os += " (" + distro_name;
if (!distro_version.empty()) { if (!distro_version.empty()) {
os += " " + distro_version; os += " " + distro_version;
}
os += ")";
} }
os += ")";
} }
} }
return os;
} }
return os;
}
} // namespace } // namespace
Command parse_command(const char* cmd_str) Command parse_command(const char* cmd_str) {
{
if (cmd_str == nullptr || std::strlen(cmd_str) == 0) { if (cmd_str == nullptr || std::strlen(cmd_str) == 0) {
return CMD_HELP; return CMD_HELP;
} }
@@ -116,8 +111,7 @@ Command parse_command(const char* cmd_str)
return CMD_UNKNOWN; return CMD_UNKNOWN;
} }
int cmd_help(dpm_ctx* ctx) int cmd_help(dpm_ctx* ctx) {
{
dpm_log(ctx, DPM_LOG_INFO, "DPM Info Module - Reports and tests libdpm-core functionality."); dpm_log(ctx, DPM_LOG_INFO, "DPM Info Module - Reports and tests libdpm-core functionality.");
dpm_log(ctx, DPM_LOG_INFO, ""); dpm_log(ctx, DPM_LOG_INFO, "");
dpm_log(ctx, DPM_LOG_INFO, "Available commands:"); dpm_log(ctx, DPM_LOG_INFO, "Available commands:");
@@ -130,8 +124,7 @@ int cmd_help(dpm_ctx* ctx)
return 0; return 0;
} }
int cmd_version(dpm_ctx* ctx) int cmd_version(dpm_ctx* ctx) {
{
std::string core_msg = "libdpm-core Version: "; std::string core_msg = "libdpm-core Version: ";
core_msg += dpm_core_version(); core_msg += dpm_core_version();
dpm_log(ctx, DPM_LOG_INFO, core_msg.c_str()); dpm_log(ctx, DPM_LOG_INFO, core_msg.c_str());
@@ -143,8 +136,7 @@ int cmd_version(dpm_ctx* ctx)
return 0; return 0;
} }
int cmd_system(dpm_ctx* ctx) int cmd_system(dpm_ctx* ctx) {
{
dpm_log(ctx, DPM_LOG_INFO, "System Information:"); dpm_log(ctx, DPM_LOG_INFO, "System Information:");
std::string os_msg = " OS: "; std::string os_msg = " OS: ";
@@ -158,8 +150,7 @@ int cmd_system(dpm_ctx* ctx)
return 0; return 0;
} }
int cmd_config(dpm_ctx* ctx) int cmd_config(dpm_ctx* ctx) {
{
dpm_log(ctx, DPM_LOG_INFO, "Configuration Information:"); dpm_log(ctx, DPM_LOG_INFO, "Configuration Information:");
const char* configured_path = dpm_config_get(ctx, "core", "modules", "path"); const char* configured_path = dpm_config_get(ctx, "core", "modules", "path");
@@ -175,8 +166,7 @@ int cmd_config(dpm_ctx* ctx)
return 0; return 0;
} }
int cmd_unknown(dpm_ctx* ctx, const char* command) int cmd_unknown(dpm_ctx* ctx, const char* command) {
{
std::string msg = "Unknown command: "; std::string msg = "Unknown command: ";
msg += (command ? command : ""); msg += (command ? command : "");
dpm_log(ctx, DPM_LOG_WARN, msg.c_str()); dpm_log(ctx, DPM_LOG_WARN, msg.c_str());

View File

@@ -33,127 +33,120 @@
#include <vector> #include <vector>
namespace { namespace {
/**
/** * @brief Prints the CLI usage message
* @brief Prints the CLI usage message */
*/ void show_help() {
void show_help() std::printf(
{ "Usage: dpm [options] [module] [module args...]\n"
std::printf( "\n"
"Usage: dpm [options] [module] [module args...]\n" "Options:\n"
"\n" " -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n"
"Options:\n" " -m, --module-path PATH Module directory (overrides configuration)\n"
" -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n" " -r, --root PATH Target root for package operations (default /)\n"
" -m, --module-path PATH Module directory (overrides configuration)\n" " -L, --log-level LEVEL FATAL, ERROR, WARN, INFO, or DEBUG\n"
" -r, --root PATH Target root for package operations (default /)\n" " -l, --list-modules List available modules\n"
" -L, --log-level LEVEL FATAL, ERROR, WARN, INFO, or DEBUG\n" " -h, --help Show this help message\n"
" -l, --list-modules List available modules\n" "\n"
" -h, --help Show this help message\n" "For module-specific help, use: dpm <module> help\n");
"\n"
"For module-specific help, use: dpm <module> help\n");
}
/**
* @brief Parses a log level name
*
* @param name The level name (case-insensitive)
* @return The DPM_LOG_* level, or -1 if unrecognized
*/
int level_from_name(const char* name)
{
if (strcasecmp(name, "FATAL") == 0) { return DPM_LOG_FATAL; }
if (strcasecmp(name, "ERROR") == 0) { return DPM_LOG_ERROR; }
if (strcasecmp(name, "WARN") == 0) { return DPM_LOG_WARN; }
if (strcasecmp(name, "INFO") == 0) { return DPM_LOG_INFO; }
if (strcasecmp(name, "DEBUG") == 0) { return DPM_LOG_DEBUG; }
return -1;
}
/**
* @brief Matches an argument against an option's short and long forms
*
* Recognizes the separate-value forms and the --option=value form.
*
* @param arg The command-line argument to test
* @param short_form The option's short form (e.g. "-c")
* @param long_form The option's long form (e.g. "--config-dir")
* @param inline_value Receives the text after '=' for the inline
* form, or NULL when the value is in the next
* argument
* @return true if the argument is this option
*/
bool option_matches(const char* arg, const char* short_form,
const char* long_form, const char** inline_value)
{
if (std::strcmp(arg, short_form) == 0 ||
std::strcmp(arg, long_form) == 0) {
*inline_value = nullptr;
return true;
} }
size_t len = std::strlen(long_form); /**
if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') { * @brief Parses a log level name
*inline_value = arg + len + 1; *
return true; * @param name The level name (case-insensitive)
* @return The DPM_LOG_* level, or -1 if unrecognized
*/
int level_from_name(const char* name) {
if (strcasecmp(name, "FATAL") == 0) { return DPM_LOG_FATAL; }
if (strcasecmp(name, "ERROR") == 0) { return DPM_LOG_ERROR; }
if (strcasecmp(name, "WARN") == 0) { return DPM_LOG_WARN; }
if (strcasecmp(name, "INFO") == 0) { return DPM_LOG_INFO; }
if (strcasecmp(name, "DEBUG") == 0) { return DPM_LOG_DEBUG; }
return -1;
} }
return false; /**
} * @brief Matches an argument against an option's short and long forms
*
* Recognizes the separate-value forms and the --option=value form.
*
* @param arg The command-line argument to test
* @param short_form The option's short form (e.g. "-c")
* @param long_form The option's long form (e.g. "--config-dir")
* @param inline_value Receives the text after '=' for the inline
* form, or NULL when the value is in the next
* argument
* @return true if the argument is this option
*/
bool option_matches(const char* arg, const char* short_form,
const char* long_form, const char** inline_value) {
if (std::strcmp(arg, short_form) == 0 ||
std::strcmp(arg, long_form) == 0) {
*inline_value = nullptr;
return true;
}
/** size_t len = std::strlen(long_form);
* @brief Prints the table of available modules if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') {
* *inline_value = arg + len + 1;
* @param ctx The libdpm-core context return true;
* @return 0 on success, 1 on failure }
*/
int list_modules(dpm_ctx* ctx) return false;
{
dpm_cursor* cur = dpm_list_modules(ctx);
if (!cur) {
const char* err = dpm_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n", err ? err : "module listing failed");
return 1;
} }
std::vector<dpm_module_info> infos; /**
dpm_module_info info; * @brief Prints the table of available modules
while (dpm_cursor_next(cur, &info) == 0) { *
infos.push_back(info); * @param ctx The libdpm-core context
} * @return 0 on success, 1 on failure
dpm_cursor_free(cur); */
int list_modules(dpm_ctx* ctx) {
dpm_cursor* cur = dpm_list_modules(ctx);
if (!cur) {
const char* err = dpm_last_error(ctx);
std::fprintf(stderr, "dpm: %s\n", err ? err : "module listing failed");
return 1;
}
std::vector<dpm_module_info> infos;
dpm_module_info info;
while (dpm_cursor_next(cur, &info) == 0) {
infos.push_back(info);
}
dpm_cursor_free(cur);
if (infos.empty()) {
std::printf("No valid modules found in %s\n", dpm_module_path(ctx));
return 0;
}
size_t name_w = std::strlen("MODULE");
size_t version_w = std::strlen("VERSION");
for (const auto& i : infos) {
name_w = std::max(name_w, std::strlen(i.name));
version_w = std::max(version_w, std::strlen(i.version));
}
std::printf("Available DPM modules:\n\n");
std::printf("%-*s %-*s %s\n",
static_cast<int>(name_w), "MODULE",
static_cast<int>(version_w), "VERSION",
"DESCRIPTION");
for (const auto& i : infos) {
std::printf("%-*s %-*s %s\n",
static_cast<int>(name_w), i.name,
static_cast<int>(version_w), i.version,
i.description);
}
std::printf("\nUse 'dpm <module> help' for module-specific help.\n");
if (infos.empty()) {
std::printf("No valid modules found in %s\n", dpm_module_path(ctx));
return 0; return 0;
} }
size_t name_w = std::strlen("MODULE");
size_t version_w = std::strlen("VERSION");
for (const auto& i : infos) {
name_w = std::max(name_w, std::strlen(i.name));
version_w = std::max(version_w, std::strlen(i.version));
}
std::printf("Available DPM modules:\n\n");
std::printf("%-*s %-*s %s\n",
static_cast<int>(name_w), "MODULE",
static_cast<int>(version_w), "VERSION",
"DESCRIPTION");
for (const auto& i : infos) {
std::printf("%-*s %-*s %s\n",
static_cast<int>(name_w), i.name,
static_cast<int>(version_w), i.version,
i.description);
}
std::printf("\nUse 'dpm <module> help' for module-specific help.\n");
return 0;
}
} // namespace } // namespace
int main(int argc, char** argv) int main(int argc, char** argv) {
{
dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1}; dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1};
bool list = false; bool list = false;

View File

@@ -33,295 +33,275 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace { namespace {
const char* DEFAULT_CONFIG_DIR = "/etc/dpm/conf.d/";
const char* DEFAULT_MODULE_PATH = "/usr/lib/dpm/modules/";
const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log";
const char* DEFAULT_CONFIG_DIR = "/etc/dpm/conf.d/"; std::string trim(const std::string& s) {
const char* DEFAULT_MODULE_PATH = "/usr/lib/dpm/modules/"; const char* ws = " \t\r\n\f\v";
const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log"; size_t start = s.find_first_not_of(ws);
if (start == std::string::npos) {
std::string trim(const std::string& s) return "";
{ }
const char* ws = " \t\r\n\f\v"; size_t end = s.find_last_not_of(ws);
size_t start = s.find_first_not_of(ws); return s.substr(start, end - start + 1);
if (start == std::string::npos) {
return "";
}
size_t end = s.find_last_not_of(ws);
return s.substr(start, end - start + 1);
}
std::string with_trailing_slash(std::string s)
{
if (!s.empty() && s.back() != '/') {
s += '/';
}
return s;
}
bool parse_bool(const std::string& v, bool fallback)
{
std::string lower;
for (char c : v) {
lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
if (lower == "true" || lower == "yes" || lower == "1" || lower == "on") {
return true;
}
if (lower == "false" || lower == "no" || lower == "0" || lower == "off") {
return false;
}
return fallback;
}
int level_from_string(const std::string& v, int fallback)
{
if (v == "FATAL") { return DPM_LOG_FATAL; }
if (v == "ERROR") { return DPM_LOG_ERROR; }
if (v == "WARN") { return DPM_LOG_WARN; }
if (v == "INFO") { return DPM_LOG_INFO; }
if (v == "DEBUG") { return DPM_LOG_DEBUG; }
return fallback;
}
const char* level_name(int level)
{
switch (level) {
case DPM_LOG_FATAL: return "FATAL";
case DPM_LOG_ERROR: return "ERROR";
case DPM_LOG_WARN: return "WARN";
case DPM_LOG_INFO: return "INFO";
case DPM_LOG_DEBUG: return "DEBUG";
default: return "UNKNOWN";
}
}
void parse_config_file(dpm_ctx* ctx, const fs::path& file,
const std::string& module_name)
{
std::ifstream in(file);
if (!in.is_open()) {
return;
} }
std::string line; std::string with_trailing_slash(std::string s) {
std::string section = "main"; if (!s.empty() && s.back() != '/') {
s += '/';
}
return s;
}
while (std::getline(in, line)) { bool parse_bool(const std::string& v, bool fallback) {
line = trim(line); std::string lower;
for (char c : v) {
lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
if (lower == "true" || lower == "yes" || lower == "1" || lower == "on") {
return true;
}
if (lower == "false" || lower == "no" || lower == "0" || lower == "off") {
return false;
}
return fallback;
}
if (line.empty() || line[0] == '#' || line[0] == ';') { int level_from_string(const std::string& v, int fallback) {
continue; if (v == "FATAL") { return DPM_LOG_FATAL; }
if (v == "ERROR") { return DPM_LOG_ERROR; }
if (v == "WARN") { return DPM_LOG_WARN; }
if (v == "INFO") { return DPM_LOG_INFO; }
if (v == "DEBUG") { return DPM_LOG_DEBUG; }
return fallback;
}
const char* level_name(int level) {
switch (level) {
case DPM_LOG_FATAL: return "FATAL";
case DPM_LOG_ERROR: return "ERROR";
case DPM_LOG_WARN: return "WARN";
case DPM_LOG_INFO: return "INFO";
case DPM_LOG_DEBUG: return "DEBUG";
default: return "UNKNOWN";
}
}
void parse_config_file(dpm_ctx* ctx, const fs::path& file,
const std::string& module_name) {
std::ifstream in(file);
if (!in.is_open()) {
return;
} }
if (line.front() == '[' && line.back() == ']') { std::string line;
std::string name = trim(line.substr(1, line.length() - 2)); std::string section = "main";
section = name.empty() ? "main" : name;
continue;
}
size_t eq = line.find('='); while (std::getline(in, line)) {
if (eq == std::string::npos) { line = trim(line);
continue;
}
std::string key = trim(line.substr(0, eq)); if (line.empty() || line[0] == '#' || line[0] == ';') {
std::string value = trim(line.substr(eq + 1)); continue;
if (key.empty()) { }
continue;
}
ctx->config[module_name][section][key] = value; if (line.front() == '[' && line.back() == ']') {
std::string name = trim(line.substr(1, line.length() - 2));
section = name.empty() ? "main" : name;
continue;
}
size_t eq = line.find('=');
if (eq == std::string::npos) {
continue;
}
std::string key = trim(line.substr(0, eq));
std::string value = trim(line.substr(eq + 1));
if (key.empty()) {
continue;
}
ctx->config[module_name][section][key] = value;
}
} }
}
} // namespace } // namespace
namespace dpm_core { namespace dpm_core {
void set_error(dpm_ctx* ctx, const std::string& msg) {
void set_error(dpm_ctx* ctx, const std::string& msg) if (ctx) {
{ ctx->last_error = msg;
if (ctx) { }
ctx->last_error = msg;
}
}
void load_config_dir(dpm_ctx* ctx)
{
std::error_code ec;
if (!fs::is_directory(ctx->config_dir, ec)) {
return;
} }
for (const auto& entry : fs::directory_iterator(ctx->config_dir, ec)) { void load_config_dir(dpm_ctx* ctx) {
if (ec) { std::error_code ec;
break; if (!fs::is_directory(ctx->config_dir, ec)) {
return;
} }
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().extension() != ".conf") {
continue;
}
parse_config_file(ctx, entry.path(), entry.path().stem().string());
}
}
for (const auto& entry : fs::directory_iterator(ctx->config_dir, ec)) {
if (ec) {
break;
}
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().extension() != ".conf") {
continue;
}
parse_config_file(ctx, entry.path(), entry.path().stem().string());
}
}
} // namespace dpm_core } // namespace dpm_core
extern "C" { extern "C" {
dpm_ctx* dpm_open(const dpm_open_overrides* overrides) {
dpm_ctx* dpm_open(const dpm_open_overrides* overrides) /* Validate explicit overrides before allocating anything. */
{ if (overrides) {
/* Validate explicit overrides before allocating anything. */ if (overrides->config_dir && *overrides->config_dir) {
if (overrides) { std::error_code ec;
if (overrides->config_dir && *overrides->config_dir) { if (!fs::is_directory(overrides->config_dir, ec)) {
std::error_code ec; return nullptr;
if (!fs::is_directory(overrides->config_dir, ec)) { }
}
if (overrides->log_level < -1 || overrides->log_level > DPM_LOG_DEBUG) {
return nullptr; return nullptr;
} }
} }
if (overrides->log_level < -1 || overrides->log_level > DPM_LOG_DEBUG) {
dpm_ctx* ctx = new (std::nothrow) dpm_ctx;
if (!ctx) {
return nullptr; return nullptr;
} }
}
dpm_ctx* ctx = new (std::nothrow) dpm_ctx; /* Config directory: override > default. */
if (!ctx) { ctx->config_dir = DEFAULT_CONFIG_DIR;
return nullptr; if (overrides && overrides->config_dir && *overrides->config_dir) {
} ctx->config_dir = with_trailing_slash(overrides->config_dir);
/* Config directory: override > default. */
ctx->config_dir = DEFAULT_CONFIG_DIR;
if (overrides && overrides->config_dir && *overrides->config_dir) {
ctx->config_dir = with_trailing_slash(overrides->config_dir);
}
dpm_core::load_config_dir(ctx);
/* Logging: config, then override. */
ctx->log_level = DPM_LOG_INFO;
ctx->write_to_log = false;
ctx->log_file = DEFAULT_LOG_FILE;
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_level")) {
ctx->log_level = level_from_string(v, DPM_LOG_INFO);
}
if (const char* v = dpm_config_get(ctx, "core", "logging", "write_to_log")) {
ctx->write_to_log = parse_bool(v, false);
}
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_file")) {
ctx->log_file = v;
}
if (overrides && overrides->log_level >= 0) {
ctx->log_level = overrides->log_level;
}
/* Module path: override > config > default. */
ctx->module_path = DEFAULT_MODULE_PATH;
if (const char* v = dpm_config_get(ctx, "core", "modules", "path")) {
ctx->module_path = with_trailing_slash(v);
}
if (overrides && overrides->module_path && *overrides->module_path) {
ctx->module_path = with_trailing_slash(overrides->module_path);
}
/* Target root: override > default. */
ctx->root = "/";
if (overrides && overrides->root && *overrides->root) {
ctx->root = overrides->root;
}
return ctx;
}
void dpm_close(dpm_ctx* ctx)
{
if (!ctx) {
return;
}
/* dpm_module destructors do not dlclose; do it here so the
registry teardown order is explicit. */
for (auto& [name, mod] : ctx->modules) {
if (mod->handle) {
dpm_internal_unload(mod->handle);
} }
}
delete ctx;
}
const char* dpm_config_get(dpm_ctx* ctx, const char* module, dpm_core::load_config_dir(ctx);
const char* section, const char* key)
{ /* Logging: config, then override. */
if (!ctx || !module || !section || !key) { ctx->log_level = DPM_LOG_INFO;
return nullptr; ctx->write_to_log = false;
ctx->log_file = DEFAULT_LOG_FILE;
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_level")) {
ctx->log_level = level_from_string(v, DPM_LOG_INFO);
}
if (const char* v = dpm_config_get(ctx, "core", "logging", "write_to_log")) {
ctx->write_to_log = parse_bool(v, false);
}
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_file")) {
ctx->log_file = v;
}
if (overrides && overrides->log_level >= 0) {
ctx->log_level = overrides->log_level;
}
/* Module path: override > config > default. */
ctx->module_path = DEFAULT_MODULE_PATH;
if (const char* v = dpm_config_get(ctx, "core", "modules", "path")) {
ctx->module_path = with_trailing_slash(v);
}
if (overrides && overrides->module_path && *overrides->module_path) {
ctx->module_path = with_trailing_slash(overrides->module_path);
}
/* Target root: override > default. */
ctx->root = "/";
if (overrides && overrides->root && *overrides->root) {
ctx->root = overrides->root;
}
return ctx;
} }
auto mod_it = ctx->config.find(module); void dpm_close(dpm_ctx* ctx) {
if (mod_it == ctx->config.end()) { if (!ctx) {
return nullptr; return;
}
/* dpm_module destructors do not dlclose; do it here so the
registry teardown order is explicit. */
for (auto& [name, mod] : ctx->modules) {
if (mod->handle) {
dpm_internal_unload(mod->handle);
}
}
delete ctx;
} }
auto sec_it = mod_it->second.find(section); const char* dpm_config_get(dpm_ctx* ctx, const char* module,
if (sec_it == mod_it->second.end()) { const char* section, const char* key) {
return nullptr; if (!ctx || !module || !section || !key) {
return nullptr;
}
auto mod_it = ctx->config.find(module);
if (mod_it == ctx->config.end()) {
return nullptr;
}
auto sec_it = mod_it->second.find(section);
if (sec_it == mod_it->second.end()) {
return nullptr;
}
auto key_it = sec_it->second.find(key);
if (key_it == sec_it->second.end()) {
return nullptr;
}
return key_it->second.c_str();
} }
auto key_it = sec_it->second.find(key); void dpm_log(dpm_ctx* ctx, int level, const char* message) {
if (key_it == sec_it->second.end()) { if (!ctx || !message) {
return nullptr; return;
} }
if (level < DPM_LOG_FATAL || level > DPM_LOG_DEBUG) {
level = DPM_LOG_INFO;
}
if (level > ctx->log_level) {
return;
}
return key_it->second.c_str(); if (level <= DPM_LOG_WARN) {
} std::fprintf(stderr, "%s: %s\n", level_name(level), message);
void dpm_log(dpm_ctx* ctx, int level, const char* message)
{
if (!ctx || !message) {
return;
}
if (level < DPM_LOG_FATAL || level > DPM_LOG_DEBUG) {
level = DPM_LOG_INFO;
}
if (level > ctx->log_level) {
return;
}
if (level <= DPM_LOG_WARN) {
std::fprintf(stderr, "%s: %s\n", level_name(level), message);
} else {
std::fprintf(stdout, "%s\n", message);
}
if (ctx->write_to_log) {
std::ofstream out(ctx->log_file, std::ios::app);
if (out.is_open()) {
char stamp[32];
std::time_t now = std::time(nullptr);
std::strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S",
std::localtime(&now));
out << stamp << " [" << level_name(level) << "] "
<< message << "\n";
} else { } else {
/* Log-file trouble never fails the operation; disable and std::fprintf(stdout, "%s\n", message);
continue on console alone. */ }
ctx->write_to_log = false;
if (ctx->write_to_log) {
std::ofstream out(ctx->log_file, std::ios::app);
if (out.is_open()) {
char stamp[32];
std::time_t now = std::time(nullptr);
std::strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S",
std::localtime(&now));
out << stamp << " [" << level_name(level) << "] "
<< message << "\n";
} else {
/* Log-file trouble never fails the operation; disable and
continue on console alone. */
ctx->write_to_log = false;
}
} }
} }
}
const char* dpm_module_path(dpm_ctx* ctx) const char* dpm_module_path(dpm_ctx* ctx) {
{ if (!ctx) {
if (!ctx) { return nullptr;
return nullptr; }
return ctx->module_path.c_str();
} }
return ctx->module_path.c_str();
}
const char* dpm_last_error(dpm_ctx* ctx) const char* dpm_last_error(dpm_ctx* ctx) {
{ if (!ctx || ctx->last_error.empty()) {
if (!ctx || ctx->last_error.empty()) { return nullptr;
return nullptr; }
return ctx->last_error.c_str();
} }
return ctx->last_error.c_str();
}
} /* extern "C" */ } /* extern "C" */

View File

@@ -41,225 +41,210 @@ namespace fs = std::filesystem;
/* dlclose wrapper referenced from context.cpp so handle release stays /* dlclose wrapper referenced from context.cpp so handle release stays
in one translation unit with the loader. */ in one translation unit with the loader. */
void dpm_internal_unload(void* handle) void dpm_internal_unload(void* handle) {
{
if (handle) { if (handle) {
dlclose(handle); dlclose(handle);
} }
} }
namespace { namespace {
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**);
using string_fn = const char* (*)(void);
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**); /** dlsym with dlerror() discipline; returns nullptr on any error. */
using string_fn = const char* (*)(void); void* resolve(void* handle, const char* symbol) {
dlerror();
/** dlsym with dlerror() discipline; returns nullptr on any error. */ void* addr = dlsym(handle, symbol);
void* resolve(void* handle, const char* symbol) if (dlerror() != nullptr) {
{ return nullptr;
dlerror(); }
void* addr = dlsym(handle, symbol); return addr;
if (dlerror() != nullptr) {
return nullptr;
} }
return addr;
}
} // namespace } // namespace
namespace dpm_core { namespace dpm_core {
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
const std::string& name,
std::string& reason) {
std::string so_path = ctx->module_path + name + ".so";
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx, std::error_code ec;
const std::string& name, if (!fs::exists(so_path, ec)) {
std::string& reason) reason = "not found at " + so_path;
{ return nullptr;
std::string so_path = ctx->module_path + name + ".so";
std::error_code ec;
if (!fs::exists(so_path, ec)) {
reason = "not found at " + so_path;
return nullptr;
}
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");
return nullptr;
}
/* Step 1: resolve all reserved contract symbols. */
static const char* required[] = {
"dpm_module_execute",
"dpm_module_version",
"dpm_module_description",
};
std::string missing;
for (const char* sym : required) {
if (!resolve(handle, sym)) {
if (!missing.empty()) {
missing += ", ";
}
missing += sym;
} }
}
if (!missing.empty()) {
reason = "missing required contract symbols: " + missing;
dlclose(handle);
return nullptr;
}
auto exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute")); void* handle = dlopen(so_path.c_str(), RTLD_NOW | RTLD_LOCAL);
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version")); if (!handle) {
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description")); const char* err = dlerror();
reason = std::string("dlopen failed: ") + (err ? err : "unknown");
return nullptr;
}
/* Step 2: probe the cheap calls. */ /* Step 1: resolve all reserved contract symbols. */
long parsed[3]; static const char* required[] = {
const char* version = version_f(); "dpm_module_execute",
if (!version || !parse_version(version, parsed)) { "dpm_module_version",
reason = "dpm_module_version() returned a malformed version"; "dpm_module_description",
dlclose(handle); };
return nullptr;
std::string missing;
for (const char* sym : required) {
if (!resolve(handle, sym)) {
if (!missing.empty()) {
missing += ", ";
}
missing += sym;
}
}
if (!missing.empty()) {
reason = "missing required contract symbols: " + missing;
dlclose(handle);
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"));
/* Step 2: probe the cheap calls. */
long parsed[3];
const char* version = version_f();
if (!version || !parse_version(version, parsed)) {
reason = "dpm_module_version() returned a malformed version";
dlclose(handle);
return nullptr;
}
const char* description = desc_f();
if (!description || !*description) {
reason = "dpm_module_description() returned nothing";
dlclose(handle);
return nullptr;
}
auto mod = std::make_unique<dpm_module>();
mod->name = name;
mod->handle = handle;
mod->version = version;
mod->description = description;
mod->execute = exec_f;
return mod;
} }
const char* description = desc_f();
if (!description || !*description) {
reason = "dpm_module_description() returned nothing";
dlclose(handle);
return nullptr;
}
auto mod = std::make_unique<dpm_module>();
mod->name = name;
mod->handle = handle;
mod->version = version;
mod->description = description;
mod->execute = exec_f;
return mod;
}
} // namespace dpm_core } // namespace dpm_core
extern "C" { extern "C" {
dpm_module* dpm_require(dpm_ctx* ctx, const char* name) {
dpm_module* dpm_require(dpm_ctx* ctx, const char* name) if (!ctx || !name || !*name) {
{ return nullptr;
if (!ctx || !name || !*name) {
return nullptr;
}
auto it = ctx->modules.find(name);
if (it != ctx->modules.end()) {
return it->second.get();
}
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);
return nullptr;
}
dpm_module* mod = loaded.get();
ctx->modules[name] = std::move(loaded);
return mod;
}
int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)
{
if (!ctx || !mod || !out) {
return 1;
}
out->name = mod->name.c_str();
out->version = mod->version.c_str();
out->description = mod->description.c_str();
return 0;
}
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
int argc, char** argv)
{
if (!ctx || !mod || !mod->execute) {
return 1;
}
return mod->execute(ctx, command, argc, argv);
}
dpm_cursor* dpm_list_modules(dpm_ctx* ctx)
{
if (!ctx) {
return nullptr;
}
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);
return nullptr;
}
std::set<std::string> names;
for (const auto& entry : fs::directory_iterator(ctx->module_path, ec)) {
if (ec) {
break;
} }
if (!entry.is_regular_file() && !entry.is_symlink()) {
continue;
}
if (entry.path().extension() != ".so") {
continue;
}
names.insert(entry.path().stem().string());
}
auto* cur = new (std::nothrow) dpm_cursor;
if (!cur) {
return nullptr;
}
for (const std::string& name : names) {
dpm_module* mod = nullptr;
auto it = ctx->modules.find(name); auto it = ctx->modules.find(name);
if (it != ctx->modules.end()) { if (it != ctx->modules.end()) {
mod = it->second.get(); return it->second.get();
} else {
std::string reason;
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
if (!loaded) {
dpm_log(ctx, DPM_LOG_WARN,
("module '" + name + "' failed validation: " +
reason).c_str());
continue;
}
mod = loaded.get();
ctx->modules[name] = std::move(loaded);
} }
dpm_module_info info; std::string reason;
info.name = mod->name.c_str(); auto loaded = dpm_core::validate_and_load(ctx, name, reason);
info.version = mod->version.c_str(); if (!loaded) {
info.description = mod->description.c_str(); dpm_core::set_error(ctx, std::string("module '") + name +
cur->infos.push_back(info); "': " + reason);
return nullptr;
}
dpm_module* mod = loaded.get();
ctx->modules[name] = std::move(loaded);
return mod;
} }
return cur; int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out) {
} if (!ctx || !mod || !out) {
return 1;
}
int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out) out->name = mod->name.c_str();
{ out->version = mod->version.c_str();
if (!cur || !out || cur->idx >= cur->infos.size()) { out->description = mod->description.c_str();
return 1; return 0;
} }
*out = cur->infos[cur->idx];
cur->idx++;
return 0;
}
void dpm_cursor_free(dpm_cursor* cur) int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
{ int argc, char** argv) {
delete cur; if (!ctx || !mod || !mod->execute) {
} return 1;
}
return mod->execute(ctx, command, argc, argv);
}
dpm_cursor* dpm_list_modules(dpm_ctx* ctx) {
if (!ctx) {
return nullptr;
}
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);
return nullptr;
}
std::set<std::string> names;
for (const auto& entry : fs::directory_iterator(ctx->module_path, ec)) {
if (ec) {
break;
}
if (!entry.is_regular_file() && !entry.is_symlink()) {
continue;
}
if (entry.path().extension() != ".so") {
continue;
}
names.insert(entry.path().stem().string());
}
auto* cur = new (std::nothrow) dpm_cursor;
if (!cur) {
return nullptr;
}
for (const std::string& name : names) {
dpm_module* mod = nullptr;
auto it = ctx->modules.find(name);
if (it != ctx->modules.end()) {
mod = it->second.get();
} else {
std::string reason;
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
if (!loaded) {
dpm_log(ctx, DPM_LOG_WARN,
("module '" + name + "' failed validation: " +
reason).c_str());
continue;
}
mod = loaded.get();
ctx->modules[name] = std::move(loaded);
}
dpm_module_info info;
info.name = mod->name.c_str();
info.version = mod->version.c_str();
info.description = mod->description.c_str();
cur->infos.push_back(info);
}
return cur;
}
int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out) {
if (!cur || !out || cur->idx >= cur->infos.size()) {
return 1;
}
*out = cur->infos[cur->idx];
cur->idx++;
return 0;
}
void dpm_cursor_free(dpm_cursor* cur) {
delete cur;
}
} /* extern "C" */ } /* extern "C" */

View File

@@ -30,44 +30,40 @@
/** libdpm-core.so's own version. */ /** libdpm-core.so's own version. */
#define DPM_CORE_VERSION_STR "1.0.0" #define DPM_CORE_VERSION_STR "1.0.0"
extern "C" const char* dpm_core_version(void) extern "C" const char* dpm_core_version(void) {
{
return DPM_CORE_VERSION_STR; return DPM_CORE_VERSION_STR;
} }
namespace dpm_core { namespace dpm_core {
bool parse_version(const char* s, long out[3]) {
bool parse_version(const char* s, long out[3]) if (!s || !*s) {
{
if (!s || !*s) {
return false;
}
const char* p = s;
for (int i = 0; i < 3; i++) {
if (!std::isdigit(static_cast<unsigned char>(*p))) {
return false; return false;
} }
char* end = nullptr; const char* p = s;
out[i] = std::strtol(p, &end, 10); for (int i = 0; i < 3; i++) {
if (out[i] < 0) { if (!std::isdigit(static_cast<unsigned char>(*p))) {
return false; return false;
}
char* end = nullptr;
out[i] = std::strtol(p, &end, 10);
if (out[i] < 0) {
return false;
}
if (i < 2) {
if (*end != '.') {
return false;
}
p = end + 1;
} else {
if (*end != '\0') {
return false;
}
}
} }
if (i < 2) { return true;
if (*end != '.') {
return false;
}
p = end + 1;
} else {
if (*end != '\0') {
return false;
}
}
} }
return true;
}
} // namespace dpm_core } // namespace dpm_core

View File

@@ -20,19 +20,16 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
extern "C" const char* dpm_module_version(void) extern "C" const char* dpm_module_version(void) {
{
return "banana"; return "banana";
} }
extern "C" const char* dpm_module_description(void) extern "C" const char* dpm_module_description(void) {
{
return "Fixture with a malformed version."; return "Fixture with a malformed version.";
} }
extern "C" int dpm_module_execute(void* ctx, const char* command, extern "C" int dpm_module_execute(void* ctx, const char* command,
int argc, char** argv) int argc, char** argv) {
{
(void)ctx; (void)ctx;
(void)command; (void)command;
(void)argc; (void)argc;

View File

@@ -24,21 +24,18 @@
*/ */
#include <cstring> #include <cstring>
extern "C" const char* dpm_module_version(void) extern "C" const char* dpm_module_version(void) {
{
return "1.2.3"; return "1.2.3";
} }
extern "C" const char* dpm_module_description(void) extern "C" const char* dpm_module_description(void) {
{
return "Known-good stub module."; return "Known-good stub module.";
} }
/* Answers "ping" with 42 so a dispatch round trip is observable, and /* Answers "ping" with 42 so a dispatch round trip is observable, and
0 for anything else. */ 0 for anything else. */
extern "C" int dpm_module_execute(void* ctx, const char* command, extern "C" int dpm_module_execute(void* ctx, const char* command,
int argc, char** argv) int argc, char** argv) {
{
(void)ctx; (void)ctx;
(void)argc; (void)argc;
(void)argv; (void)argv;

View File

@@ -21,12 +21,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
extern "C" const char* dpm_module_version(void) extern "C" const char* dpm_module_version(void) {
{
return "1.0.0"; return "1.0.0";
} }
extern "C" const char* dpm_module_description(void) extern "C" const char* dpm_module_description(void) {
{
return "Fixture missing most of the contract."; return "Fixture missing most of the contract.";
} }

View File

@@ -50,14 +50,12 @@ static int g_checks = 0;
} \ } \
} while (0) } while (0)
static bool error_contains(dpm_ctx* ctx, const char* needle) static bool error_contains(dpm_ctx* ctx, const char* needle) {
{
const char* err = dpm_last_error(ctx); const char* err = dpm_last_error(ctx);
return err != nullptr && std::strstr(err, needle) != nullptr; return err != nullptr && std::strstr(err, needle) != nullptr;
} }
int main(void) int main(void) {
{
/* ---- dpm_open: invalid explicit override refuses ---- */ /* ---- dpm_open: invalid explicit override refuses ---- */
{ {
dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1}; dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1};