Indent every scope and put opening braces on the signature line
Namespace and extern "C" bodies are indented a level, so nesting is visible from the indentation rather than only from the braces. Opening braces stay on the line that opens the scope, including function definitions, which previously carried theirs on a line of their own.
This commit is contained in:
@@ -63,23 +63,21 @@ struct dpm_ctx {
|
|||||||
};
|
};
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
/**
|
||||||
|
* @brief Records a failure reason on the context
|
||||||
|
*
|
||||||
|
* @param ctx The libdpm-core context; NULL is a no-op
|
||||||
|
* @param msg The failure description
|
||||||
|
*/
|
||||||
|
void set_error(dpm_ctx* ctx, const std::string& msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Records a failure reason on the context
|
* @brief Loads all configuration files into the context
|
||||||
*
|
*
|
||||||
* @param ctx The libdpm-core context; NULL is a no-op
|
* Parses every .conf file in the context's configuration directory
|
||||||
* @param msg The failure description
|
* into the context's configuration store.
|
||||||
*/
|
*
|
||||||
void set_error(dpm_ctx* ctx, const std::string& msg);
|
* @param ctx The libdpm-core context
|
||||||
|
*/
|
||||||
/**
|
void load_config_dir(dpm_ctx* ctx);
|
||||||
* @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 libdpm-core context
|
|
||||||
*/
|
|
||||||
void load_config_dir(dpm_ctx* ctx);
|
|
||||||
|
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|||||||
@@ -65,20 +65,18 @@ struct dpm_cursor {
|
|||||||
void dpm_internal_unload(void* handle);
|
void dpm_internal_unload(void* handle);
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
/**
|
||||||
/**
|
* @brief Runs the full load-time validation sequence against a module
|
||||||
* @brief Runs the full load-time validation sequence against a module
|
*
|
||||||
*
|
* Loads the named module's .so from the context's module path and
|
||||||
* Loads the named module's .so from the context's module path and
|
* verifies the complete contract.
|
||||||
* verifies the complete contract.
|
*
|
||||||
*
|
* @param ctx The libdpm-core context
|
||||||
* @param ctx The libdpm-core context
|
* @param name The module name
|
||||||
* @param name The module name
|
* @param reason Receives the refusal reason on failure
|
||||||
* @param reason Receives the refusal reason on failure
|
* @return The validated module (caller owns), or nullptr on failure
|
||||||
* @return The validated module (caller owns), or nullptr on failure
|
*/
|
||||||
*/
|
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||||
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
const std::string& name,
|
||||||
const std::string& name,
|
std::string& reason);
|
||||||
std::string& reason);
|
|
||||||
|
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
/**
|
||||||
/**
|
* @brief Parses a strict X.Y.Z version string
|
||||||
* @brief Parses a strict X.Y.Z version string
|
*
|
||||||
*
|
* @param s The version string
|
||||||
* @param s The version string
|
* @param out Receives the three parsed components
|
||||||
* @param out Receives the three parsed components
|
* @return true on success; false on any malformation
|
||||||
* @return true on success; false on any malformation
|
*/
|
||||||
*/
|
bool parse_version(const char* s, long out[3]);
|
||||||
bool parse_version(const char* s, long out[3]);
|
|
||||||
|
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|||||||
@@ -36,16 +36,14 @@
|
|||||||
/**
|
/**
|
||||||
* @brief Returns the module's own version
|
* @brief Returns the module's own version
|
||||||
*/
|
*/
|
||||||
extern "C" const char* dpm_module_version(void)
|
extern "C" const char* dpm_module_version(void) {
|
||||||
{
|
|
||||||
return INFO_MODULE_VERSION;
|
return INFO_MODULE_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the module's one-line description
|
* @brief Returns the module's one-line description
|
||||||
*/
|
*/
|
||||||
extern "C" const char* dpm_module_description(void)
|
extern "C" const char* dpm_module_description(void) {
|
||||||
{
|
|
||||||
return "Reports and tests libdpm-core functionality.";
|
return "Reports and tests libdpm-core functionality.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +60,7 @@ extern "C" const char* dpm_module_description(void)
|
|||||||
* @return 0 on success, non-zero on failure
|
* @return 0 on success, non-zero on failure
|
||||||
*/
|
*/
|
||||||
extern "C" int dpm_module_execute(dpm_ctx* ctx, const char* command,
|
extern "C" int dpm_module_execute(dpm_ctx* ctx, const char* command,
|
||||||
int argc, char** argv)
|
int argc, char** argv) {
|
||||||
{
|
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
|
|
||||||
|
|||||||
@@ -33,71 +33,66 @@
|
|||||||
#define INFO_VERSION "0.1.0"
|
#define INFO_VERSION "0.1.0"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
std::string detect_architecture() {
|
||||||
std::string detect_architecture()
|
struct utsname system_info;
|
||||||
{
|
if (uname(&system_info) == -1) {
|
||||||
struct utsname system_info;
|
return "Unknown";
|
||||||
if (uname(&system_info) == -1) {
|
}
|
||||||
return "Unknown";
|
return system_info.machine;
|
||||||
}
|
|
||||||
return system_info.machine;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string detect_os()
|
|
||||||
{
|
|
||||||
struct utsname system_info;
|
|
||||||
if (uname(&system_info) == -1) {
|
|
||||||
return "Unknown";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string os = system_info.sysname;
|
std::string detect_os() {
|
||||||
|
struct utsname system_info;
|
||||||
|
if (uname(&system_info) == -1) {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
if (os == "Linux") {
|
std::string os = system_info.sysname;
|
||||||
std::ifstream os_release("/etc/os-release");
|
|
||||||
if (os_release.is_open()) {
|
|
||||||
std::string line;
|
|
||||||
std::string distro_name;
|
|
||||||
std::string distro_version;
|
|
||||||
|
|
||||||
while (std::getline(os_release, line)) {
|
if (os == "Linux") {
|
||||||
if (line.rfind("NAME=", 0) == 0) {
|
std::ifstream os_release("/etc/os-release");
|
||||||
distro_name = line.substr(5);
|
if (os_release.is_open()) {
|
||||||
if (distro_name.size() >= 2 &&
|
std::string line;
|
||||||
distro_name.front() == '"' &&
|
std::string distro_name;
|
||||||
distro_name.back() == '"') {
|
std::string distro_version;
|
||||||
distro_name =
|
|
||||||
distro_name.substr(1, distro_name.length() - 2);
|
while (std::getline(os_release, line)) {
|
||||||
|
if (line.rfind("NAME=", 0) == 0) {
|
||||||
|
distro_name = line.substr(5);
|
||||||
|
if (distro_name.size() >= 2 &&
|
||||||
|
distro_name.front() == '"' &&
|
||||||
|
distro_name.back() == '"') {
|
||||||
|
distro_name =
|
||||||
|
distro_name.substr(1, distro_name.length() - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line.rfind("VERSION_ID=", 0) == 0) {
|
||||||
|
distro_version = line.substr(11);
|
||||||
|
if (distro_version.size() >= 2 &&
|
||||||
|
distro_version.front() == '"' &&
|
||||||
|
distro_version.back() == '"') {
|
||||||
|
distro_version =
|
||||||
|
distro_version.substr(1,
|
||||||
|
distro_version.length() - 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (line.rfind("VERSION_ID=", 0) == 0) {
|
|
||||||
distro_version = line.substr(11);
|
|
||||||
if (distro_version.size() >= 2 &&
|
|
||||||
distro_version.front() == '"' &&
|
|
||||||
distro_version.back() == '"') {
|
|
||||||
distro_version =
|
|
||||||
distro_version.substr(1,
|
|
||||||
distro_version.length() - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!distro_name.empty()) {
|
if (!distro_name.empty()) {
|
||||||
os += " (" + distro_name;
|
os += " (" + distro_name;
|
||||||
if (!distro_version.empty()) {
|
if (!distro_version.empty()) {
|
||||||
os += " " + distro_version;
|
os += " " + distro_version;
|
||||||
|
}
|
||||||
|
os += ")";
|
||||||
}
|
}
|
||||||
os += ")";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Command parse_command(const char* cmd_str)
|
Command parse_command(const char* cmd_str) {
|
||||||
{
|
|
||||||
if (cmd_str == nullptr || std::strlen(cmd_str) == 0) {
|
if (cmd_str == nullptr || std::strlen(cmd_str) == 0) {
|
||||||
return CMD_HELP;
|
return CMD_HELP;
|
||||||
}
|
}
|
||||||
@@ -116,8 +111,7 @@ Command parse_command(const char* cmd_str)
|
|||||||
return CMD_UNKNOWN;
|
return CMD_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_help(dpm_ctx* ctx)
|
int cmd_help(dpm_ctx* ctx) {
|
||||||
{
|
|
||||||
dpm_log(ctx, DPM_LOG_INFO, "DPM Info Module - Reports and tests libdpm-core functionality.");
|
dpm_log(ctx, DPM_LOG_INFO, "DPM Info Module - Reports and tests libdpm-core functionality.");
|
||||||
dpm_log(ctx, DPM_LOG_INFO, "");
|
dpm_log(ctx, DPM_LOG_INFO, "");
|
||||||
dpm_log(ctx, DPM_LOG_INFO, "Available commands:");
|
dpm_log(ctx, DPM_LOG_INFO, "Available commands:");
|
||||||
@@ -130,8 +124,7 @@ int cmd_help(dpm_ctx* ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_version(dpm_ctx* ctx)
|
int cmd_version(dpm_ctx* ctx) {
|
||||||
{
|
|
||||||
std::string core_msg = "libdpm-core Version: ";
|
std::string core_msg = "libdpm-core Version: ";
|
||||||
core_msg += dpm_core_version();
|
core_msg += dpm_core_version();
|
||||||
dpm_log(ctx, DPM_LOG_INFO, core_msg.c_str());
|
dpm_log(ctx, DPM_LOG_INFO, core_msg.c_str());
|
||||||
@@ -143,8 +136,7 @@ int cmd_version(dpm_ctx* ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_system(dpm_ctx* ctx)
|
int cmd_system(dpm_ctx* ctx) {
|
||||||
{
|
|
||||||
dpm_log(ctx, DPM_LOG_INFO, "System Information:");
|
dpm_log(ctx, DPM_LOG_INFO, "System Information:");
|
||||||
|
|
||||||
std::string os_msg = " OS: ";
|
std::string os_msg = " OS: ";
|
||||||
@@ -158,8 +150,7 @@ int cmd_system(dpm_ctx* ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_config(dpm_ctx* ctx)
|
int cmd_config(dpm_ctx* ctx) {
|
||||||
{
|
|
||||||
dpm_log(ctx, DPM_LOG_INFO, "Configuration Information:");
|
dpm_log(ctx, DPM_LOG_INFO, "Configuration Information:");
|
||||||
|
|
||||||
const char* configured_path = dpm_config_get(ctx, "core", "modules", "path");
|
const char* configured_path = dpm_config_get(ctx, "core", "modules", "path");
|
||||||
@@ -175,8 +166,7 @@ int cmd_config(dpm_ctx* ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_unknown(dpm_ctx* ctx, const char* command)
|
int cmd_unknown(dpm_ctx* ctx, const char* command) {
|
||||||
{
|
|
||||||
std::string msg = "Unknown command: ";
|
std::string msg = "Unknown command: ";
|
||||||
msg += (command ? command : "");
|
msg += (command ? command : "");
|
||||||
dpm_log(ctx, DPM_LOG_WARN, msg.c_str());
|
dpm_log(ctx, DPM_LOG_WARN, msg.c_str());
|
||||||
|
|||||||
211
src/cli/dpm.cpp
211
src/cli/dpm.cpp
@@ -33,127 +33,120 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
/**
|
||||||
/**
|
* @brief Prints the CLI usage message
|
||||||
* @brief Prints the CLI usage message
|
*/
|
||||||
*/
|
void show_help() {
|
||||||
void show_help()
|
std::printf(
|
||||||
{
|
"Usage: dpm [options] [module] [module args...]\n"
|
||||||
std::printf(
|
"\n"
|
||||||
"Usage: dpm [options] [module] [module args...]\n"
|
"Options:\n"
|
||||||
"\n"
|
" -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n"
|
||||||
"Options:\n"
|
" -m, --module-path PATH Module directory (overrides configuration)\n"
|
||||||
" -c, --config-dir PATH Configuration directory (default /etc/dpm/conf.d/)\n"
|
" -r, --root PATH Target root for package operations (default /)\n"
|
||||||
" -m, --module-path PATH Module directory (overrides configuration)\n"
|
" -L, --log-level LEVEL FATAL, ERROR, WARN, INFO, or DEBUG\n"
|
||||||
" -r, --root PATH Target root for package operations (default /)\n"
|
" -l, --list-modules List available modules\n"
|
||||||
" -L, --log-level LEVEL FATAL, ERROR, WARN, INFO, or DEBUG\n"
|
" -h, --help Show this help message\n"
|
||||||
" -l, --list-modules List available modules\n"
|
"\n"
|
||||||
" -h, --help Show this help message\n"
|
"For module-specific help, use: dpm <module> help\n");
|
||||||
"\n"
|
|
||||||
"For module-specific help, use: dpm <module> help\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Parses a log level name
|
|
||||||
*
|
|
||||||
* @param name The level name (case-insensitive)
|
|
||||||
* @return The DPM_LOG_* level, or -1 if unrecognized
|
|
||||||
*/
|
|
||||||
int level_from_name(const char* name)
|
|
||||||
{
|
|
||||||
if (strcasecmp(name, "FATAL") == 0) { return DPM_LOG_FATAL; }
|
|
||||||
if (strcasecmp(name, "ERROR") == 0) { return DPM_LOG_ERROR; }
|
|
||||||
if (strcasecmp(name, "WARN") == 0) { return DPM_LOG_WARN; }
|
|
||||||
if (strcasecmp(name, "INFO") == 0) { return DPM_LOG_INFO; }
|
|
||||||
if (strcasecmp(name, "DEBUG") == 0) { return DPM_LOG_DEBUG; }
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Matches an argument against an option's short and long forms
|
|
||||||
*
|
|
||||||
* Recognizes the separate-value forms and the --option=value form.
|
|
||||||
*
|
|
||||||
* @param arg The command-line argument to test
|
|
||||||
* @param short_form The option's short form (e.g. "-c")
|
|
||||||
* @param long_form The option's long form (e.g. "--config-dir")
|
|
||||||
* @param inline_value Receives the text after '=' for the inline
|
|
||||||
* form, or NULL when the value is in the next
|
|
||||||
* argument
|
|
||||||
* @return true if the argument is this option
|
|
||||||
*/
|
|
||||||
bool option_matches(const char* arg, const char* short_form,
|
|
||||||
const char* long_form, const char** inline_value)
|
|
||||||
{
|
|
||||||
if (std::strcmp(arg, short_form) == 0 ||
|
|
||||||
std::strcmp(arg, long_form) == 0) {
|
|
||||||
*inline_value = nullptr;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t len = std::strlen(long_form);
|
/**
|
||||||
if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') {
|
* @brief Parses a log level name
|
||||||
*inline_value = arg + len + 1;
|
*
|
||||||
return true;
|
* @param name The level name (case-insensitive)
|
||||||
|
* @return The DPM_LOG_* level, or -1 if unrecognized
|
||||||
|
*/
|
||||||
|
int level_from_name(const char* name) {
|
||||||
|
if (strcasecmp(name, "FATAL") == 0) { return DPM_LOG_FATAL; }
|
||||||
|
if (strcasecmp(name, "ERROR") == 0) { return DPM_LOG_ERROR; }
|
||||||
|
if (strcasecmp(name, "WARN") == 0) { return DPM_LOG_WARN; }
|
||||||
|
if (strcasecmp(name, "INFO") == 0) { return DPM_LOG_INFO; }
|
||||||
|
if (strcasecmp(name, "DEBUG") == 0) { return DPM_LOG_DEBUG; }
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
/**
|
||||||
}
|
* @brief Matches an argument against an option's short and long forms
|
||||||
|
*
|
||||||
|
* Recognizes the separate-value forms and the --option=value form.
|
||||||
|
*
|
||||||
|
* @param arg The command-line argument to test
|
||||||
|
* @param short_form The option's short form (e.g. "-c")
|
||||||
|
* @param long_form The option's long form (e.g. "--config-dir")
|
||||||
|
* @param inline_value Receives the text after '=' for the inline
|
||||||
|
* form, or NULL when the value is in the next
|
||||||
|
* argument
|
||||||
|
* @return true if the argument is this option
|
||||||
|
*/
|
||||||
|
bool option_matches(const char* arg, const char* short_form,
|
||||||
|
const char* long_form, const char** inline_value) {
|
||||||
|
if (std::strcmp(arg, short_form) == 0 ||
|
||||||
|
std::strcmp(arg, long_form) == 0) {
|
||||||
|
*inline_value = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
size_t len = std::strlen(long_form);
|
||||||
* @brief Prints the table of available modules
|
if (std::strncmp(arg, long_form, len) == 0 && arg[len] == '=') {
|
||||||
*
|
*inline_value = arg + len + 1;
|
||||||
* @param ctx The libdpm-core context
|
return true;
|
||||||
* @return 0 on success, 1 on failure
|
}
|
||||||
*/
|
|
||||||
int list_modules(dpm_ctx* ctx)
|
return false;
|
||||||
{
|
|
||||||
dpm_cursor* cur = dpm_list_modules(ctx);
|
|
||||||
if (!cur) {
|
|
||||||
const char* err = dpm_last_error(ctx);
|
|
||||||
std::fprintf(stderr, "dpm: %s\n", err ? err : "module listing failed");
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<dpm_module_info> infos;
|
/**
|
||||||
dpm_module_info info;
|
* @brief Prints the table of available modules
|
||||||
while (dpm_cursor_next(cur, &info) == 0) {
|
*
|
||||||
infos.push_back(info);
|
* @param ctx The libdpm-core context
|
||||||
}
|
* @return 0 on success, 1 on failure
|
||||||
dpm_cursor_free(cur);
|
*/
|
||||||
|
int list_modules(dpm_ctx* ctx) {
|
||||||
|
dpm_cursor* cur = dpm_list_modules(ctx);
|
||||||
|
if (!cur) {
|
||||||
|
const char* err = dpm_last_error(ctx);
|
||||||
|
std::fprintf(stderr, "dpm: %s\n", err ? err : "module listing failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<dpm_module_info> infos;
|
||||||
|
dpm_module_info info;
|
||||||
|
while (dpm_cursor_next(cur, &info) == 0) {
|
||||||
|
infos.push_back(info);
|
||||||
|
}
|
||||||
|
dpm_cursor_free(cur);
|
||||||
|
|
||||||
|
if (infos.empty()) {
|
||||||
|
std::printf("No valid modules found in %s\n", dpm_module_path(ctx));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t name_w = std::strlen("MODULE");
|
||||||
|
size_t version_w = std::strlen("VERSION");
|
||||||
|
for (const auto& i : infos) {
|
||||||
|
name_w = std::max(name_w, std::strlen(i.name));
|
||||||
|
version_w = std::max(version_w, std::strlen(i.version));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("Available DPM modules:\n\n");
|
||||||
|
std::printf("%-*s %-*s %s\n",
|
||||||
|
static_cast<int>(name_w), "MODULE",
|
||||||
|
static_cast<int>(version_w), "VERSION",
|
||||||
|
"DESCRIPTION");
|
||||||
|
for (const auto& i : infos) {
|
||||||
|
std::printf("%-*s %-*s %s\n",
|
||||||
|
static_cast<int>(name_w), i.name,
|
||||||
|
static_cast<int>(version_w), i.version,
|
||||||
|
i.description);
|
||||||
|
}
|
||||||
|
std::printf("\nUse 'dpm <module> help' for module-specific help.\n");
|
||||||
|
|
||||||
if (infos.empty()) {
|
|
||||||
std::printf("No valid modules found in %s\n", dpm_module_path(ctx));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t name_w = std::strlen("MODULE");
|
|
||||||
size_t version_w = std::strlen("VERSION");
|
|
||||||
for (const auto& i : infos) {
|
|
||||||
name_w = std::max(name_w, std::strlen(i.name));
|
|
||||||
version_w = std::max(version_w, std::strlen(i.version));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::printf("Available DPM modules:\n\n");
|
|
||||||
std::printf("%-*s %-*s %s\n",
|
|
||||||
static_cast<int>(name_w), "MODULE",
|
|
||||||
static_cast<int>(version_w), "VERSION",
|
|
||||||
"DESCRIPTION");
|
|
||||||
for (const auto& i : infos) {
|
|
||||||
std::printf("%-*s %-*s %s\n",
|
|
||||||
static_cast<int>(name_w), i.name,
|
|
||||||
static_cast<int>(version_w), i.version,
|
|
||||||
i.description);
|
|
||||||
}
|
|
||||||
std::printf("\nUse 'dpm <module> help' for module-specific help.\n");
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv) {
|
||||||
{
|
|
||||||
dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1};
|
dpm_open_overrides overrides = {nullptr, nullptr, nullptr, -1};
|
||||||
bool list = false;
|
bool list = false;
|
||||||
|
|
||||||
|
|||||||
@@ -33,295 +33,275 @@
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
const char* DEFAULT_CONFIG_DIR = "/etc/dpm/conf.d/";
|
||||||
|
const char* DEFAULT_MODULE_PATH = "/usr/lib/dpm/modules/";
|
||||||
|
const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log";
|
||||||
|
|
||||||
const char* DEFAULT_CONFIG_DIR = "/etc/dpm/conf.d/";
|
std::string trim(const std::string& s) {
|
||||||
const char* DEFAULT_MODULE_PATH = "/usr/lib/dpm/modules/";
|
const char* ws = " \t\r\n\f\v";
|
||||||
const char* DEFAULT_LOG_FILE = "/var/log/dpm/dpm.log";
|
size_t start = s.find_first_not_of(ws);
|
||||||
|
if (start == std::string::npos) {
|
||||||
std::string trim(const std::string& s)
|
return "";
|
||||||
{
|
}
|
||||||
const char* ws = " \t\r\n\f\v";
|
size_t end = s.find_last_not_of(ws);
|
||||||
size_t start = s.find_first_not_of(ws);
|
return s.substr(start, end - start + 1);
|
||||||
if (start == std::string::npos) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
size_t end = s.find_last_not_of(ws);
|
|
||||||
return s.substr(start, end - start + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string with_trailing_slash(std::string s)
|
|
||||||
{
|
|
||||||
if (!s.empty() && s.back() != '/') {
|
|
||||||
s += '/';
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_bool(const std::string& v, bool fallback)
|
|
||||||
{
|
|
||||||
std::string lower;
|
|
||||||
for (char c : v) {
|
|
||||||
lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
}
|
|
||||||
if (lower == "true" || lower == "yes" || lower == "1" || lower == "on") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (lower == "false" || lower == "no" || lower == "0" || lower == "off") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
int level_from_string(const std::string& v, int fallback)
|
|
||||||
{
|
|
||||||
if (v == "FATAL") { return DPM_LOG_FATAL; }
|
|
||||||
if (v == "ERROR") { return DPM_LOG_ERROR; }
|
|
||||||
if (v == "WARN") { return DPM_LOG_WARN; }
|
|
||||||
if (v == "INFO") { return DPM_LOG_INFO; }
|
|
||||||
if (v == "DEBUG") { return DPM_LOG_DEBUG; }
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* level_name(int level)
|
|
||||||
{
|
|
||||||
switch (level) {
|
|
||||||
case DPM_LOG_FATAL: return "FATAL";
|
|
||||||
case DPM_LOG_ERROR: return "ERROR";
|
|
||||||
case DPM_LOG_WARN: return "WARN";
|
|
||||||
case DPM_LOG_INFO: return "INFO";
|
|
||||||
case DPM_LOG_DEBUG: return "DEBUG";
|
|
||||||
default: return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_config_file(dpm_ctx* ctx, const fs::path& file,
|
|
||||||
const std::string& module_name)
|
|
||||||
{
|
|
||||||
std::ifstream in(file);
|
|
||||||
if (!in.is_open()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string line;
|
std::string with_trailing_slash(std::string s) {
|
||||||
std::string section = "main";
|
if (!s.empty() && s.back() != '/') {
|
||||||
|
s += '/';
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
while (std::getline(in, line)) {
|
bool parse_bool(const std::string& v, bool fallback) {
|
||||||
line = trim(line);
|
std::string lower;
|
||||||
|
for (char c : v) {
|
||||||
|
lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
}
|
||||||
|
if (lower == "true" || lower == "yes" || lower == "1" || lower == "on") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (lower == "false" || lower == "no" || lower == "0" || lower == "off") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
int level_from_string(const std::string& v, int fallback) {
|
||||||
continue;
|
if (v == "FATAL") { return DPM_LOG_FATAL; }
|
||||||
|
if (v == "ERROR") { return DPM_LOG_ERROR; }
|
||||||
|
if (v == "WARN") { return DPM_LOG_WARN; }
|
||||||
|
if (v == "INFO") { return DPM_LOG_INFO; }
|
||||||
|
if (v == "DEBUG") { return DPM_LOG_DEBUG; }
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* level_name(int level) {
|
||||||
|
switch (level) {
|
||||||
|
case DPM_LOG_FATAL: return "FATAL";
|
||||||
|
case DPM_LOG_ERROR: return "ERROR";
|
||||||
|
case DPM_LOG_WARN: return "WARN";
|
||||||
|
case DPM_LOG_INFO: return "INFO";
|
||||||
|
case DPM_LOG_DEBUG: return "DEBUG";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_config_file(dpm_ctx* ctx, const fs::path& file,
|
||||||
|
const std::string& module_name) {
|
||||||
|
std::ifstream in(file);
|
||||||
|
if (!in.is_open()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.front() == '[' && line.back() == ']') {
|
std::string line;
|
||||||
std::string name = trim(line.substr(1, line.length() - 2));
|
std::string section = "main";
|
||||||
section = name.empty() ? "main" : name;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t eq = line.find('=');
|
while (std::getline(in, line)) {
|
||||||
if (eq == std::string::npos) {
|
line = trim(line);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string key = trim(line.substr(0, eq));
|
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
||||||
std::string value = trim(line.substr(eq + 1));
|
continue;
|
||||||
if (key.empty()) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->config[module_name][section][key] = value;
|
if (line.front() == '[' && line.back() == ']') {
|
||||||
|
std::string name = trim(line.substr(1, line.length() - 2));
|
||||||
|
section = name.empty() ? "main" : name;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t eq = line.find('=');
|
||||||
|
if (eq == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string key = trim(line.substr(0, eq));
|
||||||
|
std::string value = trim(line.substr(eq + 1));
|
||||||
|
if (key.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->config[module_name][section][key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
void set_error(dpm_ctx* ctx, const std::string& msg) {
|
||||||
void set_error(dpm_ctx* ctx, const std::string& msg)
|
if (ctx) {
|
||||||
{
|
ctx->last_error = msg;
|
||||||
if (ctx) {
|
}
|
||||||
ctx->last_error = msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_config_dir(dpm_ctx* ctx)
|
|
||||||
{
|
|
||||||
std::error_code ec;
|
|
||||||
if (!fs::is_directory(ctx->config_dir, ec)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& entry : fs::directory_iterator(ctx->config_dir, ec)) {
|
void load_config_dir(dpm_ctx* ctx) {
|
||||||
if (ec) {
|
std::error_code ec;
|
||||||
break;
|
if (!fs::is_directory(ctx->config_dir, ec)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!entry.is_regular_file()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entry.path().extension() != ".conf") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parse_config_file(ctx, entry.path(), entry.path().stem().string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (const auto& entry : fs::directory_iterator(ctx->config_dir, ec)) {
|
||||||
|
if (ec) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry.is_regular_file()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.path().extension() != ".conf") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parse_config_file(ctx, entry.path(), entry.path().stem().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
dpm_ctx* dpm_open(const dpm_open_overrides* overrides) {
|
||||||
dpm_ctx* dpm_open(const dpm_open_overrides* overrides)
|
/* Validate explicit overrides before allocating anything. */
|
||||||
{
|
if (overrides) {
|
||||||
/* Validate explicit overrides before allocating anything. */
|
if (overrides->config_dir && *overrides->config_dir) {
|
||||||
if (overrides) {
|
std::error_code ec;
|
||||||
if (overrides->config_dir && *overrides->config_dir) {
|
if (!fs::is_directory(overrides->config_dir, ec)) {
|
||||||
std::error_code ec;
|
return nullptr;
|
||||||
if (!fs::is_directory(overrides->config_dir, ec)) {
|
}
|
||||||
|
}
|
||||||
|
if (overrides->log_level < -1 || overrides->log_level > DPM_LOG_DEBUG) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (overrides->log_level < -1 || overrides->log_level > DPM_LOG_DEBUG) {
|
|
||||||
|
dpm_ctx* ctx = new (std::nothrow) dpm_ctx;
|
||||||
|
if (!ctx) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dpm_ctx* ctx = new (std::nothrow) dpm_ctx;
|
/* Config directory: override > default. */
|
||||||
if (!ctx) {
|
ctx->config_dir = DEFAULT_CONFIG_DIR;
|
||||||
return nullptr;
|
if (overrides && overrides->config_dir && *overrides->config_dir) {
|
||||||
}
|
ctx->config_dir = with_trailing_slash(overrides->config_dir);
|
||||||
|
|
||||||
/* Config directory: override > default. */
|
|
||||||
ctx->config_dir = DEFAULT_CONFIG_DIR;
|
|
||||||
if (overrides && overrides->config_dir && *overrides->config_dir) {
|
|
||||||
ctx->config_dir = with_trailing_slash(overrides->config_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
dpm_core::load_config_dir(ctx);
|
|
||||||
|
|
||||||
/* Logging: config, then override. */
|
|
||||||
ctx->log_level = DPM_LOG_INFO;
|
|
||||||
ctx->write_to_log = false;
|
|
||||||
ctx->log_file = DEFAULT_LOG_FILE;
|
|
||||||
|
|
||||||
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_level")) {
|
|
||||||
ctx->log_level = level_from_string(v, DPM_LOG_INFO);
|
|
||||||
}
|
|
||||||
if (const char* v = dpm_config_get(ctx, "core", "logging", "write_to_log")) {
|
|
||||||
ctx->write_to_log = parse_bool(v, false);
|
|
||||||
}
|
|
||||||
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_file")) {
|
|
||||||
ctx->log_file = v;
|
|
||||||
}
|
|
||||||
if (overrides && overrides->log_level >= 0) {
|
|
||||||
ctx->log_level = overrides->log_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Module path: override > config > default. */
|
|
||||||
ctx->module_path = DEFAULT_MODULE_PATH;
|
|
||||||
if (const char* v = dpm_config_get(ctx, "core", "modules", "path")) {
|
|
||||||
ctx->module_path = with_trailing_slash(v);
|
|
||||||
}
|
|
||||||
if (overrides && overrides->module_path && *overrides->module_path) {
|
|
||||||
ctx->module_path = with_trailing_slash(overrides->module_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Target root: override > default. */
|
|
||||||
ctx->root = "/";
|
|
||||||
if (overrides && overrides->root && *overrides->root) {
|
|
||||||
ctx->root = overrides->root;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dpm_close(dpm_ctx* ctx)
|
|
||||||
{
|
|
||||||
if (!ctx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* dpm_module destructors do not dlclose; do it here so the
|
|
||||||
registry teardown order is explicit. */
|
|
||||||
for (auto& [name, mod] : ctx->modules) {
|
|
||||||
if (mod->handle) {
|
|
||||||
dpm_internal_unload(mod->handle);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
delete ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* dpm_config_get(dpm_ctx* ctx, const char* module,
|
dpm_core::load_config_dir(ctx);
|
||||||
const char* section, const char* key)
|
|
||||||
{
|
/* Logging: config, then override. */
|
||||||
if (!ctx || !module || !section || !key) {
|
ctx->log_level = DPM_LOG_INFO;
|
||||||
return nullptr;
|
ctx->write_to_log = false;
|
||||||
|
ctx->log_file = DEFAULT_LOG_FILE;
|
||||||
|
|
||||||
|
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_level")) {
|
||||||
|
ctx->log_level = level_from_string(v, DPM_LOG_INFO);
|
||||||
|
}
|
||||||
|
if (const char* v = dpm_config_get(ctx, "core", "logging", "write_to_log")) {
|
||||||
|
ctx->write_to_log = parse_bool(v, false);
|
||||||
|
}
|
||||||
|
if (const char* v = dpm_config_get(ctx, "core", "logging", "log_file")) {
|
||||||
|
ctx->log_file = v;
|
||||||
|
}
|
||||||
|
if (overrides && overrides->log_level >= 0) {
|
||||||
|
ctx->log_level = overrides->log_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Module path: override > config > default. */
|
||||||
|
ctx->module_path = DEFAULT_MODULE_PATH;
|
||||||
|
if (const char* v = dpm_config_get(ctx, "core", "modules", "path")) {
|
||||||
|
ctx->module_path = with_trailing_slash(v);
|
||||||
|
}
|
||||||
|
if (overrides && overrides->module_path && *overrides->module_path) {
|
||||||
|
ctx->module_path = with_trailing_slash(overrides->module_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Target root: override > default. */
|
||||||
|
ctx->root = "/";
|
||||||
|
if (overrides && overrides->root && *overrides->root) {
|
||||||
|
ctx->root = overrides->root;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mod_it = ctx->config.find(module);
|
void dpm_close(dpm_ctx* ctx) {
|
||||||
if (mod_it == ctx->config.end()) {
|
if (!ctx) {
|
||||||
return nullptr;
|
return;
|
||||||
|
}
|
||||||
|
/* dpm_module destructors do not dlclose; do it here so the
|
||||||
|
registry teardown order is explicit. */
|
||||||
|
for (auto& [name, mod] : ctx->modules) {
|
||||||
|
if (mod->handle) {
|
||||||
|
dpm_internal_unload(mod->handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sec_it = mod_it->second.find(section);
|
const char* dpm_config_get(dpm_ctx* ctx, const char* module,
|
||||||
if (sec_it == mod_it->second.end()) {
|
const char* section, const char* key) {
|
||||||
return nullptr;
|
if (!ctx || !module || !section || !key) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mod_it = ctx->config.find(module);
|
||||||
|
if (mod_it == ctx->config.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sec_it = mod_it->second.find(section);
|
||||||
|
if (sec_it == mod_it->second.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto key_it = sec_it->second.find(key);
|
||||||
|
if (key_it == sec_it->second.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return key_it->second.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto key_it = sec_it->second.find(key);
|
void dpm_log(dpm_ctx* ctx, int level, const char* message) {
|
||||||
if (key_it == sec_it->second.end()) {
|
if (!ctx || !message) {
|
||||||
return nullptr;
|
return;
|
||||||
}
|
}
|
||||||
|
if (level < DPM_LOG_FATAL || level > DPM_LOG_DEBUG) {
|
||||||
|
level = DPM_LOG_INFO;
|
||||||
|
}
|
||||||
|
if (level > ctx->log_level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return key_it->second.c_str();
|
if (level <= DPM_LOG_WARN) {
|
||||||
}
|
std::fprintf(stderr, "%s: %s\n", level_name(level), message);
|
||||||
|
|
||||||
void dpm_log(dpm_ctx* ctx, int level, const char* message)
|
|
||||||
{
|
|
||||||
if (!ctx || !message) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (level < DPM_LOG_FATAL || level > DPM_LOG_DEBUG) {
|
|
||||||
level = DPM_LOG_INFO;
|
|
||||||
}
|
|
||||||
if (level > ctx->log_level) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (level <= DPM_LOG_WARN) {
|
|
||||||
std::fprintf(stderr, "%s: %s\n", level_name(level), message);
|
|
||||||
} else {
|
|
||||||
std::fprintf(stdout, "%s\n", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx->write_to_log) {
|
|
||||||
std::ofstream out(ctx->log_file, std::ios::app);
|
|
||||||
if (out.is_open()) {
|
|
||||||
char stamp[32];
|
|
||||||
std::time_t now = std::time(nullptr);
|
|
||||||
std::strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S",
|
|
||||||
std::localtime(&now));
|
|
||||||
out << stamp << " [" << level_name(level) << "] "
|
|
||||||
<< message << "\n";
|
|
||||||
} else {
|
} else {
|
||||||
/* Log-file trouble never fails the operation; disable and
|
std::fprintf(stdout, "%s\n", message);
|
||||||
continue on console alone. */
|
}
|
||||||
ctx->write_to_log = false;
|
|
||||||
|
if (ctx->write_to_log) {
|
||||||
|
std::ofstream out(ctx->log_file, std::ios::app);
|
||||||
|
if (out.is_open()) {
|
||||||
|
char stamp[32];
|
||||||
|
std::time_t now = std::time(nullptr);
|
||||||
|
std::strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S",
|
||||||
|
std::localtime(&now));
|
||||||
|
out << stamp << " [" << level_name(level) << "] "
|
||||||
|
<< message << "\n";
|
||||||
|
} else {
|
||||||
|
/* Log-file trouble never fails the operation; disable and
|
||||||
|
continue on console alone. */
|
||||||
|
ctx->write_to_log = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const char* dpm_module_path(dpm_ctx* ctx)
|
const char* dpm_module_path(dpm_ctx* ctx) {
|
||||||
{
|
if (!ctx) {
|
||||||
if (!ctx) {
|
return nullptr;
|
||||||
return nullptr;
|
}
|
||||||
|
return ctx->module_path.c_str();
|
||||||
}
|
}
|
||||||
return ctx->module_path.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* dpm_last_error(dpm_ctx* ctx)
|
const char* dpm_last_error(dpm_ctx* ctx) {
|
||||||
{
|
if (!ctx || ctx->last_error.empty()) {
|
||||||
if (!ctx || ctx->last_error.empty()) {
|
return nullptr;
|
||||||
return nullptr;
|
}
|
||||||
|
return ctx->last_error.c_str();
|
||||||
}
|
}
|
||||||
return ctx->last_error.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
|
|||||||
@@ -41,225 +41,210 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
/* dlclose wrapper referenced from context.cpp so handle release stays
|
/* dlclose wrapper referenced from context.cpp so handle release stays
|
||||||
in one translation unit with the loader. */
|
in one translation unit with the loader. */
|
||||||
void dpm_internal_unload(void* handle)
|
void dpm_internal_unload(void* handle) {
|
||||||
{
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**);
|
||||||
|
using string_fn = const char* (*)(void);
|
||||||
|
|
||||||
using execute_fn = int (*)(dpm_ctx*, const char*, int, char**);
|
/** dlsym with dlerror() discipline; returns nullptr on any error. */
|
||||||
using string_fn = const char* (*)(void);
|
void* resolve(void* handle, const char* symbol) {
|
||||||
|
dlerror();
|
||||||
/** dlsym with dlerror() discipline; returns nullptr on any error. */
|
void* addr = dlsym(handle, symbol);
|
||||||
void* resolve(void* handle, const char* symbol)
|
if (dlerror() != nullptr) {
|
||||||
{
|
return nullptr;
|
||||||
dlerror();
|
}
|
||||||
void* addr = dlsym(handle, symbol);
|
return addr;
|
||||||
if (dlerror() != nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||||
|
const std::string& name,
|
||||||
|
std::string& reason) {
|
||||||
|
std::string so_path = ctx->module_path + name + ".so";
|
||||||
|
|
||||||
std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
std::error_code ec;
|
||||||
const std::string& name,
|
if (!fs::exists(so_path, ec)) {
|
||||||
std::string& reason)
|
reason = "not found at " + so_path;
|
||||||
{
|
return nullptr;
|
||||||
std::string so_path = ctx->module_path + name + ".so";
|
|
||||||
|
|
||||||
std::error_code ec;
|
|
||||||
if (!fs::exists(so_path, ec)) {
|
|
||||||
reason = "not found at " + so_path;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* handle = dlopen(so_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
|
||||||
if (!handle) {
|
|
||||||
const char* err = dlerror();
|
|
||||||
reason = std::string("dlopen failed: ") + (err ? err : "unknown");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Step 1: resolve all reserved contract symbols. */
|
|
||||||
static const char* required[] = {
|
|
||||||
"dpm_module_execute",
|
|
||||||
"dpm_module_version",
|
|
||||||
"dpm_module_description",
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string missing;
|
|
||||||
for (const char* sym : required) {
|
|
||||||
if (!resolve(handle, sym)) {
|
|
||||||
if (!missing.empty()) {
|
|
||||||
missing += ", ";
|
|
||||||
}
|
|
||||||
missing += sym;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!missing.empty()) {
|
|
||||||
reason = "missing required contract symbols: " + missing;
|
|
||||||
dlclose(handle);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute"));
|
void* handle = dlopen(so_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||||
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version"));
|
if (!handle) {
|
||||||
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description"));
|
const char* err = dlerror();
|
||||||
|
reason = std::string("dlopen failed: ") + (err ? err : "unknown");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/* Step 2: probe the cheap calls. */
|
/* Step 1: resolve all reserved contract symbols. */
|
||||||
long parsed[3];
|
static const char* required[] = {
|
||||||
const char* version = version_f();
|
"dpm_module_execute",
|
||||||
if (!version || !parse_version(version, parsed)) {
|
"dpm_module_version",
|
||||||
reason = "dpm_module_version() returned a malformed version";
|
"dpm_module_description",
|
||||||
dlclose(handle);
|
};
|
||||||
return nullptr;
|
|
||||||
|
std::string missing;
|
||||||
|
for (const char* sym : required) {
|
||||||
|
if (!resolve(handle, sym)) {
|
||||||
|
if (!missing.empty()) {
|
||||||
|
missing += ", ";
|
||||||
|
}
|
||||||
|
missing += sym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!missing.empty()) {
|
||||||
|
reason = "missing required contract symbols: " + missing;
|
||||||
|
dlclose(handle);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute"));
|
||||||
|
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version"));
|
||||||
|
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description"));
|
||||||
|
|
||||||
|
/* Step 2: probe the cheap calls. */
|
||||||
|
long parsed[3];
|
||||||
|
const char* version = version_f();
|
||||||
|
if (!version || !parse_version(version, parsed)) {
|
||||||
|
reason = "dpm_module_version() returned a malformed version";
|
||||||
|
dlclose(handle);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const char* description = desc_f();
|
||||||
|
if (!description || !*description) {
|
||||||
|
reason = "dpm_module_description() returned nothing";
|
||||||
|
dlclose(handle);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mod = std::make_unique<dpm_module>();
|
||||||
|
mod->name = name;
|
||||||
|
mod->handle = handle;
|
||||||
|
mod->version = version;
|
||||||
|
mod->description = description;
|
||||||
|
mod->execute = exec_f;
|
||||||
|
return mod;
|
||||||
}
|
}
|
||||||
const char* description = desc_f();
|
|
||||||
if (!description || !*description) {
|
|
||||||
reason = "dpm_module_description() returned nothing";
|
|
||||||
dlclose(handle);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mod = std::make_unique<dpm_module>();
|
|
||||||
mod->name = name;
|
|
||||||
mod->handle = handle;
|
|
||||||
mod->version = version;
|
|
||||||
mod->description = description;
|
|
||||||
mod->execute = exec_f;
|
|
||||||
return mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
dpm_module* dpm_require(dpm_ctx* ctx, const char* name) {
|
||||||
dpm_module* dpm_require(dpm_ctx* ctx, const char* name)
|
if (!ctx || !name || !*name) {
|
||||||
{
|
return nullptr;
|
||||||
if (!ctx || !name || !*name) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = ctx->modules.find(name);
|
|
||||||
if (it != ctx->modules.end()) {
|
|
||||||
return it->second.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string reason;
|
|
||||||
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
|
|
||||||
if (!loaded) {
|
|
||||||
dpm_core::set_error(ctx, std::string("module '") + name +
|
|
||||||
"': " + reason);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
dpm_module* mod = loaded.get();
|
|
||||||
ctx->modules[name] = std::move(loaded);
|
|
||||||
return mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)
|
|
||||||
{
|
|
||||||
if (!ctx || !mod || !out) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
out->name = mod->name.c_str();
|
|
||||||
out->version = mod->version.c_str();
|
|
||||||
out->description = mod->description.c_str();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
|
|
||||||
int argc, char** argv)
|
|
||||||
{
|
|
||||||
if (!ctx || !mod || !mod->execute) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return mod->execute(ctx, command, argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
dpm_cursor* dpm_list_modules(dpm_ctx* ctx)
|
|
||||||
{
|
|
||||||
if (!ctx) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::error_code ec;
|
|
||||||
if (!fs::is_directory(ctx->module_path, ec)) {
|
|
||||||
dpm_core::set_error(ctx, "module path is not a readable directory: " +
|
|
||||||
ctx->module_path);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::string> names;
|
|
||||||
for (const auto& entry : fs::directory_iterator(ctx->module_path, ec)) {
|
|
||||||
if (ec) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!entry.is_regular_file() && !entry.is_symlink()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entry.path().extension() != ".so") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
names.insert(entry.path().stem().string());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* cur = new (std::nothrow) dpm_cursor;
|
|
||||||
if (!cur) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string& name : names) {
|
|
||||||
dpm_module* mod = nullptr;
|
|
||||||
|
|
||||||
auto it = ctx->modules.find(name);
|
auto it = ctx->modules.find(name);
|
||||||
if (it != ctx->modules.end()) {
|
if (it != ctx->modules.end()) {
|
||||||
mod = it->second.get();
|
return it->second.get();
|
||||||
} else {
|
|
||||||
std::string reason;
|
|
||||||
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
|
|
||||||
if (!loaded) {
|
|
||||||
dpm_log(ctx, DPM_LOG_WARN,
|
|
||||||
("module '" + name + "' failed validation: " +
|
|
||||||
reason).c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
mod = loaded.get();
|
|
||||||
ctx->modules[name] = std::move(loaded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dpm_module_info info;
|
std::string reason;
|
||||||
info.name = mod->name.c_str();
|
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
|
||||||
info.version = mod->version.c_str();
|
if (!loaded) {
|
||||||
info.description = mod->description.c_str();
|
dpm_core::set_error(ctx, std::string("module '") + name +
|
||||||
cur->infos.push_back(info);
|
"': " + reason);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
dpm_module* mod = loaded.get();
|
||||||
|
ctx->modules[name] = std::move(loaded);
|
||||||
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cur;
|
int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out) {
|
||||||
}
|
if (!ctx || !mod || !out) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out)
|
out->name = mod->name.c_str();
|
||||||
{
|
out->version = mod->version.c_str();
|
||||||
if (!cur || !out || cur->idx >= cur->infos.size()) {
|
out->description = mod->description.c_str();
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
*out = cur->infos[cur->idx];
|
|
||||||
cur->idx++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dpm_cursor_free(dpm_cursor* cur)
|
int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command,
|
||||||
{
|
int argc, char** argv) {
|
||||||
delete cur;
|
if (!ctx || !mod || !mod->execute) {
|
||||||
}
|
return 1;
|
||||||
|
}
|
||||||
|
return mod->execute(ctx, command, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpm_cursor* dpm_list_modules(dpm_ctx* ctx) {
|
||||||
|
if (!ctx) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!fs::is_directory(ctx->module_path, ec)) {
|
||||||
|
dpm_core::set_error(ctx, "module path is not a readable directory: " +
|
||||||
|
ctx->module_path);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> names;
|
||||||
|
for (const auto& entry : fs::directory_iterator(ctx->module_path, ec)) {
|
||||||
|
if (ec) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!entry.is_regular_file() && !entry.is_symlink()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.path().extension() != ".so") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
names.insert(entry.path().stem().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* cur = new (std::nothrow) dpm_cursor;
|
||||||
|
if (!cur) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& name : names) {
|
||||||
|
dpm_module* mod = nullptr;
|
||||||
|
|
||||||
|
auto it = ctx->modules.find(name);
|
||||||
|
if (it != ctx->modules.end()) {
|
||||||
|
mod = it->second.get();
|
||||||
|
} else {
|
||||||
|
std::string reason;
|
||||||
|
auto loaded = dpm_core::validate_and_load(ctx, name, reason);
|
||||||
|
if (!loaded) {
|
||||||
|
dpm_log(ctx, DPM_LOG_WARN,
|
||||||
|
("module '" + name + "' failed validation: " +
|
||||||
|
reason).c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mod = loaded.get();
|
||||||
|
ctx->modules[name] = std::move(loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
dpm_module_info info;
|
||||||
|
info.name = mod->name.c_str();
|
||||||
|
info.version = mod->version.c_str();
|
||||||
|
info.description = mod->description.c_str();
|
||||||
|
cur->infos.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out) {
|
||||||
|
if (!cur || !out || cur->idx >= cur->infos.size()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
*out = cur->infos[cur->idx];
|
||||||
|
cur->idx++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dpm_cursor_free(dpm_cursor* cur) {
|
||||||
|
delete cur;
|
||||||
|
}
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
|
|||||||
@@ -30,44 +30,40 @@
|
|||||||
/** libdpm-core.so's own version. */
|
/** libdpm-core.so's own version. */
|
||||||
#define DPM_CORE_VERSION_STR "1.0.0"
|
#define DPM_CORE_VERSION_STR "1.0.0"
|
||||||
|
|
||||||
extern "C" const char* dpm_core_version(void)
|
extern "C" const char* dpm_core_version(void) {
|
||||||
{
|
|
||||||
return DPM_CORE_VERSION_STR;
|
return DPM_CORE_VERSION_STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace dpm_core {
|
namespace dpm_core {
|
||||||
|
bool parse_version(const char* s, long out[3]) {
|
||||||
bool parse_version(const char* s, long out[3])
|
if (!s || !*s) {
|
||||||
{
|
|
||||||
if (!s || !*s) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* p = s;
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
if (!std::isdigit(static_cast<unsigned char>(*p))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* end = nullptr;
|
const char* p = s;
|
||||||
out[i] = std::strtol(p, &end, 10);
|
for (int i = 0; i < 3; i++) {
|
||||||
if (out[i] < 0) {
|
if (!std::isdigit(static_cast<unsigned char>(*p))) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* end = nullptr;
|
||||||
|
out[i] = std::strtol(p, &end, 10);
|
||||||
|
if (out[i] < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 2) {
|
||||||
|
if (*end != '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
p = end + 1;
|
||||||
|
} else {
|
||||||
|
if (*end != '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < 2) {
|
return true;
|
||||||
if (*end != '.') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
p = end + 1;
|
|
||||||
} else {
|
|
||||||
if (*end != '\0') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace dpm_core
|
} // namespace dpm_core
|
||||||
|
|||||||
9
tests/fixtures/src/bad_version.cpp
vendored
9
tests/fixtures/src/bad_version.cpp
vendored
@@ -20,19 +20,16 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
extern "C" const char* dpm_module_version(void)
|
extern "C" const char* dpm_module_version(void) {
|
||||||
{
|
|
||||||
return "banana";
|
return "banana";
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" const char* dpm_module_description(void)
|
extern "C" const char* dpm_module_description(void) {
|
||||||
{
|
|
||||||
return "Fixture with a malformed version.";
|
return "Fixture with a malformed version.";
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||||
int argc, char** argv)
|
int argc, char** argv) {
|
||||||
{
|
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
(void)command;
|
(void)command;
|
||||||
(void)argc;
|
(void)argc;
|
||||||
|
|||||||
9
tests/fixtures/src/good.cpp
vendored
9
tests/fixtures/src/good.cpp
vendored
@@ -24,21 +24,18 @@
|
|||||||
*/
|
*/
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
extern "C" const char* dpm_module_version(void)
|
extern "C" const char* dpm_module_version(void) {
|
||||||
{
|
|
||||||
return "1.2.3";
|
return "1.2.3";
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" const char* dpm_module_description(void)
|
extern "C" const char* dpm_module_description(void) {
|
||||||
{
|
|
||||||
return "Known-good stub module.";
|
return "Known-good stub module.";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Answers "ping" with 42 so a dispatch round trip is observable, and
|
/* Answers "ping" with 42 so a dispatch round trip is observable, and
|
||||||
0 for anything else. */
|
0 for anything else. */
|
||||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||||
int argc, char** argv)
|
int argc, char** argv) {
|
||||||
{
|
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
|
|||||||
6
tests/fixtures/src/missing_symbols.cpp
vendored
6
tests/fixtures/src/missing_symbols.cpp
vendored
@@ -21,12 +21,10 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern "C" const char* dpm_module_version(void)
|
extern "C" const char* dpm_module_version(void) {
|
||||||
{
|
|
||||||
return "1.0.0";
|
return "1.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" const char* dpm_module_description(void)
|
extern "C" const char* dpm_module_description(void) {
|
||||||
{
|
|
||||||
return "Fixture missing most of the contract.";
|
return "Fixture missing most of the contract.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,14 +50,12 @@ static int g_checks = 0;
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static bool error_contains(dpm_ctx* ctx, const char* needle)
|
static bool error_contains(dpm_ctx* ctx, const char* needle) {
|
||||||
{
|
|
||||||
const char* err = dpm_last_error(ctx);
|
const char* err = dpm_last_error(ctx);
|
||||||
return err != nullptr && std::strstr(err, needle) != nullptr;
|
return err != nullptr && std::strstr(err, needle) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void) {
|
||||||
{
|
|
||||||
/* ---- dpm_open: invalid explicit override refuses ---- */
|
/* ---- dpm_open: invalid explicit override refuses ---- */
|
||||||
{
|
{
|
||||||
dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1};
|
dpm_open_overrides bad = {"/does/not/exist/conf", nullptr, nullptr, -1};
|
||||||
|
|||||||
Reference in New Issue
Block a user