Files
dpm-core-ng/include/dpm/core.h
Christopher M. Punches cafdeefa11 Describe dpm_module_execute as the entry point, not the whole surface
A module's reported version is consumed: a calling module reads it
through dpm_module_info_of and decides from it whether to issue a
command. Calling dpm_module_execute the module's entire functional
surface contradicted that.

dpm_module_execute is the only entry through which a module performs
work. dpm_module_version and dpm_module_description are what it reports
about itself, read at load and served to consumers, and the contract
page in the public header now says so.
2026-08-15 20:51:44 -04:00

390 lines
14 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 the public API visible
*
* The library is compiled with hidden default symbol visibility; the
* functions carrying this 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 */
/* ------------------------------------------------------------------ */
/**
* @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 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: 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 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 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, 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_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) */
/* ------------------------------------------------------------------ */
/**
* @mainpage libdpm-core.so — API Reference
*
* This is the generated reference for libdpm-core.so, extracted from
* the source. It covers the public C API in <dpm/core.h>, the library's
* internals, and the module contract.
*
* Start at core.h for the API a consumer or a module calls, and at
* @ref module_contract for what a module implements.
*
* The project's prose documentation — overview, design, build, module
* authoring, and consumer guide — lives in the docs/ directory of the
* repository and is read there.
*/
/**
* @page module_contract The Module Contract
*
* A module is one .so in the module directory. It includes
* <dpm/core.h>, links -ldpm-core, and exports three reserved symbols as
* extern "C":
*
* @code
* 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);
* @endcode
*
* @section module_contract_surface The Functional Surface
*
* dpm_module_execute is the only entry through which a module performs
* work; its capabilities are addressed by command string, so a module
* publishes no headers, struct layouts, or symbols to anything that
* calls it. NULL or an empty command behaves as the module's help
* command.
*
* dpm_module_version and dpm_module_description are what a module
* reports about itself. The library reads both at load and serves them
* to consumers through dpm_module_info_of, where the version is what a
* calling module judges before deciding to issue a command.
*
* @section module_contract_version Version Compatibility
*
* A module determines for itself whether it can work with the library
* it is running against: dpm_core_version() reports the running
* version, and the module proceeds or fails on its own judgement. A
* module is built against the system-installed libdpm-core.so and is
* responsible for being correct against it.
*
* The same holds toward a peer. A module requires the peer by name,
* reads the version the library reports, and decides whether that
* version suits the commands it intends to issue.
*
* @section module_contract_validation Validation
*
* The library refuses to load any module that does not validate
* completely: every reserved symbol resolves, and the version and
* description probes return well-formed values. A module that loads is
* fully valid.
*/
#ifdef __cplusplus
}
#endif
#endif /* DPM_CORE_H */