Files
dpm-core-ng/include/dpm/core.h
Christopher M. Punches 97b39cac6c Route all module interaction through dispatch
A module is addressed by name and command string, and nothing else.
Typed API access handed a caller a pointer into the callee's function
table, which meant compiling against that module's struct layout — a
build-time dependency between modules that the design does not permit.
Removing it also removes the manifest, the table magic constant, and
the table size field, which existed only to describe and validate
those tables.

Load validation is now three steps: reserved contract symbols resolve,
the minimum-version handshake passes, and the version and description
probes return well-formed values. The contract is four reserved
symbols, and a module's interface is the command vocabulary it
documents.

Documentation is brought in line, and artifacts are named exactly:
libdpm-core.so for the library, <dpm/core.h> for the header, the dpm
binary for the command-line tool.
2026-08-15 02:15:18 -04:00

313 lines
12 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 */
/* ------------------------------------------------------------------ */
/**
* Marks the public API visible. The library is compiled with hidden
* default symbol visibility; these functions are its entire exported
* surface.
*/
#ifndef DPM_API
#define DPM_API __attribute__((visibility("default")))
#endif
/* ------------------------------------------------------------------ */
/* Opaque handles */
/* ------------------------------------------------------------------ */
typedef struct dpm_ctx dpm_ctx;
typedef struct dpm_module dpm_module;
typedef struct dpm_cursor dpm_cursor;
/* ------------------------------------------------------------------ */
/* Log levels */
/* ------------------------------------------------------------------ */
enum {
DPM_LOG_FATAL = 0,
DPM_LOG_ERROR = 1,
DPM_LOG_WARN = 2,
DPM_LOG_INFO = 3,
DPM_LOG_DEBUG = 4
};
/* ------------------------------------------------------------------ */
/* Context configuration overrides */
/* ------------------------------------------------------------------ */
/**
* 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 dpm CLI flag.
*/
typedef struct dpm_open_overrides {
const char* config_dir; /* NULL = /etc/dpm/conf.d/ */
const char* module_path; /* NULL = config value, then built-in */
const char* root; /* NULL = "/" (target root for pkg ops) */
int log_level; /* -1 = config value; else DPM_LOG_* */
} dpm_open_overrides;
/* ------------------------------------------------------------------ */
/* Module information (enumeration results) */
/* ------------------------------------------------------------------ */
typedef struct dpm_module_info {
const char* name; /* module name (filename minus .so) */
const char* version; /* module's own X.Y.Z */
const char* description; /* one-line description */
const char* core_min; /* minimum libdpm-core version it supports */
} dpm_module_info;
/* ------------------------------------------------------------------ */
/* Context lifecycle */
/* ------------------------------------------------------------------ */
/**
* @brief Creates a libdpm-core 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_API dpm_ctx* dpm_open(const dpm_open_overrides* overrides);
/**
* @brief Releases a libdpm-core 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_API 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, not this library's:
* read the loaded module's version with dpm_module_info_of() and
* decide whether it is acceptable.
*
* @param ctx The libdpm-core 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_API dpm_module* dpm_require(dpm_ctx* ctx, const char* name);
/**
* @brief Reports what libdpm-core sees in a loaded module
*
* Fills `out` with the module's name, version, description, and
* minimum-libdpm-core version, exactly as they were read at load. No
* compatibility conclusion is drawn from these values; the caller
* decides whether the version it is looking at is too new or too
* old for its purposes.
*
* @param ctx The libdpm-core 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_API 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 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_API 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 context
* @return A cursor over all valid modules, or NULL on an unreadable
* module path
*/
DPM_API dpm_cursor* dpm_list_modules(dpm_ctx* ctx);
/**
* @brief Advances an enumeration cursor
*
* Fills `out` with the next module's name, version, description, and
* minimum-libdpm-core version; 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_API 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_API void dpm_cursor_free(dpm_cursor* cur);
/* ------------------------------------------------------------------ */
/* Services (available to modules and external consumers alike) */
/* ------------------------------------------------------------------ */
/**
* @brief Returns libdpm-core's own version
*
* @return The libdpm-core version as a static X.Y.Z string; callable
* without a context
*/
DPM_API 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 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_API 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 context
* @param level The severity (DPM_LOG_FATAL through DPM_LOG_DEBUG)
* @param message The message to log; NULL is a no-op
*/
DPM_API void dpm_log(dpm_ctx* ctx, int level, const char* message);
/**
* @brief Returns the resolved module directory path
*
* @param ctx The libdpm-core context
* @return The module directory path this context resolved
*/
DPM_API const char* dpm_module_path(dpm_ctx* ctx);
/**
* @brief Returns the most recent failure recorded on the context
*
* @param ctx The libdpm-core context
* @return A human-readable description of the most recent failure, or
* NULL if none; overwritten by the next failing call
*/
DPM_API const char* dpm_last_error(dpm_ctx* ctx);
/* ------------------------------------------------------------------ */
/* Module contract (implemented by modules, called by libdpm-core) */
/* ------------------------------------------------------------------ */
/*
* 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_core_min(void);
*
* dpm_module_execute is the module's entire functional surface; its
* capabilities are addressed by command string, so a module publishes
* no headers, struct layouts, or symbols to anything that calls it.
* The library refuses to load any module that does not validate
* completely (see the DPM specification: load-time enforcement).
*/
#ifdef __cplusplus
}
#endif
#endif /* DPM_CORE_H */