From 1ce163ef29bc335ae684ab61b42fc08e59d6d84d Mon Sep 17 00:00:00 2001 From: Chris Punches Date: Mon, 17 Feb 2025 23:10:35 -0500 Subject: [PATCH] First Commit --- CMakeLists.txt | 25 +++++++++ README.md | 8 ++- include/ModuleLoader.hpp | 23 ++++++++ include/dpm_interface.hpp | 30 ++++++++++ include/error.hpp | 11 ++++ include/module_interface.hpp | 12 ++++ modules/info.cpp | 72 ++++++++++++++++++++++++ src/ModuleLoader.cpp | 106 +++++++++++++++++++++++++++++++++++ src/dpm.cpp | 50 +++++++++++++++++ src/dpm_interface.cpp | 106 +++++++++++++++++++++++++++++++++++ 10 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 CMakeLists.txt create mode 100644 include/ModuleLoader.hpp create mode 100644 include/dpm_interface.hpp create mode 100644 include/error.hpp create mode 100644 include/module_interface.hpp create mode 100644 modules/info.cpp create mode 100644 src/ModuleLoader.cpp create mode 100644 src/dpm.cpp create mode 100644 src/dpm_interface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..81b63c6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.22) +project(dpm) + +set(CMAKE_CXX_STANDARD 20) + +# Create modules directory +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/modules) + +add_executable( + dpm + src/dpm.cpp + src/ModuleLoader.cpp + src/dpm_interface.cpp +) + +target_include_directories(dpm PRIVATE include) +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 + SUFFIX ".so" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" +) diff --git a/README.md b/README.md index 4209474..267d1a1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # DPM-Core -Dark Horse Linux Package Manager: Core Component \ No newline at end of file +The core component of the Dark Horse Linux Package Manager + +# What is DPM? + +https://dpm.darkhorselinux.org + + diff --git a/include/ModuleLoader.hpp b/include/ModuleLoader.hpp new file mode 100644 index 0000000..279e0f6 --- /dev/null +++ b/include/ModuleLoader.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include "error.hpp" + +// Forward declaration to avoid circular dependency +struct CommandArgs; + +class ModuleLoader { +public: + explicit ModuleLoader(std::string module_path = "/usr/lib/dpm/modules/"); + DPMError check_module_path() const; + std::pair, DPMError> list_available_modules() const; + const std::string& get_module_path() const { return module_path_; } + std::string get_absolute_module_path() const; + + // Split into two separate methods + void* load_module(const std::string& module_name) const; + int execute_module(void* module_handle, const std::string& command) const; + +private: + std::string module_path_; +}; \ No newline at end of file diff --git a/include/dpm_interface.hpp b/include/dpm_interface.hpp new file mode 100644 index 0000000..4f56bad --- /dev/null +++ b/include/dpm_interface.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include +#include "error.hpp" +#include "ModuleLoader.hpp" // This should include ModuleLoader since it's used directly + +/* + * + * 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. + * + */ + +// check if the module path exists +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 = "/usr/lib/dpm/modules/"; + 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[]); \ No newline at end of file diff --git a/include/error.hpp b/include/error.hpp new file mode 100644 index 0000000..4beb439 --- /dev/null +++ b/include/error.hpp @@ -0,0 +1,11 @@ +#pragma once + +enum class DPMError { + SUCCESS, + PATH_NOT_FOUND, + PATH_NOT_DIRECTORY, + PERMISSION_DENIED, + MODULE_NOT_FOUND, + MODULE_LOAD_FAILED, + INVALID_MODULE +}; \ No newline at end of file diff --git a/include/module_interface.hpp b/include/module_interface.hpp new file mode 100644 index 0000000..e39784b --- /dev/null +++ b/include/module_interface.hpp @@ -0,0 +1,12 @@ +#pragma once +#include + +/* + * Provides reserved symbol names we look for in modules. + */ + +// Common interface for all DPM modules +extern "C" { + // Module must export this symbol to be considered valid + int dpm_module_execute(const char* command, int argc, char** argv); +} \ No newline at end of file diff --git a/modules/info.cpp b/modules/info.cpp new file mode 100644 index 0000000..68b6281 --- /dev/null +++ b/modules/info.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +// Implementation of the info module +// This module provides information about the DPM system + +// Main entry point that will be called by DPM +extern "C" int dpm_module_execute(const char* command, int argc, char** argv) { + // Handle the case when no command is provided + if (command == nullptr || strlen(command) == 0) { + std::cout << "DPM Info Module - Provides information about the DPM system\n"; + std::cout << "Usage: dpm info [args]\n"; + std::cout << "Available commands:\n"; + std::cout << " version - Display DPM version information\n"; + std::cout << " system - Display system information\n"; + std::cout << " help - Display this help message\n"; + return 0; + } + + // Convert command to string for easier comparison + std::string cmd(command); + + if (cmd == "version") { + std::cout << "DPM Version: 0.1.0\n"; + std::cout << "Build Date: " << __DATE__ << "\n"; + std::cout << "Build Time: " << __TIME__ << "\n"; + return 0; + } + else if (cmd == "system") { + std::cout << "System Information:\n"; + std::cout << " OS: " << +#ifdef _WIN32 + "Windows" +#elif __APPLE__ + "macOS" +#elif __linux__ + "Linux" +#else + "Unknown" +#endif + << "\n"; + std::cout << " Architecture: " << +#ifdef __x86_64__ + "x86_64" +#elif __i386__ + "x86" +#elif __arm__ + "ARM" +#elif __aarch64__ + "ARM64" +#else + "Unknown" +#endif + << "\n"; + return 0; + } + else if (cmd == "help") { + std::cout << "DPM Info Module - Provides information about the DPM system\n"; + std::cout << "Available commands:\n"; + std::cout << " version - Display DPM version information\n"; + std::cout << " system - Display system information\n"; + std::cout << " help - Display this help message\n"; + return 0; + } + else { + std::cerr << "Unknown command: " << cmd << "\n"; + std::cerr << "Run 'dpm info help' for a list of available commands\n"; + return 1; + } +} diff --git a/src/ModuleLoader.cpp b/src/ModuleLoader.cpp new file mode 100644 index 0000000..83b4be3 --- /dev/null +++ b/src/ModuleLoader.cpp @@ -0,0 +1,106 @@ +#include "ModuleLoader.hpp" +#include "dpm_interface.hpp" +#include +#include +#include + +namespace fs = std::filesystem; + +ModuleLoader::ModuleLoader(std::string module_path) : module_path_(std::move(module_path)) +{ + if (!module_path_.empty() && module_path_.back() != '/') { + module_path_ += '/'; + } +} + +DPMError ModuleLoader::check_module_path() const +{ + if (!fs::exists(module_path_)) { + return DPMError::PATH_NOT_FOUND; + } + + if (!fs::is_directory(module_path_)) { + return DPMError::PATH_NOT_DIRECTORY; + } + + try { + fs::directory_iterator(module_path_); + } catch (const fs::filesystem_error&) { + return DPMError::PERMISSION_DENIED; + } + + return DPMError::SUCCESS; +} + +std::string ModuleLoader::get_absolute_module_path() const +{ + try { + return fs::absolute(module_path_).string(); + } catch (const fs::filesystem_error&) { + return module_path_; // Return relative path if conversion fails + } +} + +std::pair, DPMError> ModuleLoader::list_available_modules() const +{ + std::vector modules; + + try { + fs::path absolute_path = fs::absolute(module_path_); + for (const auto& entry : fs::directory_iterator(absolute_path)) { + if (entry.is_regular_file()) { + std::string filename = entry.path().filename().string(); + // Check if it's a .so file + if (filename.size() > 3 && filename.substr(filename.size() - 3) == ".so") { + // Remove the .so extension + modules.push_back(filename.substr(0, filename.size() - 3)); + } + } + } + } catch (const fs::filesystem_error&) { + return {modules, DPMError::PERMISSION_DENIED}; + } + + return {modules, DPMError::SUCCESS}; +} + +void* ModuleLoader::load_module(const std::string& module_name) const +{ + // Construct path to module shared object + std::string module_so_path = module_path_ + module_name + ".so"; + + // Load the module + void* module_handle = dlopen(module_so_path.c_str(), RTLD_LAZY); + if (!module_handle) { + std::cerr << "Failed to load module: " << dlerror() << std::endl; + return nullptr; + } + + // Clear any existing errors + dlerror(); + + return module_handle; +} + +int ModuleLoader::execute_module(void* module_handle, const std::string& command) const +{ + if (!module_handle) { + std::cerr << "Invalid module handle" << std::endl; + return 1; + } + + // Find the execution entry point + using ExecuteFn = int (*)(const char*, int, char**); + ExecuteFn execute_fn = (ExecuteFn)dlsym(module_handle, "dpm_module_execute"); + + const char* error = dlerror(); + if (error != nullptr) { + std::cerr << "Failed to find module entry point: " << error << std::endl; + return 1; + } + + // Execute the module with just the provided command string + int result = execute_fn(command.c_str(), 0, nullptr); + + return result; +} \ No newline at end of file diff --git a/src/dpm.cpp b/src/dpm.cpp new file mode 100644 index 0000000..173b419 --- /dev/null +++ b/src/dpm.cpp @@ -0,0 +1,50 @@ +#include +#include + +#include "ModuleLoader.hpp" +#include "dpm_interface.hpp" + +/* + * DPM serves three functions: + * 1. Find and load modules. + * 2. Route commands to modules. + * 3. Provide a module-agnostic unified interface for modules. + */ + +// the default behaviour if dpm is executed without being told to do anything +int default_behavior(const ModuleLoader& loader) +{ + return main_list_modules(loader); +} + +// entry point for the DPM utility +int main(int argc, char* argv[]) +{ + auto args = parse_args(argc, argv); + ModuleLoader loader(args.module_path); + + // check if the modules path even exists and return an error if not since we can't do anything + if (auto result = main_check_module_path(loader); result != 0) { + return result; + } + + // if no modules are supplied, execute the default behaviour and exit + if (args.module_name.empty()) { + return default_behavior(loader); + } + + // load the module specified + void* module_handle = loader.load_module(args.module_name); + if (!module_handle) { + std::cerr << "Failed to load module: " << args.module_name << std::endl; + return 1; + } + + // Execute the module with the command string + int result = loader.execute_module(module_handle, args.command); + + // Cleanup + dlclose(module_handle); + + return result; +} \ No newline at end of file diff --git a/src/dpm_interface.cpp b/src/dpm_interface.cpp new file mode 100644 index 0000000..2fd9ae0 --- /dev/null +++ b/src/dpm_interface.cpp @@ -0,0 +1,106 @@ +#include "dpm_interface.hpp" + +/* + * + * 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. + * + */ + +// check if the module path exists +int main_check_module_path(const ModuleLoader& loader) +{ + if (auto result = loader.check_module_path(); result != DPMError::SUCCESS) { + switch (result) { + case DPMError::PATH_NOT_FOUND: + std::cerr << "Module path not found: " << loader.get_absolute_module_path() << std::endl; + break; + case DPMError::PATH_NOT_DIRECTORY: + std::cerr << "Not a directory: " << loader.get_absolute_module_path() << std::endl; + break; + case DPMError::PERMISSION_DENIED: + std::cerr << "Permission denied: " << loader.get_absolute_module_path() << std::endl; + break; + default: + std::cerr << "Failed checking module path: " << loader.get_absolute_module_path() << std::endl; + } + return 1; + } + return 0; +} + +// list the modules +int main_list_modules(const ModuleLoader& loader) +{ + auto [modules, list_error] = loader.list_available_modules(); + if (list_error != DPMError::SUCCESS) { + switch (list_error) { + case DPMError::PERMISSION_DENIED: + std::cerr << "Permission denied reading modules from: " << loader.get_absolute_module_path() << std::endl; + break; + default: + std::cerr << "Failed listing modules from: " << loader.get_absolute_module_path() << std::endl; + } + return 1; + } + + std::cout << "Available modules in " << loader.get_absolute_module_path() << ":\n"; + for (const auto& module : modules) { + std::cout << " " << module << "\n"; + } + return 0; +} + +// parser for populating data structure for supplied arguments +CommandArgs parse_args(int argc, char* argv[]) +{ + CommandArgs args; + + 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" + << "Options:\n" + << " -m, --module-path PATH Path to DPM modules\n" + << " -h, --help Show this help message\n" + << "\nIf no module is specified, available modules will be listed.\n"; + exit(0); + case '?': + exit(1); + } + } + + // If there are remaining args, the first one is the module name + if (optind < argc) { + args.module_name = argv[optind++]; + + // Collect all remaining arguments and combine them into a single command string + for (int i = optind; i < argc; i++) { + if (!args.command.empty()) { + args.command += " "; + } + + // Handle arguments with spaces by quoting them + 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