Initial commit: DPM core library, CLI, bundled info module, and tests

This commit is contained in:
2026-08-09 18:00:05 -04:00
commit 19cf285cb7
29 changed files with 3722 additions and 0 deletions

335
include/dpm/core.h Normal file
View File

@@ -0,0 +1,335 @@
/**
* @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 core version it supports */
} dpm_module_info;
/* ------------------------------------------------------------------ */
/* Module contract structures (layout fixed by the DPM spec) */
/* ------------------------------------------------------------------ */
/** Magic constant opening every module API table. */
#define DPM_API_TABLE_MAGIC 0x314D5044u /* "DPM1" */
/**
* Every module API table begins with this header: the magic constant,
* then the full size in bytes of the table struct as the exporting
* module compiled it (permits tail-extension within a table version).
*/
typedef struct dpm_api_table_header {
uint32_t magic;
uint32_t size;
} dpm_api_table_header;
/** One entry of a module's declared functional surface. */
typedef struct dpm_manifest_entry {
const char* api_name; /* e.g. "raw" */
int table_version; /* e.g. 1 */
const char* symbol; /* exact exported symbol carrying the
table, e.g. "raw_api_v1" */
} dpm_manifest_entry;
/** A module's manifest: its entire declared functional surface. */
typedef struct dpm_manifest {
uint32_t count;
const dpm_manifest_entry* entries;
} dpm_manifest;
/* ------------------------------------------------------------------ */
/* Context lifecycle */
/* ------------------------------------------------------------------ */
/**
* @brief Creates a 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 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.
*
* @param ctx The core context
* @param name The module name (its filename minus .so)
* @param min_version Minimum acceptable module version as X.Y.Z;
* NULL accepts any version
* @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,
const char* min_version);
/**
* @brief Returns a module's API table for direct typed calls
*
* The table was already validated (manifest cross-check, magic,
* minimum size) at module load. The caller casts the pointer to the
* table struct type for that API and version. The table is valid for
* the life of the context.
*
* @param ctx The core context
* @param mod A module handle from dpm_require()
* @param api_name The API name as declared in the module's manifest
* @param table_version The table version to retrieve
* @return The table pointer, or NULL if the module does not provide
* that api/version pair
*/
DPM_API const void* dpm_get_api(dpm_ctx* ctx, dpm_module* mod,
const char* api_name, int table_version);
/**
* @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.
*
* @param ctx The 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 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-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 core's own version
*
* @return Core's 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; "core" names core's own file.
*
* @param ctx The 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 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 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 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 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);
* const dpm_manifest* dpm_module_manifest(void);
*
* plus one exported table symbol per manifest entry. Core refuses to
* load any module that does not validate completely (see the DPM
* specification: load-time enforcement).
*/
#ifdef __cplusplus
}
#endif
#endif /* DPM_CORE_H */

View File

@@ -0,0 +1,74 @@
/**
* @file context.hpp
* @brief The core context: configuration, logging, module registry
*
* @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/>.
*/
#pragma once
#include <dpm/core.h>
#include <map>
#include <memory>
#include <string>
#include "internal/modules.hpp"
/** @brief A core context: configuration, logging, and the module registry */
struct dpm_ctx {
std::string config_dir;
std::string module_path;
std::string root;
int log_level = DPM_LOG_INFO;
bool write_to_log = false;
std::string log_file;
/* config[module][section][key] = value */
std::map<std::string,
std::map<std::string,
std::map<std::string, std::string>>> config;
/* validated modules, keyed by name; loaded at most once per ctx */
std::map<std::string, std::unique_ptr<dpm_module>> modules;
std::string last_error;
};
namespace dpmcore {
/**
* @brief Records a failure reason on the context
*
* @param ctx The core context; NULL is a no-op
* @param msg The failure description
*/
void set_error(dpm_ctx* ctx, const std::string& msg);
/**
* @brief Loads all configuration files into the context
*
* Parses every .conf file in the context's configuration directory
* into the context's configuration store.
*
* @param ctx The core context
*/
void load_config_dir(dpm_ctx* ctx);
} // namespace dpmcore

View File

@@ -0,0 +1,74 @@
/**
* @file modules.hpp
* @brief Module handle, cursor, and loader declarations
*
* @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/>.
*/
#pragma once
#include <dpm/core.h>
#include <memory>
#include <string>
#include <vector>
/** @brief A loaded, fully validated module */
struct dpm_module {
std::string name;
void* handle = nullptr;
std::string version;
std::string description;
std::string core_min;
const dpm_manifest* manifest = nullptr;
int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr;
};
/** @brief Enumeration cursor over validated modules */
struct dpm_cursor {
std::vector<dpm_module_info> infos;
size_t idx = 0;
};
/**
* @brief Closes a dlopen handle
*
* Keeps handle release beside the loader in one translation unit.
*
* @param handle The handle to close; NULL is a no-op
*/
void dpm_internal_unload(void* handle);
namespace dpmcore {
/**
* @brief Runs the full load-time validation sequence against a module
*
* Loads the named module's .so from the context's module path and
* verifies the complete contract.
*
* @param ctx The core context
* @param name The module name
* @param reason Receives the refusal reason on failure
* @return The validated module (caller owns), or nullptr on failure
*/
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
const std::string& name,
std::string& reason);
} // namespace dpmcore

View File

@@ -0,0 +1,45 @@
/**
* @file version.hpp
* @brief X.Y.Z version parsing and comparison
*
* @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/>.
*/
#pragma once
namespace dpmcore {
/**
* @brief Parses a strict X.Y.Z version string
*
* @param s The version string
* @param out Receives the three parsed components
* @return true on success; false on any malformation
*/
bool parse_version(const char* s, long out[3]);
/**
* @brief Compares two valid X.Y.Z version strings
*
* @param a First version
* @param b Second version
* @return -1 if a < b, 0 if equal, 1 if a > b
*/
int compare_versions(const char* a, const char* b);
} // namespace dpmcore