Files
dpm-core-ng/include/dpm/core.h
Christopher M. Punches 50b95fb729 Document the namespaces and the opaque handles
dpm_core carries a namespace block saying what it holds and why its
members keep external linkage while staying off the export table. Each
unnamed namespace says its contents are private to that translation
unit, and the one in the info module says why it has to be unnamed
there: a module is built without hidden visibility or a version script,
so a helper with external linkage would reach that module's exports.

The three opaque handles in the public header were undocumented. What
documentation they had came from the struct definitions in
include/internal, which a consumer never sees.

EXTRACT_ANON_NSPACES was off, so the documentation on every file-private
helper was written and discarded. Doxygen also capitalizes the first
letter of a brief, which turned libdpm-core.so into Libdpm-core.so
wherever a brief opened with it; those are reworded.

Bare references to the library are replaced with the artifact name
throughout, including three in the info module's output. The
cli_info_version regex follows.
2026-08-16 01:42:04 -04:00

393 lines
13 KiB
C

/**
* @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 <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DPM_CORE_H
#define DPM_CORE_H
#include <stddef.h>
#include <stdint.h>
#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_module_info_of() 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_module_info_of() 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_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_module_info_of(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_module_path(dpm_ctx* ctx);
/**
* @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_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_module_info_of.
*/
#ifdef __cplusplus
}
#endif
#endif /* DPM_CORE_H */