error handling layer implemented, header boilerplated for licensing info

master
Chris Punches 2025-02-27 01:57:21 -05:00
parent e30ed309bd
commit a5e2c86882
14 changed files with 855 additions and 204 deletions

View File

@ -11,6 +11,10 @@ add_executable(
src/dpm.cpp
src/ModuleLoader.cpp
src/dpm_interface.cpp
src/error.cpp
include/dpm_interface_helpers.hpp
src/dpm_interface_helpers.cpp
src/handlers.cpp
)
target_include_directories(dpm PRIVATE include)
@ -19,7 +23,7 @@ target_link_libraries(dpm dl)
# Add the info module
add_library(info MODULE modules/info.cpp)
set_target_properties(info PROPERTIES
PREFIX "" # Remove lib prefix
PREFIX ""
SUFFIX ".so"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
)

View File

@ -1,32 +1,71 @@
/**
* @file ModuleLoader.hpp
* @brief Dynamic module loading and management for DPM
*
* Defines the ModuleLoader class which is responsible for finding, loading,
* validating, and executing DPM modules. It handles the dynamic loading of
* shared objects and ensures they conform to the expected module interface.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <string>
#include <vector>
#include "error.hpp"
#include <filesystem>
#include <dlfcn.h>
#include <iostream>
#include <cstring>
#include <dirent.h>
#include <sys/stat.h>
#include "error.hpp"
#include "module_interface.hpp"
class ModuleLoader {
public:
// initializer
explicit ModuleLoader(std::string module_path = "/usr/lib/dpm/modules/");
DPMError list_available_modules(std::vector<std::string>& modules) const;
DPMError get_module_path(std::string& path) const;
public:
// initializer
explicit ModuleLoader(std::string module_path = "/usr/lib/dpm/modules/");
DPMErrorCategory list_available_modules(std::vector<std::string>& modules) const;
DPMErrorCategory get_module_path(std::string& path) const;
// Load and execute methods
DPMError load_module(const std::string& module_name, void*& module_handle) const;
DPMError execute_module(const std::string& module_name, const std::string& command) const;
// Load and execute methods
DPMErrorCategory load_module(const std::string& module_name, void*& module_handle) const;
DPMErrorCategory execute_module(const std::string& module_name, const std::string& command) const;
// Get module version
DPMError get_module_version(void* module_handle, std::string& version) const;
// Get module version
DPMErrorCategory get_module_version(void* module_handle, std::string& version) const;
// Get module description
DPMError get_module_description(void* module_handle, std::string& description) const;
// Get module description
DPMErrorCategory get_module_description(void* module_handle, std::string& description) const;
// Check if all required symbols from module_interface.hpp are exported by the module
DPMError validate_module_interface(void* module_handle, std::vector<std::string>& missing_symbols) const;
// Check if all required symbols from module_interface.hpp are exported by the module
DPMErrorCategory validate_module_interface(void* module_handle, std::vector<std::string>& missing_symbols) const;
private:
std::string _module_path;
// Helper method to check module path validity
DPMErrorCategory check_module_path() const;
private:
std::string _module_path;
};

View File

@ -1,12 +1,44 @@
/**
* @file dpm_interface.hpp
* @brief Interface declarations for the DPM command-line functionality
*
* Defines the public interface methods that provide human-readable interaction
* with the DPM core functionality, including module path validation and
* module listing capabilities.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <iostream>
#include <getopt.h>
#include <vector>
#include "error.hpp"
#include <dlfcn.h>
#include <iomanip>
#include <filesystem>
#include "error.hpp"
#include "ModuleLoader.hpp"
#include "dpm_interface_helpers.hpp"
/*
*
@ -21,16 +53,3 @@ int main_check_module_path(const ModuleLoader& loader);
// list the modules
int main_list_modules(const ModuleLoader& loader);
// data structure for supplied arguments
struct CommandArgs {
std::string module_path;
std::string module_name;
std::string command; // All arguments combined into a single command string
};
// parser for populating data structure for supplied arguments
CommandArgs parse_args(int argc, char* argv[]);
// pairs DPMErrors to error messages, prints those error messages, and returns
int print_error(DPMError error, const std::string& module_name, const std::string& module_path);

View File

@ -0,0 +1,44 @@
/**
* @file dpm_interface_helpers.hpp
* @brief Helper functions for DPM command-line interface
*
* Provides utility functions for command-line argument parsing and
* data structures for representing command arguments in a structured format.
* These helpers are used by the main DPM interface to process user input.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <string>
#include <iostream>
#include <getopt.h>
// data structure for supplied arguments
struct CommandArgs {
std::string module_path;
std::string module_name;
std::string command; // All arguments combined into a single command string
};
// parse dpm cli arguments into a serialized structure
CommandArgs parse_args( int argc, char * argv[] );

View File

@ -1,10 +1,42 @@
/**
* @file error.hpp
* @brief Error handling system for the DPM utility
*
* Defines the error categories, error context structure, and utility
* functions for error handling throughout the DPM system. Provides a
* consistent approach to error reporting and management.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <iostream>
// global errors for the core DPM routing/execution component
enum class DPMError {
enum class DPMErrorCategory {
SUCCESS,
PATH_NOT_FOUND,
PATH_NOT_DIRECTORY,
PATH_TOO_LONG,
PERMISSION_DENIED,
MODULE_NOT_FOUND,
MODULE_NOT_LOADED,
@ -13,4 +45,18 @@ enum class DPMError {
SYMBOL_NOT_FOUND,
SYMBOL_EXECUTION_FAILED,
UNDEFINED_ERROR
};
};
// A generic context object that can hold any error-specific data
// only DPMErrorCategory is required, all other fields are optional
typedef struct {
DPMErrorCategory error;
const char * module_name;
const char * module_path;
const char * message;
// Add other potential fields as needed as all fields beyond error are optional
} FlexDPMError;
// shorthand for creating a FlexDPMError instance
FlexDPMError make_error( DPMErrorCategory error_category );

53
include/handlers.hpp Normal file
View File

@ -0,0 +1,53 @@
/**
* @file handlers.hpp
* @brief Error handling functions for the DPM system
*
* Defines specialized handler functions for each error category in the DPM
* error system. These handlers translate error codes into user-friendly
* messages and provide appropriate exit behavior for different error conditions.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <iostream>
#include "error.hpp"
// fatal error routing method
int handle_error(FlexDPMError context);
// declare a required field
void validate_field(FlexDPMError context, const char* field_name, const void* field_value);
// Individual error handler prototypes
int handle_path_not_found(FlexDPMError context);
int handle_path_not_directory(FlexDPMError context);
int handle_path_too_long(FlexDPMError context);
int handle_permission_denied(FlexDPMError context);
int handle_module_not_found(FlexDPMError context);
int handle_module_not_loaded(FlexDPMError context);
int handle_module_load_failed(FlexDPMError context);
int handle_invalid_module(FlexDPMError context);
int handle_symbol_not_found(FlexDPMError context);
int handle_symbol_execution_failed(FlexDPMError context);
int handle_undefined_error(FlexDPMError context);

View File

@ -1,3 +1,33 @@
/**
* @file module_interface.hpp
* @brief Defines the interface for DPM modules
*
* Establishes the required symbols and common interface that all DPM modules
* must implement to be loadable and executable by the core DPM system.
* This forms the contract between the main DPM application and its modules.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#pragma once
#include <string>
#include <vector>

View File

@ -1,9 +1,37 @@
/**
* @file info.cpp
* @brief Implementation of the DPM info module
*
* Provides information about the DPM system through a module interface.
* This module supports commands for displaying version information,
* system details, and module help documentation.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include "../include/module_interface.hpp"
#include <gpgme.h>
// Implementation of the info module
// This module provides information about the DPM system

View File

@ -2,6 +2,7 @@
namespace fs = std::filesystem;
// ModuleLoader constructor
ModuleLoader::ModuleLoader(std::string module_path)
{
try {
@ -17,67 +18,200 @@ ModuleLoader::ModuleLoader(std::string module_path)
}
}
DPMError ModuleLoader::get_module_path(std::string& path) const
// getter for module path
DPMErrorCategory ModuleLoader::get_module_path(std::string& path) const
{
path = _module_path;
return DPMError::SUCCESS;
return DPMErrorCategory::SUCCESS;
}
DPMError ModuleLoader::list_available_modules(std::vector<std::string>& modules) const
// Helper method to check module path validity
DPMErrorCategory ModuleLoader::check_module_path() const
{
modules.clear();
try {
for (const auto& entry : fs::directory_iterator(_module_path)) {
if (entry.is_regular_file()) {
std::string filename = entry.path().filename().string();
if (filename.size() > 3 && filename.substr(filename.size() - 3) == ".so") {
modules.push_back(filename.substr(0, filename.size() - 3));
}
}
// Verify the path exists and get its properties
struct stat path_stat;
if (stat(_module_path.c_str(), &path_stat) != 0) {
// Check errno to determine the specific error
switch (errno) {
case ENOENT:
return DPMErrorCategory::PATH_NOT_FOUND;
case EACCES:
return DPMErrorCategory::PERMISSION_DENIED;
case ENAMETOOLONG:
return DPMErrorCategory::PATH_TOO_LONG;
case ENOTDIR:
// This happens when a component of the path prefix isn't a directory
return DPMErrorCategory::PATH_NOT_DIRECTORY;
default:
return DPMErrorCategory::UNDEFINED_ERROR;
}
} catch (const fs::filesystem_error&) {
return DPMError::PERMISSION_DENIED;
}
return DPMError::SUCCESS;
// At this point stat() succeeded, now check if the final path component is a directory
if (!S_ISDIR(path_stat.st_mode)) {
return DPMErrorCategory::PATH_NOT_DIRECTORY;
}
// Check read permissions using the stat results
if ((path_stat.st_mode & S_IRUSR) == 0) {
return DPMErrorCategory::PERMISSION_DENIED;
}
return DPMErrorCategory::SUCCESS;
}
DPMError ModuleLoader::load_module(const std::string& module_name, void*& module_handle) const
DPMErrorCategory ModuleLoader::list_available_modules(std::vector<std::string>& modules) const
{
// ensure we start with an empty vector
modules.clear();
// Check module path using the helper method
DPMErrorCategory path_check = check_module_path();
if (path_check != DPMErrorCategory::SUCCESS) {
return path_check;
}
// prepare to iterate the directory
DIR* dir = opendir(_module_path.c_str());
if (!dir) {
// Check errno to determine the cause of the failure
switch (errno) {
case EACCES:
return DPMErrorCategory::PERMISSION_DENIED;
case ENOENT:
return DPMErrorCategory::PATH_NOT_FOUND;
case ENOTDIR:
return DPMErrorCategory::PATH_NOT_DIRECTORY;
default:
return DPMErrorCategory::UNDEFINED_ERROR;
}
}
// read each entry
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
// build the full path
std::string full_path = _module_path + entry->d_name;
// verify it's a file or a symlink
struct stat st;
if (stat(full_path.c_str(), &st) == -1) {
continue;
}
// Skip if not a regular file or a symlink
if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) {
continue;
}
// get length of filename for boundary checking
size_t name_len = strlen(entry->d_name);
// skip if filename too short to be .so
if (name_len <= 3) {
continue;
}
// verify file ends in .so
if (strcmp(entry->d_name + name_len - 3, ".so") != 0) {
continue;
}
// calculate the module name length
size_t module_name_len = name_len - 3;
// create space for the module name
char * module_name = (char*) malloc(module_name_len + 1);
if (!module_name) {
closedir(dir);
return DPMErrorCategory::UNDEFINED_ERROR;
}
// copy the name without .so
strncpy(module_name, entry->d_name, module_name_len);
module_name[module_name_len] = '\0';
// add to our list
modules.push_back(std::string(module_name));
// clean up
free(module_name);
}
// clean up
closedir(dir);
return DPMErrorCategory::SUCCESS;
}
DPMErrorCategory ModuleLoader::load_module(const std::string& module_name, void*& module_handle) const
{
// First check if the module exists in the list of available modules
std::vector<std::string> available_modules;
DPMErrorCategory list_error = list_available_modules( available_modules );
// if there was a listing error, return that
if ( list_error != DPMErrorCategory::SUCCESS ) {
return list_error;
}
// Check if the requested module is in the list of available modules
bool module_found = false;
for ( size_t i = 0; i < available_modules.size(); i++ ) {
if ( available_modules[i] == module_name ) {
module_found = true;
break;
}
}
// if the supplied module isn't in the list of available modules, return the error
if ( !module_found ) {
return DPMErrorCategory::MODULE_NOT_FOUND;
}
// construct the path to load the module from based on supplied identifier
// DPM uses whatever the file name is
std::string module_so_path = _module_path + module_name + ".so";
// go ahead and open the module
module_handle = dlopen(module_so_path.c_str(), RTLD_LAZY);
if ( !module_handle ) {
return DPMError::MODULE_LOAD_FAILED;
if (!module_handle) {
return DPMErrorCategory::MODULE_LOAD_FAILED;
}
// if there was a loading error, return that
const char * load_error = dlerror();
if ( load_error != nullptr ) {
return DPMError::MODULE_LOAD_FAILED;
return DPMErrorCategory::MODULE_LOAD_FAILED;
}
// validate the module's exposed API
// return an error if it's not up to spec
std::vector<std::string> missing_symbols;
DPMError validate_error = validate_module_interface(module_handle, missing_symbols);
if ( validate_error != DPMError::SUCCESS ) {
dlclose(module_handle);
DPMErrorCategory validate_error = validate_module_interface( module_handle, missing_symbols );
if ( validate_error != DPMErrorCategory::SUCCESS ) {
// we failed to validate the interface, so close the module handle since we won't use it
dlclose( module_handle );
return validate_error;
}
// return what's going to be a success
return validate_error;
}
DPMError ModuleLoader::execute_module( const std::string& module_name, const std::string& command ) const
DPMErrorCategory ModuleLoader::execute_module( const std::string& module_name, const std::string& command ) const
{
// declare a module_handle
void * module_handle;
// attempt to load the module
DPMError load_error = load_module( module_name, module_handle );
if ( load_error != DPMError::SUCCESS ) {
DPMErrorCategory load_error = load_module( module_name, module_handle );
if ( load_error != DPMErrorCategory::SUCCESS ) {
return load_error;
}
@ -85,7 +219,7 @@ DPMError ModuleLoader::execute_module( const std::string& module_name, const std
const char* pre_error = dlerror();
if ( pre_error != nullptr ) {
dlclose( module_handle );
return DPMError::UNDEFINED_ERROR;
return DPMErrorCategory::UNDEFINED_ERROR;
}
// declare a function pointer type to hold the module symbol to execute
@ -98,13 +232,13 @@ DPMError ModuleLoader::execute_module( const std::string& module_name, const std
const char * dlsym_error = dlerror();
if ( dlsym_error != nullptr ) {
dlclose( module_handle );
return DPMError::SYMBOL_NOT_FOUND;
return DPMErrorCategory::SYMBOL_NOT_FOUND;
}
// check if the void pointer was populated
if ( execute_fn == nullptr ) {
dlclose( module_handle );
return DPMError::SYMBOL_NOT_FOUND;
return DPMErrorCategory::SYMBOL_NOT_FOUND;
}
// execute the symbol that was loaded and supply the command string being routed from DPM
@ -115,26 +249,26 @@ DPMError ModuleLoader::execute_module( const std::string& module_name, const std
// if the result of execution was not 0, return an error
if ( exec_error != 0 ) {
return DPMError::SYMBOL_EXECUTION_FAILED;
return DPMErrorCategory::SYMBOL_EXECUTION_FAILED;
}
// if we made it here, assume it was successful
return DPMError::SUCCESS;
return DPMErrorCategory::SUCCESS;
}
DPMError ModuleLoader::get_module_version( void * module_handle, std::string& version ) const
DPMErrorCategory ModuleLoader::get_module_version( void * module_handle, std::string& version ) const
{
// validate that the module is even loaded
if ( !module_handle ) {
version = "DPM ERROR";
return DPMError::MODULE_NOT_LOADED;
return DPMErrorCategory::MODULE_NOT_LOADED;
}
// Clear any previous error state and handle any residual failure
const char* pre_error = dlerror();
if ( pre_error != nullptr ) {
version = pre_error;
return DPMError::UNDEFINED_ERROR;
return DPMErrorCategory::UNDEFINED_ERROR;
}
// declare a function pointer type to hold the module symbol to execute
@ -147,13 +281,13 @@ DPMError ModuleLoader::get_module_version( void * module_handle, std::string& ve
const char* error = dlerror();
if (error != nullptr) {
version = error;
return DPMError::SYMBOL_NOT_FOUND;
return DPMErrorCategory::SYMBOL_NOT_FOUND;
}
// check if the void pointer was populated
if ( version_fn == nullptr ) {
version = "ERROR";
return DPMError::SYMBOL_NOT_FOUND;
return DPMErrorCategory::SYMBOL_NOT_FOUND;
}
// execute the loaded symbol
@ -162,26 +296,29 @@ DPMError ModuleLoader::get_module_version( void * module_handle, std::string& ve
// check the return, and throw an error if it's a null value
if ( ver == nullptr ) {
version = "MODULE ERROR";
return DPMError::INVALID_MODULE;
return DPMErrorCategory::INVALID_MODULE;
}
// Set the version string with the result
version = ver;
// if you made it here, assume success
return DPMError::SUCCESS;
return DPMErrorCategory::SUCCESS;
}
DPMError ModuleLoader::get_module_description( void * module_handle, std::string& description ) const
DPMErrorCategory ModuleLoader::get_module_description( void * module_handle, std::string& description ) const
{
// validate that the module is even loaded
if (!module_handle) {
description = "DPM ERROR";
return DPMError::MODULE_NOT_LOADED;
return DPMErrorCategory::MODULE_NOT_LOADED;
}
// Clear any previous error state and handle any residual failure
const char* pre_error = dlerror();
if ( pre_error != nullptr ) {
description = pre_error;
return DPMError::UNDEFINED_ERROR;
return DPMErrorCategory::UNDEFINED_ERROR;
}
// declare a function pointer type to hold the module symbol to execute
@ -191,16 +328,16 @@ DPMError ModuleLoader::get_module_description( void * module_handle, std::string
DescriptionFn description_fn = (DescriptionFn) dlsym( module_handle, "dpm_get_description" );
// check for errors from dlsym
const char* error = dlerror();
const char * error = dlerror();
if ( error != nullptr ) {
description = "ERROR";
return DPMError::SYMBOL_NOT_FOUND;
return DPMErrorCategory::SYMBOL_NOT_FOUND;
}
// check if the void pointer was populated
if ( description_fn == nullptr ) {
description = "ERROR";
return DPMError::INVALID_MODULE;
return DPMErrorCategory::INVALID_MODULE;
}
// execute the loaded symbol
@ -209,18 +346,21 @@ DPMError ModuleLoader::get_module_description( void * module_handle, std::string
// check the return, and throw an error if it's a null value
if ( desc == nullptr ) {
description = "MODULE ERROR";
return DPMError::INVALID_MODULE;
return DPMErrorCategory::INVALID_MODULE;
}
// Set the description string with the result
description = desc;
// if you made it here, assume success
return DPMError::SUCCESS;
return DPMErrorCategory::SUCCESS;
}
DPMError ModuleLoader::validate_module_interface( void* module_handle, std::vector<std::string>& missing_symbols ) const
DPMErrorCategory ModuleLoader::validate_module_interface( void* module_handle, std::vector<std::string>& missing_symbols ) const
{
// validate that the module is even loaded
if ( !module_handle ) {
return DPMError::MODULE_NOT_LOADED;
return DPMErrorCategory::MODULE_NOT_LOADED;
}
// ensure our starting point of missing symbols is empty
@ -232,7 +372,7 @@ DPMError ModuleLoader::validate_module_interface( void* module_handle, std::vect
// check for any residual lingering errors
const char * pre_error = dlerror();
if ( pre_error != nullptr ) {
return DPMError::UNDEFINED_ERROR;
return DPMErrorCategory::UNDEFINED_ERROR;
}
// declare a function pointer type to hold the module symbol to execute
@ -255,9 +395,9 @@ DPMError ModuleLoader::validate_module_interface( void* module_handle, std::vect
// if there are no missing symbols, return successfully -- the module has a valid API
if ( missing_symbols.empty() ) {
return DPMError::SUCCESS;
return DPMErrorCategory::SUCCESS;
}
// if not successful, the module's API is invalid and return the appropriate error code
return DPMError::INVALID_MODULE;
return DPMErrorCategory::INVALID_MODULE;
}

View File

@ -1,8 +1,40 @@
/**
* @file dpm.cpp
* @brief Main entry point for the Dark Horse Package Manager (DPM)
*
* Implements the core command-line interface and module routing functionality
* for the DPM utility, handling argument parsing, module loading, and execution.
*
* @copyright Copyright (c) 2025 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/>.
*
* For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/
#include <iostream>
#include <handlers.hpp>
#include <dlfcn.h>
#include "ModuleLoader.hpp"
#include "dpm_interface.hpp"
#include "dpm_interface_helpers.hpp"
#include "error.hpp"
/*
* DPM serves three functions:
@ -17,7 +49,16 @@ int default_behavior(const ModuleLoader& loader)
return main_list_modules(loader);
}
// entry point for the DPM utility
/**
* @brief Entry point for the DPM utility
*
* Processes command-line arguments, loads and executes the appropriate module,
* and handles any errors that occur during execution.
*
* @param argc Number of command-line arguments
* @param argv Array of C-style strings containing the arguments
* @return Exit code indicating success (0) or failure (non-zero)
*/
int main( int argc, char* argv[] )
{
// process the arguments suppplied to DPM and provide
@ -43,8 +84,15 @@ int main( int argc, char* argv[] )
}
// execute the module
DPMError execute_error = loader.execute_module(args.module_name, args.command);
DPMErrorCategory execute_error = loader.execute_module(args.module_name, args.command);
std::string extracted_path;
loader.get_module_path(extracted_path);
FlexDPMError result = make_error(execute_error);
result.module_name = args.module_name.c_str();
result.module_path = extracted_path.c_str();
// pair result with a message and exit with the appropriate error code
return print_error( execute_error, args.module_name, args.module_path );
return handle_error( result );
}

View File

@ -1,14 +1,34 @@
#include "dpm_interface.hpp"
/*
* DPM Interface methods.
*
* DPM Interface methods. These are wrappers of DPM functionality that are meant to handle user view, turning
* error codes into human-presentable information, etc. Features are defined internally, these will only ever be
* wrappers of existing features to provide the human/cli interface.
* These are for entry points for the DPM cli.
*
* These are wrappers of DPM functionality that are meant to handle user
* view, turning error codes into human-presentable information, etc.
*
* Also includes helpers related to the CLI.
*/
// check if the module path exists
/**
* Verify that the module path exists and is accessible.
*
* This function checks if the configured module path exists, is a directory,
* and has the necessary read permissions.
*
* @param loader Reference to a ModuleLoader object that provides the module path
*
* @return 0 if the path exists and is accessible, 1 otherwise
*
* The function performs the following checks:
* 1. Retrieves the module path from the loader
* 2. Verifies that the path exists in the filesystem
* 3. Confirms that the path is a directory
* 4. Checks that the directory has read permissions
*
* If any check fails, an appropriate error message is displayed to stderr.
*/
int main_check_module_path(const ModuleLoader& loader)
{
std::string path;
@ -38,20 +58,46 @@ int main_check_module_path(const ModuleLoader& loader)
return 0;
}
// list the modules
/**
* List all available and valid DPM modules.
*
* This function retrieves and displays a formatted table of available DPM modules
* from the specified module path, including their versions and descriptions.
*
* @param loader Reference to a ModuleLoader object that provides access to modules
*
* @return 0 on success, 1 on failure
*
* The function performs the following operations:
* 1. Gets the configured module path from the loader
* 2. Retrieves a list of all potential modules in that path
* 3. Validates each module by checking for required symbols
* 4. Collects version and description information from valid modules
* 5. Formats and displays the information in a tabular format
*
* If no modules are found or if no valid modules are found, appropriate
* messages are displayed.
*
* Modules are considered valid if they expose all required interface
* symbols as defined in module_interface.hpp.
*/
int main_list_modules(const ModuleLoader& loader)
{
// initialize an empty modules list
std::vector<std::string> modules;
// initialize an empty path
std::string path;
DPMError get_path_error = loader.get_module_path(path);
if (get_path_error != DPMError::SUCCESS) {
// set the module path
DPMErrorCategory get_path_error = loader.get_module_path(path);
if ( get_path_error != DPMErrorCategory::SUCCESS ) {
std::cerr << "Failed to get module path" << std::endl;
return 1;
}
DPMError list_error = loader.list_available_modules(modules);
if (list_error != DPMError::SUCCESS) {
DPMErrorCategory list_error = loader.list_available_modules(modules);
if (list_error != DPMErrorCategory::SUCCESS) {
std::cerr << "No modules found in: " << path << std::endl;
return 1;
}
@ -61,17 +107,18 @@ int main_list_modules(const ModuleLoader& loader)
return 0;
}
// First pass: Identify valid modules
std::vector<std::string> valid_modules;
for (int i = 0; i < modules.size(); i++) {
void* handle;
DPMError load_error = loader.load_module(modules[i], handle);
if (load_error != DPMError::SUCCESS) {
DPMErrorCategory load_error = loader.load_module(modules[i], handle);
if (load_error != DPMErrorCategory::SUCCESS) {
continue;
}
std::vector<std::string> missing_symbols;
DPMError validate_error = loader.validate_module_interface(handle, missing_symbols);
if (validate_error == DPMError::SUCCESS) {
DPMErrorCategory validate_error = loader.validate_module_interface(handle, missing_symbols);
if (validate_error == DPMErrorCategory::SUCCESS) {
valid_modules.push_back(modules[i]);
}
dlclose(handle);
@ -82,130 +129,53 @@ int main_list_modules(const ModuleLoader& loader)
return 0;
}
// Second pass: Collect module information and calculate column widths
size_t max_name_length = 0;
size_t max_version_length = 0;
std::vector<std::string> versions(valid_modules.size(), "unknown");
std::vector<std::string> descriptions(valid_modules.size(), "unknown");
for (int i = 0; i < valid_modules.size(); i++) {
void* module_handle;
std::string version;
max_name_length = std::max(max_name_length, valid_modules[i].length());
DPMError load_error = loader.load_module(valid_modules[i], module_handle);
if (load_error == DPMError::SUCCESS) {
DPMError version_error = loader.get_module_version(module_handle, version);
if (version_error == DPMError::SUCCESS) {
DPMErrorCategory load_error = loader.load_module(valid_modules[i], module_handle);
if (load_error == DPMErrorCategory::SUCCESS) {
// Get version
std::string version = "unknown";
DPMErrorCategory version_error = loader.get_module_version(module_handle, version);
if (version_error == DPMErrorCategory::SUCCESS) {
versions[i] = version;
max_version_length = std::max(max_version_length, version.length());
}
// Get description
std::string description = "unknown";
DPMErrorCategory desc_error = loader.get_module_description(module_handle, description);
if (desc_error == DPMErrorCategory::SUCCESS) {
descriptions[i] = description;
}
dlclose(module_handle);
}
}
const int column_spacing = 4;
// Display the table header
std::cout << "\nAvailable modules in '" << path << "':" << std::endl << std::endl;
std::cout << std::left << std::setw(max_name_length + column_spacing) << "MODULE"
<< std::setw(max_version_length + column_spacing) << "VERSION"
<< "DESCRIPTION" << std::endl;
// Display the table rows
for (int i = 0; i < valid_modules.size(); i++) {
void* module_handle;
std::string version = "unknown";
std::string description = "unknown";
DPMError load_error = loader.load_module(valid_modules[i], module_handle);
if (load_error == DPMError::SUCCESS) {
DPMError version_error = loader.get_module_version(module_handle, version);
DPMError desc_error = loader.get_module_description(module_handle, description);
dlclose(module_handle);
}
std::cout << std::left << std::setw(max_name_length + column_spacing) << valid_modules[i]
<< std::setw(max_version_length + column_spacing) << version
<< description << std::endl;
<< std::setw(max_version_length + column_spacing) << versions[i]
<< descriptions[i] << std::endl;
}
return 0;
}
CommandArgs parse_args(int argc, char* argv[])
{
CommandArgs args;
args.module_path = "/usr/lib/dpm/modules/"; // Set to same default as ModuleLoader
static struct option long_options[] = {
{"module-path", required_argument, 0, 'm'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "m:h", long_options, &option_index)) != -1) {
switch (opt) {
case 'm':
args.module_path = optarg;
break;
case 'h':
std::cout << "Usage: dpm [options] [module-name] [module args...]\n\n"
<< "Options:\n\n"
<< " -m, --module-path PATH Path to DPM modules\n"
<< " -h, --help Show this help message\n\n"
<< "If no module is specified, available modules will be listed.\n\n";
exit(0);
case '?':
exit(1);
}
}
if (optind < argc) {
args.module_name = argv[optind++];
for (int i = optind; i < argc; i++) {
if (!args.command.empty()) {
args.command += " ";
}
std::string arg = argv[i];
if (arg.find(' ') != std::string::npos) {
args.command += "\"" + arg + "\"";
} else {
args.command += arg;
}
}
}
return args;
}
int print_error(DPMError error, const std::string& module_name, const std::string& module_path) {
switch (error) {
case DPMError::SUCCESS:
return 0;
case DPMError::PATH_NOT_FOUND:
std::cerr << "Module path not found: " << module_path << std::endl;
return 1;
case DPMError::PATH_NOT_DIRECTORY:
std::cerr << "Module path is not a directory: " << module_path << std::endl;
return 1;
case DPMError::PERMISSION_DENIED:
std::cerr << "Permission denied accessing module: " << module_name << std::endl;
return 1;
case DPMError::MODULE_NOT_FOUND:
std::cerr << "Module not found: " << module_name << std::endl;
return 1;
case DPMError::MODULE_NOT_LOADED:
std::cerr << "Attempted to execute module before loading it: " << module_name << std::endl;
return 1;
case DPMError::MODULE_LOAD_FAILED:
std::cerr << "Failed to load module: " << module_name << std::endl;
return 1;
case DPMError::INVALID_MODULE:
std::cerr << "Invalid module format: " << module_name << std::endl;
return 1;
case DPMError::UNDEFINED_ERROR:
std::cerr << "Undefined error occurred with module: " << module_name << std::endl;
return 1;
default:
std::cerr << "Unknown error executing module: " << module_name << std::endl;
return 1;
}
}

View File

@ -0,0 +1,74 @@
#include "dpm_interface_helpers.hpp"
/**
* Parse command line arguments for DPM.
*
* This function parses the command line arguments provided to DPM
* and builds a CommandArgs structure containing the parsed values.
*
* @param argc The number of arguments provided to the program
* @param argv Array of C-style strings containing the arguments
*
* @return CommandArgs structure containing the parsed command line arguments
*
* The function handles the following arguments:
* - ``-m, --module-path PATH``: Sets the directory path where DPM modules are located
* - ``-h, --help``: Displays a help message and exits
*
* Additional arguments are processed as follows:
* - First non-option argument is treated as the module name
* - All remaining arguments are combined into a single command string for the module
*
* If the argument contains spaces, it will be quoted in the command string.
*
* If no module name is provided, the module_name field will be empty.
*/
CommandArgs parse_args(int argc, char* argv[])
{
CommandArgs args;
args.module_path = "/usr/lib/dpm/modules/"; // Set to same default as ModuleLoader
static struct option long_options[] = {
{"module-path", required_argument, 0, 'm'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "m:h", long_options, &option_index)) != -1) {
switch (opt) {
case 'm':
args.module_path = optarg;
break;
case 'h':
std::cout << "Usage: dpm [options] [module-name] [module args...]\n\n"
<< "Options:\n\n"
<< " -m, --module-path PATH Path to DPM modules\n"
<< " -h, --help Show this help message\n\n"
<< "If no module is specified, available modules will be listed.\n\n";
exit(0);
case '?':
exit(1);
}
}
if (optind < argc) {
args.module_name = argv[optind++];
for (int i = optind; i < argc; i++) {
if (!args.command.empty()) {
args.command += " ";
}
std::string arg = argv[i];
if (arg.find(' ') != std::string::npos) {
args.command += "\"" + arg + "\"";
} else {
args.command += arg;
}
}
}
return args;
}

19
src/error.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "error.hpp"
// Simple helper function that takes only the required error category
FlexDPMError make_error(DPMErrorCategory error_category)
{
// Create an empty error struct
FlexDPMError error;
// Set the error category
error.error = error_category;
// Initialize the other fields to NULL
error.module_name = NULL;
error.module_path = NULL;
error.message = NULL;
// let the consumer populate any other fields they want with `self.field_name = whatever`.
return error;
}

137
src/handlers.cpp Normal file
View File

@ -0,0 +1,137 @@
#include "handlers.hpp"
// Helper function for validating required fields in a FlexDPMError
void validate_field(FlexDPMError context, const char* field_name, const void* field_value)
{
if (!field_value) {
std::cerr << "Error: Incomplete error context. Missing required field: " << field_name;
std::cerr << " (Error category: " << static_cast<int>(context.error) << ")" << std::endl;
// Hard exit when a required field is missing
exit(1);
}
}
// Main error handler that dispatches to specific handlers
int handle_error(FlexDPMError context) {
if (context.error == DPMErrorCategory::SUCCESS) {
return 0;
}
switch (context.error) {
case DPMErrorCategory::PATH_NOT_FOUND:
validate_field(context, "module_path", context.module_path);
return handle_path_not_found(context);
case DPMErrorCategory::PATH_NOT_DIRECTORY:
validate_field(context, "module_path", context.module_path);
return handle_path_not_directory(context);
case DPMErrorCategory::PATH_TOO_LONG:
validate_field(context, "module_path", context.module_path);
return handle_path_too_long(context);
case DPMErrorCategory::PERMISSION_DENIED:
validate_field(context, "module_path", context.module_path);
return handle_permission_denied(context);
case DPMErrorCategory::MODULE_NOT_FOUND:
validate_field(context, "module_name", context.module_name);
validate_field(context, "module_path", context.module_path);
return handle_module_not_found(context);
case DPMErrorCategory::MODULE_NOT_LOADED:
validate_field(context, "module_name", context.module_name);
return handle_module_not_loaded(context);
case DPMErrorCategory::MODULE_LOAD_FAILED:
validate_field(context, "module_name", context.module_name);
return handle_module_load_failed(context);
case DPMErrorCategory::INVALID_MODULE:
validate_field(context, "module_name", context.module_name);
return handle_invalid_module(context);
case DPMErrorCategory::SYMBOL_NOT_FOUND:
validate_field(context, "module_name", context.module_name);
return handle_symbol_not_found(context);
case DPMErrorCategory::SYMBOL_EXECUTION_FAILED:
validate_field(context, "module_name", context.module_name);
return handle_symbol_execution_failed(context);
case DPMErrorCategory::UNDEFINED_ERROR:
return handle_undefined_error(context);
default:
std::cerr << "Error: Unknown error code" << std::endl;
return 1;
}
}
// Now the individual handlers can be simplified since required fields are guaranteed
int handle_path_not_found( FlexDPMError context ) {
std::cerr << "Fatal error: The module directory '" << context.module_path << "' was not found. Exiting." << std::endl;
return 1;
}
int handle_path_not_directory( FlexDPMError context ) {
std::cerr << "Fatal error: The module path '" << context.module_path << "' is not a directory. Exiting." << std::endl;
return 1;
}
int handle_path_too_long( FlexDPMError context ) {
std::cerr << "Error: Module path is too long: '" << context.module_path << "'. Exiting." << std::endl;
return 1;
}
int handle_permission_denied( FlexDPMError context ) {
std::cerr << "Error: Permission denied accessing the modules path: '" << context.module_path << "'. Exiting." << std::endl;
return 1;
}
int handle_module_not_found( FlexDPMError context ) {
std::cerr << "Error: Module '"<< context.module_name << "' not found in '" << context.module_path << "'. Exiting." << std::endl;
return 1;
}
int handle_module_not_loaded( FlexDPMError context ) {
std::cerr << "Error: Attempted to execute module before loading it: " << context.module_name << std::endl;
return 1;
}
int handle_module_load_failed( FlexDPMError context ) {
std::cerr << "Error: Failed to load module: " << context.module_name << std::endl;
return 1;
}
int handle_invalid_module( FlexDPMError context ) {
std::cerr << "Error: Invalid module format: " << context.module_name << std::endl;
return 1;
}
int handle_symbol_not_found( FlexDPMError context ) {
std::cerr << "Error: Symbol not found in module: " << context.module_name;
if (context.message) {
std::cerr << " (" << context.message << ")";
}
std::cerr << std::endl;
return 1;
}
int handle_symbol_execution_failed(FlexDPMError context) {
std::cerr << "Error: Module execution failed: " << context.module_name << std::endl;
return 1;
}
int handle_undefined_error(FlexDPMError context) {
std::cerr << "Error: Undefined error occurred";
if (context.module_name) {
std::cerr << " with module: " << context.module_name;
}
if (context.message) {
std::cerr << " (" << context.message << ")";
}
std::cerr << std::endl;
return 1;
}