/** * @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; /** * @brief A cursor over recorded module aliases * * Obtained from dpm_list_module_aliases(), advanced with * dpm_alias_cursor_next(), and released with dpm_alias_cursor_free(). * * The cursor holds its own copy of what it reports, so it stays readable * after the alias table is changed, and releasing it leaves the table * untouched. */ typedef struct dpm_alias_cursor dpm_alias_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; /** NULL selects the configured value, then the built-in default. */ const char* metadata_dir; } 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; /** * @brief One recorded alias * * Filled by dpm_alias_cursor_next(). The string pointers remain valid * until the cursor is released. */ typedef struct dpm_alias_info { /** The alternate name. */ const char* alias; /** The module that name resolves to. */ const char* module; } dpm_alias_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 name to a module file 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. * * The name is resolved against the installed modules first, then the * alias table, then the module path directly. Reaching the module path * means the module is not installed, which is logged and does not stop * the load. * * 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); /* ------------------------------------------------------------------ */ /* Installation */ /* ------------------------------------------------------------------ */ /** * @brief Records what a module reports about itself * * Loads the named module once, reads its version, description, and * declared aliases, and writes them into the metadata directory. From * then on dpm_list_modules() reports that module from the record and * opens nothing. * * Each declared alias is added when the name is free. A name already * taken keeps its existing meaning and the installation continues. * * @param ctx The libdpm-core.so context * @param name The module name, its filename minus .so * @return 0 on success, nonzero on failure with the reason available * from dpm_get_last_error() */ DPM_PUBLIC_ABI_EXPORT int dpm_install_module(dpm_ctx* ctx, const char* name); /** * @brief Removes a module's record and every alias resolving to it * * Leaves the module file itself in place. The module is thereafter * reported as uninstalled by dpm_list_modules(), and dpm_require() still * loads it by its own name. * * @param ctx The libdpm-core.so context * @param name The module name * @return 0 on success, nonzero on failure with the reason available * from dpm_get_last_error() */ DPM_PUBLIC_ABI_EXPORT int dpm_uninstall_module(dpm_ctx* ctx, const char* name); /* ------------------------------------------------------------------ */ /* Aliases */ /* ------------------------------------------------------------------ */ /** * @brief Records an alternate name for a module * * A name is recorded once. A name already serving as an alias, or * already belonging to an installed module, is refused, so an existing * route to a module is never replaced by a later one. Changing where an * alias points is a removal followed by an addition. * * @param ctx The libdpm-core.so context * @param module The module the name resolves to * @param alias The alternate name * @return 0 on success, nonzero on failure with the reason available * from dpm_get_last_error() */ DPM_PUBLIC_ABI_EXPORT int dpm_add_module_alias(dpm_ctx* ctx, const char* module, const char* alias); /** * @brief Removes an alternate name * * @param ctx The libdpm-core.so context * @param alias The alternate name, which identifies the entry on its own * @return 0 on success, nonzero on failure with the reason available * from dpm_get_last_error() */ DPM_PUBLIC_ABI_EXPORT int dpm_remove_module_alias(dpm_ctx* ctx, const char* alias); /** * @brief Enumerates recorded aliases * * @param ctx The libdpm-core.so context * @param module NULL for every alias on the system, or a module name for * the aliases resolving to it * @return A cursor over the matching aliases, or NULL on failure */ DPM_PUBLIC_ABI_EXPORT dpm_alias_cursor* dpm_list_module_aliases(dpm_ctx* ctx, const char* module); /** * @brief Advances an alias cursor * * @param cur The cursor from dpm_list_module_aliases() * @param out Receives the next alias and the module it resolves to * @return 0 while entries remain; nonzero at end */ DPM_PUBLIC_ABI_EXPORT int dpm_alias_cursor_next(dpm_alias_cursor* cur, dpm_alias_info* out); /** * @brief Releases an alias cursor * * @param cur The cursor to release; NULL is a no-op */ DPM_PUBLIC_ABI_EXPORT void dpm_alias_cursor_free(dpm_alias_cursor* cur); /* ------------------------------------------------------------------ */ /* Enumeration */ /* ------------------------------------------------------------------ */ /** * @brief Enumerates the modules in the module path * * Reports each installed module from its record, and each module * without one with a version of "<uninstalled>" and an empty * description. No module is opened, so nothing in the module path is * executed to produce a listing. * * @param ctx The libdpm-core.so context * @return A cursor over the modules present, 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); * const char* dpm_module_aliases(void); * * dpm_module_execute is the only entry through which a module performs * work. The other three are what it reports about itself; the library * reads them at load, serves the version and description through * dpm_get_module_info, and records the aliases at installation. * * dpm_module_aliases returns a comma-separated list of alternate names * the module answers to, or NULL to declare none. NULL is an answer; the * symbol's absence is a contract violation and the module is refused. */ #ifdef __cplusplus } #endif #endif /* DPM_CORE_H */