/** * @file core.h * @brief Public C API for libdpm-core * * The single entry point for all DPM consumers: the dpm CLI, modules, * and external programs (build systems, Dark Horse tooling). All types * crossing this boundary are C types; state passes through opaque * handles; errors are int codes with per-context detail strings. * * @copyright Copyright (c) 2026 SILO GROUP LLC * @author Chris Punches * * 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 . */ #ifndef DPM_CORE_H #define DPM_CORE_H #include #include #ifdef __cplusplus extern "C" { #endif /* ------------------------------------------------------------------ */ /* Export annotation */ /* ------------------------------------------------------------------ */ /** * @brief Marks a declaration as part of the public ABI * * The library is compiled with hidden default symbol visibility; the * declarations carrying this are its entire exported surface. It sits * on its own line above the declaration it applies to, so it reads as * an annotation rather than as part of the return type. */ #ifndef DPM_PUBLIC_ABI_EXPORT #define DPM_PUBLIC_ABI_EXPORT __attribute__((visibility("default"))) #endif /* ------------------------------------------------------------------ */ /* Opaque handles */ /* ------------------------------------------------------------------ */ /** * @brief A libdpm-core.so context * * Everything a consumer does happens through one of these. It carries * the resolved configuration, the module path, the target root, the log * targets, and the modules loaded so far. * * Obtained from dpm_open() and released by dpm_close(). The context owns * everything it hands out: every string and handle a caller receives * from it stays valid until it is closed, and the caller frees none of * them. * * Several contexts may be open at once, each with its own target root. */ typedef struct dpm_ctx dpm_ctx; /** * @brief A loaded, fully validated module * * Obtained from dpm_require(). A handle is issued only for a module that * passed validation completely, so holding one means the contract is * satisfied. * * Owned by the context that loaded it: a module is loaded at most once * per context, repeated requests return the same handle, and closing the * context unloads it. */ typedef struct dpm_module dpm_module; /** * @brief A cursor over the valid modules in the module path * * Obtained from dpm_list_modules(), advanced with dpm_cursor_next(), and * released with dpm_cursor_free(). * * The cursor holds its own copy of what it reports, so releasing it * leaves the modules loaded and the strings it produced valid until the * context closes. */ typedef struct dpm_cursor dpm_cursor; /* ------------------------------------------------------------------ */ /* Log levels */ /* ------------------------------------------------------------------ */ /** * @brief Severity levels accepted by dpm_log() * * A message at a level above the context's configured level is dropped. */ enum { /** Unrecoverable failure. */ DPM_LOG_FATAL = 0, /** Operation failed. */ DPM_LOG_ERROR = 1, /** Operation continued, something was wrong. */ DPM_LOG_WARN = 2, /** Normal reporting; the default level. */ DPM_LOG_INFO = 3, /** Detail for diagnosing behaviour. */ DPM_LOG_DEBUG = 4 }; /* ------------------------------------------------------------------ */ /* Context configuration overrides */ /* ------------------------------------------------------------------ */ /** * @brief Overrides for dpm_open() * * Any field may be left NULL (or -1 for log_level) to accept * configuration-file values and built-in defaults. Every field here is * exposed as a flag on the dpm binary. */ typedef struct dpm_open_overrides { /** NULL selects /etc/dpm/conf.d/. */ const char* config_dir; /** NULL selects the configured value, then the built-in default. */ const char* module_path; /** NULL selects "/", the target root for package operations. */ const char* root; /** -1 selects the configured value; otherwise a DPM_LOG_* level. */ int log_level; } dpm_open_overrides; /* ------------------------------------------------------------------ */ /* Module information (enumeration results) */ /* ------------------------------------------------------------------ */ /** * @brief What the library read from a module at load * * Filled by dpm_get_module_info() and by dpm_cursor_next(). The string * pointers remain valid until the context is closed. */ typedef struct dpm_module_info { /** Module name, its filename minus .so. */ const char* name; /** The module's own X.Y.Z. */ const char* version; /** One-line description. */ const char* description; } dpm_module_info; /* ------------------------------------------------------------------ */ /* Context lifecycle */ /* ------------------------------------------------------------------ */ /** * @brief Creates a libdpm-core.so context * * Reads configuration from /etc/dpm/conf.d/ (or the overridden config * directory), resolves the module path (override > config > built-in * default), and initializes logging per configuration. Performs no * module loading. Multiple simultaneous contexts with different roots * are legal. * * @param overrides Optional overrides; NULL accepts configuration * values and built-in defaults * @return A context handle, or NULL on allocation failure or an * unreadable/invalid explicit override (a missing default * config directory is not an error) */ DPM_PUBLIC_ABI_EXPORT dpm_ctx* dpm_open(const dpm_open_overrides* overrides); /** * @brief Releases a libdpm-core.so context * * Unloads every module handle the context issued, closes log targets, * and frees all memory owned by the context. All handles and strings * obtained through the context are invalid after this call. * * @param ctx The context to release; NULL is a no-op */ DPM_PUBLIC_ABI_EXPORT void dpm_close(dpm_ctx* ctx); /* ------------------------------------------------------------------ */ /* Module acquisition */ /* ------------------------------------------------------------------ */ /** * @brief Loads and returns a validated module * * Resolves the named module in the module path and runs the full * load-time validation sequence if it is not already loaded in this * context. Modules are loaded at most once per context; repeated * calls return the same handle. * * Version compatibility is the caller's judgement: read the loaded * module's version with dpm_get_module_info() and decide whether it is * acceptable. * * @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_get_last_error() */ DPM_PUBLIC_ABI_EXPORT dpm_module* dpm_require(dpm_ctx* ctx, const char* name); /** * @brief Reports what the library sees in a loaded module * * Fills `out` with the module's name, version, and description, * exactly as they were read at load. The caller decides whether the * version it is looking at suits its purposes. * * @param ctx The libdpm-core.so context * @param mod A module handle from dpm_require() * @param out Receives the module's information; the string pointers * remain valid until context close * @return 0 on success, nonzero if the module cannot be reported on */ DPM_PUBLIC_ABI_EXPORT int dpm_get_module_info(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out); /** * @brief Dispatches a command to a module * * Invokes the module's dpm_module_execute with the context, the * command, and the argument vector. argv[0] is the command when * argc > 0; semantics beyond that are the module's to define. * * This is the only path into module code. A caller addresses a module * by name and a capability by command string, so it holds no * compile-time knowledge of the module it is calling — the same call * a module makes to reach a peer. * * @param ctx The libdpm-core.so context * @param mod A module handle from dpm_require() * @param command The command name; NULL or empty behaves as the * module's help command * @param argc Number of arguments * @param argv Argument vector * @return The module's return value verbatim; 0 on success */ DPM_PUBLIC_ABI_EXPORT int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command, int argc, char** argv); /* ------------------------------------------------------------------ */ /* Enumeration */ /* ------------------------------------------------------------------ */ /** * @brief Enumerates the valid modules in the module path * * Scans the module path and validates each candidate .so; failures * are logged and excluded from the results. * * @param ctx The libdpm-core.so context * @return A cursor over all valid modules, or NULL on an unreadable * module path */ DPM_PUBLIC_ABI_EXPORT dpm_cursor* dpm_list_modules(dpm_ctx* ctx); /** * @brief Advances an enumeration cursor * * Fills `out` with the next module's name, version, and description; * the string pointers remain valid until context close. * * @param cur The cursor from dpm_list_modules() * @param out Receives the next module's information * @return 0 while entries remain; nonzero at end */ DPM_PUBLIC_ABI_EXPORT int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out); /** * @brief Releases an enumeration cursor * * @param cur The cursor to release; NULL is a no-op */ DPM_PUBLIC_ABI_EXPORT void dpm_cursor_free(dpm_cursor* cur); /* ------------------------------------------------------------------ */ /* Services (available to modules and external consumers alike) */ /* ------------------------------------------------------------------ */ /** * @brief Returns the version of libdpm-core.so * * @return The libdpm-core.so version as a static X.Y.Z string; callable * without a context */ DPM_PUBLIC_ABI_EXPORT const char* dpm_core_version(void); /** * @brief Returns a configuration value from a module's namespace * * A module's configuration namespace is its own .conf file under the * context's configuration directory; the namespace named "core", from * core.conf, is the library's own. * * @param ctx The libdpm-core.so context * @param module The configuration namespace to read * @param section The section name within the file * @param key The key within the section * @return The configured value, or NULL if unset; valid until * context close */ DPM_PUBLIC_ABI_EXPORT const char* dpm_config_get(dpm_ctx* ctx, const char* module, const char* section, const char* key); /** * @brief Writes a message to the context's configured log targets * * Targets are the console and, when configured, the log file. * Messages above the configured level are dropped. * * @param ctx The libdpm-core.so context * @param level The severity (DPM_LOG_FATAL through DPM_LOG_DEBUG) * @param message The message to log; NULL is a no-op */ DPM_PUBLIC_ABI_EXPORT void dpm_log(dpm_ctx* ctx, int level, const char* message); /** * @brief Returns the resolved module directory path * * @param ctx The libdpm-core.so context * @return The module directory path this context resolved */ DPM_PUBLIC_ABI_EXPORT 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 * * @param ctx The libdpm-core.so context * @return A human-readable description of the most recent failure, or * NULL if none; overwritten by the next failing call */ DPM_PUBLIC_ABI_EXPORT const char* dpm_get_last_error(dpm_ctx* ctx); /* ------------------------------------------------------------------ */ /* Module contract (implemented by modules, called by libdpm-core.so) */ /* ------------------------------------------------------------------ */ /* * Every module exports, as extern "C": * * int dpm_module_execute(dpm_ctx* ctx, const char* command, * int argc, char** argv); * const char* dpm_module_version(void); * const char* dpm_module_description(void); * * dpm_module_execute is the only entry through which a module performs * work. The other two are what it reports about itself; the library * reads both at load and serves them through dpm_get_module_info. */ #ifdef __cplusplus } #endif #endif /* DPM_CORE_H */