diff --git a/include/internal/context.hpp b/include/internal/context.hpp index caa8521..bbb113b 100644 --- a/include/internal/context.hpp +++ b/include/internal/context.hpp @@ -63,23 +63,21 @@ struct dpm_ctx { }; 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 - * - * @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 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); - + /** + * @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 diff --git a/include/internal/modules.hpp b/include/internal/modules.hpp index 13ed70d..21590bd 100644 --- a/include/internal/modules.hpp +++ b/include/internal/modules.hpp @@ -65,20 +65,18 @@ struct dpm_cursor { void dpm_internal_unload(void* handle); namespace dpm_core { - -/** - * @brief Runs the full load-time validation sequence against a module - * - * Loads the named module's .so from the context's module path and - * verifies the complete contract. - * - * @param ctx The libdpm-core context - * @param name The module name - * @param reason Receives the refusal reason on failure - * @return The validated module (caller owns), or nullptr on failure - */ -std::unique_ptr validate_and_load(dpm_ctx* ctx, - const std::string& name, - std::string& reason); - + /** + * @brief Runs the full load-time validation sequence against a module + * + * Loads the named module's .so from the context's module path and + * verifies the complete contract. + * + * @param ctx The libdpm-core context + * @param name The module name + * @param reason Receives the refusal reason on failure + * @return The validated module (caller owns), or nullptr on failure + */ + std::unique_ptr validate_and_load(dpm_ctx* ctx, + const std::string& name, + std::string& reason); } // namespace dpm_core diff --git a/include/internal/version.hpp b/include/internal/version.hpp index a1bcdca..4f7a3a3 100644 --- a/include/internal/version.hpp +++ b/include/internal/version.hpp @@ -23,14 +23,12 @@ #pragma once namespace dpm_core { - -/** - * @brief Parses a strict X.Y.Z version string - * - * @param s The version string - * @param out Receives the three parsed components - * @return true on success; false on any malformation - */ -bool parse_version(const char* s, long out[3]); - + /** + * @brief Parses a strict X.Y.Z version string + * + * @param s The version string + * @param out Receives the three parsed components + * @return true on success; false on any malformation + */ + bool parse_version(const char* s, long out[3]); } // namespace dpm_core diff --git a/src/bundled-modules/info/info.cpp b/src/bundled-modules/info/info.cpp index d9a639b..24db2a0 100644 --- a/src/bundled-modules/info/info.cpp +++ b/src/bundled-modules/info/info.cpp @@ -36,16 +36,14 @@ /** * @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; } /** * @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."; } @@ -62,8 +60,7 @@ extern "C" const char* dpm_module_description(void) * @return 0 on success, non-zero on failure */ extern "C" int dpm_module_execute(dpm_ctx* ctx, const char* command, - int argc, char** argv) -{ + int argc, char** argv) { (void)argc; (void)argv; diff --git a/src/bundled-modules/info/src/commands.cpp b/src/bundled-modules/info/src/commands.cpp index 9c4fc26..829d7d7 100644 --- a/src/bundled-modules/info/src/commands.cpp +++ b/src/bundled-modules/info/src/commands.cpp @@ -33,71 +33,66 @@ #define INFO_VERSION "0.1.0" namespace { - -std::string detect_architecture() -{ - struct utsname system_info; - if (uname(&system_info) == -1) { - return "Unknown"; - } - return system_info.machine; -} - -std::string detect_os() -{ - struct utsname system_info; - if (uname(&system_info) == -1) { - return "Unknown"; + std::string detect_architecture() { + struct utsname system_info; + if (uname(&system_info) == -1) { + return "Unknown"; + } + return system_info.machine; } - 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::ifstream os_release("/etc/os-release"); - if (os_release.is_open()) { - std::string line; - std::string distro_name; - std::string distro_version; + std::string os = system_info.sysname; - 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 (os == "Linux") { + 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 (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()) { - os += " (" + distro_name; - if (!distro_version.empty()) { - os += " " + distro_version; + if (!distro_name.empty()) { + os += " (" + distro_name; + if (!distro_version.empty()) { + os += " " + distro_version; + } + os += ")"; } - os += ")"; } } + + return os; } - - return os; -} - } // namespace -Command parse_command(const char* cmd_str) -{ +Command parse_command(const char* cmd_str) { if (cmd_str == nullptr || std::strlen(cmd_str) == 0) { return CMD_HELP; } @@ -116,8 +111,7 @@ Command parse_command(const char* cmd_str) 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_log(ctx, DPM_LOG_INFO, "Available commands:"); @@ -130,8 +124,7 @@ int cmd_help(dpm_ctx* ctx) return 0; } -int cmd_version(dpm_ctx* ctx) -{ +int cmd_version(dpm_ctx* ctx) { std::string core_msg = "libdpm-core Version: "; core_msg += dpm_core_version(); dpm_log(ctx, DPM_LOG_INFO, core_msg.c_str()); @@ -143,8 +136,7 @@ int cmd_version(dpm_ctx* ctx) return 0; } -int cmd_system(dpm_ctx* ctx) -{ +int cmd_system(dpm_ctx* ctx) { dpm_log(ctx, DPM_LOG_INFO, "System Information:"); std::string os_msg = " OS: "; @@ -158,8 +150,7 @@ int cmd_system(dpm_ctx* ctx) return 0; } -int cmd_config(dpm_ctx* ctx) -{ +int cmd_config(dpm_ctx* ctx) { dpm_log(ctx, DPM_LOG_INFO, "Configuration Information:"); const char* configured_path = dpm_config_get(ctx, "core", "modules", "path"); @@ -175,8 +166,7 @@ int cmd_config(dpm_ctx* ctx) 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: "; msg += (command ? command : ""); dpm_log(ctx, DPM_LOG_WARN, msg.c_str()); diff --git a/src/cli/dpm.cpp b/src/cli/dpm.cpp index 9e64135..01a77e8 100644 --- a/src/cli/dpm.cpp +++ b/src/cli/dpm.cpp @@ -33,127 +33,120 @@ #include namespace { - -/** - * @brief Prints the CLI usage message - */ -void show_help() -{ - std::printf( - "Usage: dpm [options] [module] [module args...]\n" - "\n" - "Options:\n" - " -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n" - " -m, --module-path PATH Module 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" - "For module-specific help, use: dpm 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; + /** + * @brief Prints the CLI usage message + */ + void show_help() { + std::printf( + "Usage: dpm [options] [module] [module args...]\n" + "\n" + "Options:\n" + " -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n" + " -m, --module-path PATH Module 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" + "For module-specific help, use: dpm help\n"); } - size_t len = std::strlen(long_form); - if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') { - *inline_value = arg + len + 1; - return true; + /** + * @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; } - 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; + } -/** - * @brief Prints the table of available modules - * - * @param ctx The libdpm-core context - * @return 0 on success, 1 on failure - */ -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; + size_t len = std::strlen(long_form); + if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') { + *inline_value = arg + len + 1; + return true; + } + + return false; } - std::vector infos; - dpm_module_info info; - while (dpm_cursor_next(cur, &info) == 0) { - infos.push_back(info); - } - dpm_cursor_free(cur); + /** + * @brief Prints the table of available modules + * + * @param ctx The libdpm-core context + * @return 0 on success, 1 on failure + */ + 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 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(name_w), "MODULE", + static_cast(version_w), "VERSION", + "DESCRIPTION"); + for (const auto& i : infos) { + std::printf("%-*s %-*s %s\n", + static_cast(name_w), i.name, + static_cast(version_w), i.version, + i.description); + } + std::printf("\nUse 'dpm help' for module-specific help.\n"); - 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(name_w), "MODULE", - static_cast(version_w), "VERSION", - "DESCRIPTION"); - for (const auto& i : infos) { - std::printf("%-*s %-*s %s\n", - static_cast(name_w), i.name, - static_cast(version_w), i.version, - i.description); - } - std::printf("\nUse 'dpm help' for module-specific help.\n"); - - return 0; -} - } // namespace -int main(int argc, char** argv) -{ +int main(int argc, char** argv) { dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1}; bool list = false; diff --git a/src/core/context.cpp b/src/core/context.cpp index 9eb52d0..9299105 100644 --- a/src/core/context.cpp +++ b/src/core/context.cpp @@ -33,295 +33,275 @@ namespace fs = std::filesystem; 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/"; -const char* DEFAULT_MODULE_PATH = "/usr/lib/dpm/modules/"; -const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log"; - -std::string trim(const std::string& s) -{ - const char* ws = " \t\r\n\f\v"; - size_t start = s.find_first_not_of(ws); - 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(std::tolower(static_cast(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 trim(const std::string& s) { + const char* ws = " \t\r\n\f\v"; + size_t start = s.find_first_not_of(ws); + if (start == std::string::npos) { + return ""; + } + size_t end = s.find_last_not_of(ws); + return s.substr(start, end - start + 1); } - std::string line; - std::string section = "main"; + std::string with_trailing_slash(std::string s) { + if (!s.empty() && s.back() != '/') { + s += '/'; + } + return s; + } - while (std::getline(in, line)) { - line = trim(line); + bool parse_bool(const std::string& v, bool fallback) { + std::string lower; + for (char c : v) { + lower += static_cast(std::tolower(static_cast(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] == ';') { - continue; + 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; } - if (line.front() == '[' && line.back() == ']') { - std::string name = trim(line.substr(1, line.length() - 2)); - section = name.empty() ? "main" : name; - continue; - } + std::string line; + std::string section = "main"; - size_t eq = line.find('='); - if (eq == std::string::npos) { - continue; - } + while (std::getline(in, line)) { + line = trim(line); - std::string key = trim(line.substr(0, eq)); - std::string value = trim(line.substr(eq + 1)); - if (key.empty()) { - continue; - } + if (line.empty() || line[0] == '#' || line[0] == ';') { + 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 dpm_core { - -void set_error(dpm_ctx* ctx, const std::string& 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; + void set_error(dpm_ctx* ctx, const std::string& msg) { + if (ctx) { + ctx->last_error = msg; + } } - for (const auto& entry : fs::directory_iterator(ctx->config_dir, ec)) { - if (ec) { - break; + void load_config_dir(dpm_ctx* ctx) { + std::error_code ec; + 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 extern "C" { - -dpm_ctx* dpm_open(const dpm_open_overrides* overrides) -{ - /* Validate explicit overrides before allocating anything. */ - if (overrides) { - if (overrides->config_dir && *overrides->config_dir) { - std::error_code ec; - if (!fs::is_directory(overrides->config_dir, ec)) { + dpm_ctx* dpm_open(const dpm_open_overrides* overrides) { + /* Validate explicit overrides before allocating anything. */ + if (overrides) { + if (overrides->config_dir && *overrides->config_dir) { + std::error_code ec; + if (!fs::is_directory(overrides->config_dir, ec)) { + return nullptr; + } + } + if (overrides->log_level < -1 || overrides->log_level > DPM_LOG_DEBUG) { 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; } - } - dpm_ctx* ctx = new (std::nothrow) dpm_ctx; - if (!ctx) { - return nullptr; - } - - /* 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); + /* 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); } - } - delete ctx; -} -const char* dpm_config_get(dpm_ctx* ctx, const char* module, - const char* section, const char* key) -{ - if (!ctx || !module || !section || !key) { - return nullptr; + 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; } - auto mod_it = ctx->config.find(module); - if (mod_it == ctx->config.end()) { - return nullptr; + 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; } - auto sec_it = mod_it->second.find(section); - if (sec_it == mod_it->second.end()) { - return nullptr; + const char* dpm_config_get(dpm_ctx* ctx, const char* module, + const char* section, const char* key) { + 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); - if (key_it == sec_it->second.end()) { - return nullptr; - } + 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; + } - return key_it->second.c_str(); -} - -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"; + if (level <= DPM_LOG_WARN) { + std::fprintf(stderr, "%s: %s\n", level_name(level), message); } else { - /* Log-file trouble never fails the operation; disable and - continue on console alone. */ - ctx->write_to_log = false; + 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 { + /* 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) -{ - if (!ctx) { - return nullptr; + const char* dpm_module_path(dpm_ctx* ctx) { + if (!ctx) { + return nullptr; + } + return ctx->module_path.c_str(); } - return ctx->module_path.c_str(); -} -const char* dpm_last_error(dpm_ctx* ctx) -{ - if (!ctx || ctx->last_error.empty()) { - return nullptr; + const char* dpm_last_error(dpm_ctx* ctx) { + if (!ctx || ctx->last_error.empty()) { + return nullptr; + } + return ctx->last_error.c_str(); } - return ctx->last_error.c_str(); -} - } /* extern "C" */ diff --git a/src/core/modules.cpp b/src/core/modules.cpp index f10fb50..b0bbf98 100644 --- a/src/core/modules.cpp +++ b/src/core/modules.cpp @@ -41,225 +41,210 @@ namespace fs = std::filesystem; /* dlclose wrapper referenced from context.cpp so handle release stays in one translation unit with the loader. */ -void dpm_internal_unload(void* handle) -{ +void dpm_internal_unload(void* handle) { if (handle) { dlclose(handle); } } 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**); -using string_fn = const char* (*)(void); - -/** dlsym with dlerror() discipline; returns nullptr on any error. */ -void* resolve(void* handle, const char* symbol) -{ - dlerror(); - void* addr = dlsym(handle, symbol); - if (dlerror() != nullptr) { - return nullptr; + /** dlsym with dlerror() discipline; returns nullptr on any error. */ + void* resolve(void* handle, const char* symbol) { + dlerror(); + void* addr = dlsym(handle, symbol); + if (dlerror() != nullptr) { + return nullptr; + } + return addr; } - return addr; -} - } // namespace namespace dpm_core { + std::unique_ptr 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 validate_and_load(dpm_ctx* ctx, - const std::string& name, - std::string& reason) -{ - 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; + std::error_code ec; + if (!fs::exists(so_path, ec)) { + reason = "not found at " + so_path; + return nullptr; } - } - if (!missing.empty()) { - reason = "missing required contract symbols: " + missing; - dlclose(handle); - return nullptr; - } - auto exec_f = reinterpret_cast(resolve(handle, "dpm_module_execute")); - auto version_f = reinterpret_cast(resolve(handle, "dpm_module_version")); - auto desc_f = reinterpret_cast(resolve(handle, "dpm_module_description")); + 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 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; + /* 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(resolve(handle, "dpm_module_execute")); + auto version_f = reinterpret_cast(resolve(handle, "dpm_module_version")); + auto desc_f = reinterpret_cast(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(); + 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(); - mod->name = name; - mod->handle = handle; - mod->version = version; - mod->description = description; - mod->execute = exec_f; - return mod; -} - } // namespace dpm_core extern "C" { - -dpm_module* dpm_require(dpm_ctx* ctx, const char* name) -{ - 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 names; - for (const auto& entry : fs::directory_iterator(ctx->module_path, ec)) { - if (ec) { - break; + dpm_module* dpm_require(dpm_ctx* ctx, const char* name) { + if (!ctx || !name || !*name) { + return nullptr; } - 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); + return it->second.get(); } - 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); + 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; } - 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) -{ - if (!cur || !out || cur->idx >= cur->infos.size()) { - return 1; + out->name = mod->name.c_str(); + out->version = mod->version.c_str(); + out->description = mod->description.c_str(); + return 0; } - *out = cur->infos[cur->idx]; - cur->idx++; - return 0; -} -void dpm_cursor_free(dpm_cursor* cur) -{ - delete cur; -} + 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 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" */ diff --git a/src/core/version.cpp b/src/core/version.cpp index 0a43e28..a6839cb 100644 --- a/src/core/version.cpp +++ b/src/core/version.cpp @@ -30,44 +30,40 @@ /** libdpm-core.so's own version. */ #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; } namespace dpm_core { - -bool parse_version(const char* s, long out[3]) -{ - if (!s || !*s) { - return false; - } - - const char* p = s; - for (int i = 0; i < 3; i++) { - if (!std::isdigit(static_cast(*p))) { + bool parse_version(const char* s, long out[3]) { + if (!s || !*s) { return false; } - char* end = nullptr; - out[i] = std::strtol(p, &end, 10); - if (out[i] < 0) { - return false; + const char* p = s; + for (int i = 0; i < 3; i++) { + if (!std::isdigit(static_cast(*p))) { + 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) { - if (*end != '.') { - return false; - } - p = end + 1; - } else { - if (*end != '\0') { - return false; - } - } + return true; } - - return true; -} - } // namespace dpm_core diff --git a/tests/fixtures/src/bad_version.cpp b/tests/fixtures/src/bad_version.cpp index e29ca58..1d4fb26 100644 --- a/tests/fixtures/src/bad_version.cpp +++ b/tests/fixtures/src/bad_version.cpp @@ -20,19 +20,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -extern "C" const char* dpm_module_version(void) -{ +extern "C" const char* dpm_module_version(void) { return "banana"; } -extern "C" const char* dpm_module_description(void) -{ +extern "C" const char* dpm_module_description(void) { return "Fixture with a malformed version."; } extern "C" int dpm_module_execute(void* ctx, const char* command, - int argc, char** argv) -{ + int argc, char** argv) { (void)ctx; (void)command; (void)argc; diff --git a/tests/fixtures/src/good.cpp b/tests/fixtures/src/good.cpp index fffc07c..9f99fd3 100644 --- a/tests/fixtures/src/good.cpp +++ b/tests/fixtures/src/good.cpp @@ -24,21 +24,18 @@ */ #include -extern "C" const char* dpm_module_version(void) -{ +extern "C" const char* dpm_module_version(void) { 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."; } /* Answers "ping" with 42 so a dispatch round trip is observable, and 0 for anything else. */ extern "C" int dpm_module_execute(void* ctx, const char* command, - int argc, char** argv) -{ + int argc, char** argv) { (void)ctx; (void)argc; (void)argv; diff --git a/tests/fixtures/src/missing_symbols.cpp b/tests/fixtures/src/missing_symbols.cpp index acd0e2d..a51dd6e 100644 --- a/tests/fixtures/src/missing_symbols.cpp +++ b/tests/fixtures/src/missing_symbols.cpp @@ -21,12 +21,10 @@ * along with this program. If not, see . */ -extern "C" const char* dpm_module_version(void) -{ +extern "C" const char* dpm_module_version(void) { 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."; } diff --git a/tests/test_core.cpp b/tests/test_core.cpp index f15ea29..dfa22a7 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -50,14 +50,12 @@ static int g_checks = 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); return err != nullptr && std::strstr(err, needle) != nullptr; } -int main(void) -{ +int main(void) { /* ---- dpm_open: invalid explicit override refuses ---- */ { dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1};