From a5e2c86882db20efd6e4e61c68233be515ac7907 Mon Sep 17 00:00:00 2001 From: Chris Punches Date: Thu, 27 Feb 2025 01:57:21 -0500 Subject: [PATCH] error handling layer implemented, header boilerplated for licensing info --- CMakeLists.txt | 6 +- include/ModuleLoader.hpp | 73 ++++++--- include/dpm_interface.hpp | 47 ++++-- include/dpm_interface_helpers.hpp | 44 ++++++ include/error.hpp | 50 +++++- include/handlers.hpp | 53 +++++++ include/module_interface.hpp | 30 ++++ modules/info.cpp | 32 +++- src/ModuleLoader.cpp | 244 +++++++++++++++++++++++------- src/dpm.cpp | 54 ++++++- src/dpm_interface.cpp | 196 ++++++++++-------------- src/dpm_interface_helpers.cpp | 74 +++++++++ src/error.cpp | 19 +++ src/handlers.cpp | 137 +++++++++++++++++ 14 files changed, 855 insertions(+), 204 deletions(-) create mode 100644 include/dpm_interface_helpers.hpp create mode 100644 include/handlers.hpp create mode 100644 src/dpm_interface_helpers.cpp create mode 100644 src/error.cpp create mode 100644 src/handlers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 81b63c6..f26e3ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" ) diff --git a/include/ModuleLoader.hpp b/include/ModuleLoader.hpp index 75ef68d..7c5141c 100644 --- a/include/ModuleLoader.hpp +++ b/include/ModuleLoader.hpp @@ -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 +* +* 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 . +* +* For bug reports or contributions, please contact the dhlp-contributors +* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors +*/ + #pragma once + #include #include -#include "error.hpp" #include #include #include +#include +#include +#include + +#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& 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& 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& 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& 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; }; \ No newline at end of file diff --git a/include/dpm_interface.hpp b/include/dpm_interface.hpp index d4cec7b..17ca9c3 100644 --- a/include/dpm_interface.hpp +++ b/include/dpm_interface.hpp @@ -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 + * + * 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 . + * + * For bug reports or contributions, please contact the dhlp-contributors + * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors + */ + #pragma once #include #include #include -#include "error.hpp" #include #include #include + +#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); \ No newline at end of file diff --git a/include/dpm_interface_helpers.hpp b/include/dpm_interface_helpers.hpp new file mode 100644 index 0000000..d1ef0e1 --- /dev/null +++ b/include/dpm_interface_helpers.hpp @@ -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 + * + * 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 . + * + * For bug reports or contributions, please contact the dhlp-contributors + * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors + */ + +#pragma once +#include +#include +#include + +// 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[] ); diff --git a/include/error.hpp b/include/error.hpp index b586c72..b779b0a 100644 --- a/include/error.hpp +++ b/include/error.hpp @@ -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 + * + * 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 . + * + * For bug reports or contributions, please contact the dhlp-contributors + * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors + */ + #pragma once +#include // 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 -}; \ No newline at end of file +}; + +// 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 ); + diff --git a/include/handlers.hpp b/include/handlers.hpp new file mode 100644 index 0000000..00f0df5 --- /dev/null +++ b/include/handlers.hpp @@ -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 + * + * 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 . + * + * For bug reports or contributions, please contact the dhlp-contributors + * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors + */ + +#pragma once +#include + +#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); diff --git a/include/module_interface.hpp b/include/module_interface.hpp index 3c0de9c..ad6f33c 100644 --- a/include/module_interface.hpp +++ b/include/module_interface.hpp @@ -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 +* +* 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 . +* +* For bug reports or contributions, please contact the dhlp-contributors +* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors +*/ + #pragma once #include #include diff --git a/modules/info.cpp b/modules/info.cpp index 18d9d6e..d3746cf 100644 --- a/modules/info.cpp +++ b/modules/info.cpp @@ -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 +* +* 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 . +* +* For bug reports or contributions, please contact the dhlp-contributors +* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors +*/ + #include #include #include #include -#include "../include/module_interface.hpp" -#include // Implementation of the info module // This module provides information about the DPM system diff --git a/src/ModuleLoader.cpp b/src/ModuleLoader.cpp index fe81a78..d9539bc 100644 --- a/src/ModuleLoader.cpp +++ b/src/ModuleLoader.cpp @@ -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& 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& 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 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 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& missing_symbols ) const +DPMErrorCategory ModuleLoader::validate_module_interface( void* module_handle, std::vector& 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; } diff --git a/src/dpm.cpp b/src/dpm.cpp index 16b8a9b..0d3e257 100644 --- a/src/dpm.cpp +++ b/src/dpm.cpp @@ -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 + * + * 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 . + * + * For bug reports or contributions, please contact the dhlp-contributors + * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors + */ + #include +#include #include #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 ); } \ No newline at end of file diff --git a/src/dpm_interface.cpp b/src/dpm_interface.cpp index 45dd56b..4a2cfd1 100644 --- a/src/dpm_interface.cpp +++ b/src/dpm_interface.cpp @@ -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 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 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 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 versions(valid_modules.size(), "unknown"); + std::vector 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; - } -} \ No newline at end of file diff --git a/src/dpm_interface_helpers.cpp b/src/dpm_interface_helpers.cpp new file mode 100644 index 0000000..35867ca --- /dev/null +++ b/src/dpm_interface_helpers.cpp @@ -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; +} \ No newline at end of file diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..b9fe8a9 --- /dev/null +++ b/src/error.cpp @@ -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; +} diff --git a/src/handlers.cpp b/src/handlers.cpp new file mode 100644 index 0000000..6399ac5 --- /dev/null +++ b/src/handlers.cpp @@ -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(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; +} \ No newline at end of file