Compare commits

..

29 Commits

Author SHA1 Message Date
Chris Punches
9b3c86aa93 slowly transitioning to a more efficient way of interacting with sealed packages 2025-04-06 19:42:17 -04:00
Chris Punches
7ebc3ebad3 rough impl of checksum verification 2025-04-01 23:33:22 -04:00
Chris Punches
d41c9a65e3 externalized checksum from build module for verify module consumption to constrain dependency tree to build module 2025-03-31 23:32:19 -04:00
Chris Punches
81645c6d09 struggling a little with API exposure between modules and C++ types, needs a complete overhaul for checksums -- snapshot commit -- not functional 2025-03-31 02:03:18 -04:00
Chris Punches
045294aeb6 restructure of verify module for better code organizations 2025-03-30 22:56:13 -04:00
Chris Punches
25f9afd1c8 snapshot of verify module buildout 2025-03-30 04:00:21 -04:00
Chris Punches
56e471e227 moving some loading functionality to dpmdk 2025-03-28 22:04:27 -04:00
Chris Punches
7b29824e40 basic POC functionality established for peer module loading 2025-03-27 22:28:02 -04:00
Chris Punches
448dc0cdfd working out peer module loading 2025-03-27 21:51:21 -04:00
Chris Punches
49c73d1876 boilerplat to begin working on verify module -- to be developed in paralle to the install module which will load an external symbol if this module is available for simpler bootstrapping 2025-03-27 00:14:47 -04:00
Chris Punches
15360edc42 improved metadata safety when sealing -- always needs to refresh 2025-03-26 01:18:09 -04:00
Chris Punches
1d34a62e38 improved consistency in argument handling, introduction of dpm_con (not everything needs to be in the log) 2025-03-26 01:10:18 -04:00
Chris Punches
b8ee0f9eff fixed some issues with logging paths 2025-03-26 00:11:49 -04:00
Chris Punches
c27d91a573 overhaul of metadata generation and management 2025-03-25 23:22:15 -04:00
Chris Punches
8b7e594d33 adding missing files from previous, and various fixes for PGP signing; path mgmt is a little clunky 2025-03-23 19:51:18 -04:00
Chris Punches
78891a1881 append to last 2025-03-23 14:25:32 -04:00
Chris Punches
9642581509 basic signing capabilities - initial implementation, lots of kinks to work out 2025-03-23 14:16:47 -04:00
Chris Punches
c733bb634c renamed some files, implemented stage and package sealing, and introduced scaffolding for signing 2025-03-23 05:54:24 -04:00
Chris Punches
ac0d91f240 slight module list view improvement 2025-03-20 21:58:59 -04:00
Chris Punches
58089f234f rough refresh implementation and some cleanup with method names 2025-03-20 21:27:41 -04:00
Chris Punches
7ac80d12f3 partially implemented separate manifest management 2025-03-20 01:46:04 -04:00
Chris Punches
2623bcf2b3 starting the process of moving manifest mgmt to a dedicated subcommand 2025-03-16 23:16:40 -04:00
Chris Punches
172bc2e9c8 output cleanup 2025-03-16 02:11:15 -04:00
Chris Punches
6d34c69b9f likely fixed, needs more testing 2025-03-16 02:07:38 -04:00
Chris Punches
28555d5773 struggling a little with proper available algo detection 2025-03-16 02:02:13 -04:00
Chris Punches
5ec3c0ca83 fixed available algo enumeration 2025-03-15 23:03:10 -04:00
Chris Punches
aad077a24a implemented configurable checksum algorithm, defaults to sha256, introduces openssl dependency to build module 2025-03-15 21:27:21 -04:00
Chris Punches
1f4f73ff0f started implementation of updating CONTENTS_MANIFEST_DIGEST 2025-03-15 19:38:49 -04:00
Chris Punches
cf860503c9 major cleanup / refactor of stage creation 2025-03-15 19:23:54 -04:00
54 changed files with 6089 additions and 462 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.idea .idea
cmake-build-debug cmake-build-debug
design

View File

@@ -35,8 +35,11 @@ add_subdirectory(modules/info ${CMAKE_BINARY_DIR}/build-modules/info)
# add the build module by including that # add the build module by including that
add_subdirectory(modules/build ${CMAKE_BINARY_DIR}/build-modules/build) add_subdirectory(modules/build ${CMAKE_BINARY_DIR}/build-modules/build)
# add the verify module
add_subdirectory(modules/verify ${CMAKE_BINARY_DIR}/build-modules/verify)
# Create a custom target for building all modules # Create a custom target for building all modules
add_custom_target(modules DEPENDS info build) add_custom_target(modules DEPENDS info build verify)
# Installation rules # Installation rules
install(TARGETS dpm DESTINATION bin) install(TARGETS dpm DESTINATION bin)

View File

@@ -1,4 +1,4 @@
[logging] [logging]
log_file = /var/log/dpm.log log_file = /home/phanes/dpm.log
write_to_log = true write_to_log = true
log_level = INFO log_level = INFO

2
data/security.conf Normal file
View File

@@ -0,0 +1,2 @@
[cryptography]
checksum_algorithm=sha256

View File

@@ -31,6 +31,9 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <sys/stat.h>
#include <dlfcn.h>
#include "ModuleOperations.hpp"
/** /**
* @brief Fatal log level constant * @brief Fatal log level constant
@@ -131,6 +134,17 @@ extern "C" {
*/ */
void dpm_log(int level, const char* message); void dpm_log(int level, const char* message);
/**
* @brief Console logging function
*
* Allows modules to log messages to the console only, bypassing the file logging.
* This is useful for user-facing output that doesn't need to be recorded in logs.
*
* @param level The log level (LOG_FATAL, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG)
* @param message The message to log
*/
void dpm_con(int level, const char* message);
/** /**
* @brief Sets the logging level * @brief Sets the logging level
* *
@@ -140,6 +154,16 @@ extern "C" {
* @param level The log level (LOG_FATAL, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG) * @param level The log level (LOG_FATAL, LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG)
*/ */
void dpm_set_logging_level(int level); void dpm_set_logging_level(int level);
/**
* @brief Gets the module path
*
* Returns the path where DPM modules are located, as determined by
* command-line arguments, configuration files, or defaults.
*
* @return The module path
*/
const char* dpm_get_module_path(void);
} }
/** /**
@@ -149,57 +173,7 @@ extern "C" {
*/ */
#define DPM_VERSION "0.1.0" #define DPM_VERSION "0.1.0"
// If we're building in standalone mode, add support for standalone execution // If we're building in standalone mode, include the standalone implementations
#ifdef BUILD_STANDALONE #ifdef BUILD_STANDALONE
#include "StandaloneModuleImpl.hpp"
// Declare, but don't define the standalone implementations
// These will be defined in the main file through the DPM_STANDALONE_IMPL macro
/**
* @brief Helper macro to create a standalone main function for modules
*
* This macro defines a main() function that initializes the module and
* processes command-line arguments to execute commands directly.
*/
#define DPM_MODULE_STANDALONE_MAIN() \
extern "C" void dpm_log(int level, const char* message) { \
const char* level_str; \
switch (level) { \
case 0: level_str = "FATAL"; break; \
case 1: level_str = "ERROR"; break; \
case 2: level_str = "WARN"; break; \
case 3: level_str = "INFO"; break; \
case 4: level_str = "DEBUG"; break; \
default: level_str = "UNKNOWN"; break; \
} \
std::cout << "[" << level_str << "] " << message << std::endl; \
} \
extern "C" const char* dpm_get_config(const char* section, const char* key) { \
if (!section || !key) return nullptr; \
\
/* Create environment variable name in format SECTION_KEY */ \
std::string env_name = std::string(section) + "_" + std::string(key); \
\
/* Check if environment variable exists */ \
const char* env_value = getenv(env_name.c_str()); \
return env_value; /* Will be null if env var doesn't exist */ \
} \
extern "C" void dpm_set_logging_level(int level) { \
std::cout << "[INFO] Verbosity level ignored, as all standalone executions have maximum verbosity" << std::endl; \
} \
int main(int argc, char** argv) { \
/* Default to "help" if no command is provided */ \
const char* command = "help"; \
\
/* If arguments are provided, use the first as command */ \
if (argc > 1) { \
command = argv[1]; \
/* Shift arguments for the command handler but keep the original argc count */ \
argv++; \
argc--; \
} \
\
return dpm_module_execute(command, argc, argv); \
}
#endif // BUILD_STANDALONE #endif // BUILD_STANDALONE

View File

@@ -0,0 +1,88 @@
/**
* @file ModuleOperations.hpp
* @brief C++ interface for module operations with direct passthrough
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <filesystem>
#include <dlfcn.h>
#include <sys/stat.h>
#include <dpmdk/include/CommonModuleAPI.hpp>
/**
* @brief Checks if a module exists
*
* Verifies if a module exists at the configured module path.
*
* @param module_name Name of the module to check
* @return true if the module exists, false otherwise
*/
bool dpm_module_exists(const char* module_name);
/**
* @brief Loads a DPM module
*
* Attempts to load a module from the configured module path.
*
* @param module_name Name of the module to load
* @param module_handle Pointer to store the loaded module handle
* @return 0 on success, non-zero on failure
*/
int dpm_load_module(const char* module_name, void** module_handle);
/**
* @brief Checks if a symbol exists in a module
*
* Verifies if a specific symbol exists in a loaded module.
*
* @param module_handle Handle to a loaded module
* @param symbol_name Name of the symbol to check
* @return true if the symbol exists, false otherwise
*/
bool dpm_symbol_exists(void* module_handle, const char* symbol_name);
/**
* @brief Executes a symbol in a module with direct argument passthrough
*
* Template function that directly passes any number of arguments to the target function.
*
* @tparam Args Variable argument types to pass to the function
* @param module_handle Handle to a loaded module
* @param symbol_name Name of the symbol to execute
* @param args Arguments to pass to the function
* @return Return value from the executed function
*/
template<typename... Args>
int dpm_execute_symbol(void* module_handle, const char* symbol_name, Args&&... args) {
if (!module_handle || !symbol_name) return 1;
// Clear any previous error
dlerror();
// Look up the symbol with the correct function signature
typedef int (*FunctionPtr)(Args...);
FunctionPtr func = reinterpret_cast<FunctionPtr>(dlsym(module_handle, symbol_name));
// Check for errors
const char* error = dlerror();
if (error || !func) return 1;
// Call the function with the provided arguments - direct passthrough
return func(std::forward<Args>(args)...);
}
/**
* @brief Unloads a module
*
* Frees resources used by a loaded module.
*
* @param module_handle Handle to a loaded module
*/
void dpm_unload_module(void* module_handle);

View File

@@ -0,0 +1,123 @@
/**
* @file StandaloneModuleImpl.hpp
* @brief Standalone implementations for DPM modules
*
* Provides implementations of core DPM functions for standalone module builds,
* allowing modules to be compiled and run independently of the main DPM system
* for testing and development purposes.
*
* @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 <string>
#include <cstdlib>
#include <sys/stat.h>
#include <dlfcn.h>
// All implementations must be inline to prevent multiple definition errors when included in multiple files
/**
* @brief Standalone implementation of dpm_log
*/
inline void dpm_log(int level, const char* message) {
const char* level_str;
switch (level) {
case 0: level_str = "FATAL"; break;
case 1: level_str = "ERROR"; break;
case 2: level_str = "WARN"; break;
case 3: level_str = "INFO"; break;
case 4: level_str = "DEBUG"; break;
default: level_str = "UNKNOWN"; break;
}
std::cout << "[" << level_str << "] " << message << std::endl;
}
/**
* @brief Standalone implementation of dpm_con
*/
inline void dpm_con(int level, const char* message) {
const char* level_str;
switch (level) {
case 0: level_str = "FATAL"; break;
case 1: level_str = "ERROR"; break;
case 2: level_str = "WARN"; break;
case 3: level_str = "INFO"; break;
case 4: level_str = "DEBUG"; break;
default: level_str = "UNKNOWN"; break;
}
std::cout << "[" << level_str << "] " << message << std::endl;
}
/**
* @brief Standalone implementation of dpm_get_config
*/
inline const char* dpm_get_config(const char* section, const char* key) {
if (!section || !key) return nullptr;
// Create environment variable name in format SECTION_KEY
std::string env_name = std::string(section) + "_" + std::string(key);
// Check if environment variable exists
const char* env_value = getenv(env_name.c_str());
return env_value; // Will be null if env var doesn't exist
}
/**
* @brief Standalone implementation of dpm_set_logging_level
*/
inline void dpm_set_logging_level(int level) {
std::cout << "[INFO] Verbosity level ignored, as all standalone executions have maximum verbosity" << std::endl;
}
/**
* @brief Standalone implementation of dpm_get_module_path
*/
inline const char* dpm_get_module_path(void) {
// Get from environment variable or use default
const char* env_path = dpm_get_config("modules", "modules_path");
return env_path ? env_path : "/usr/lib/dpm/modules/";
}
/**
* @brief Standalone module main function
*
* Provides a main() function for standalone module builds that
* initializes the environment and routes to the module's execute function.
*/
#define DPM_MODULE_STANDALONE_MAIN() \
int main(int argc, char** argv) { \
/* Default to "help" if no command is provided */ \
const char* command = "help"; \
\
/* If arguments are provided, use the first as command */ \
if (argc > 1) { \
command = argv[1]; \
/* Shift arguments for the command handler but keep the original argc count */ \
argv++; \
argc--; \
} \
\
return dpm_module_execute(command, argc, argv); \
}
/* End of file */

View File

@@ -1,11 +1,9 @@
/** /**
* @file CommonModuleAPI.cpp * @file CommonModuleAPI.cpp
* @brief Implementation stub for DPM module interface * @brief Implementation for DPMDK functions
* *
* This file serves primarily as a documentation reference for the module * Implements the module utility functions provided by the DPMDK library
* interface. The actual implementations of dpm_get_config and dpm_log are * for module interoperability and loading.
* provided by the DPM core, while dpm_module_execute, dpm_module_get_version,
* and dpm_get_description must be implemented by each module.
* *
* @copyright Copyright (c) 2025 SILO GROUP LLC * @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org> * @author Chris Punches <chris.punches@silogroup.org>
@@ -28,5 +26,10 @@
* For bug reports or contributions, please contact the dhlp-contributors * For bug reports or contributions, please contact the dhlp-contributors
* mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors * mailing list at: https://lists.darkhorselinux.org/mailman/listinfo/dhlp-contributors
*/ */
#include "CommonModuleAPI.hpp" #include "CommonModuleAPI.hpp"
// Only define these functions when not in standalone mode
#ifndef BUILD_STANDALONE
#endif // BUILD_STANDALONE

View File

@@ -0,0 +1,69 @@
/**
* @file ModuleOperations.cpp
* @brief Implementation of C++ interface for module operations
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "ModuleOperations.hpp"
bool dpm_module_exists(const char* module_name) {
if (!module_name) return false;
// Get the module path
const char* module_path = dpm_get_module_path();
if (!module_path) return false;
// Build path to the module
std::string module_file = std::string(module_path) + "/" + module_name + ".so";
// Check if file exists
struct stat buffer;
return (stat(module_file.c_str(), &buffer) == 0);
}
int dpm_load_module(const char* module_name, void** module_handle) {
if (!module_name || !module_handle) return 1;
// Get the module path
const char* module_path = dpm_get_module_path();
if (!module_path) return 1;
// Build path to the module
std::string module_file = std::string(module_path) + "/" + module_name + ".so";
// Check if the file exists
if (!dpm_module_exists(module_name)) return 1;
// Load the module
*module_handle = dlopen(module_file.c_str(), RTLD_LAZY);
if (!*module_handle) return 1;
return 0;
}
bool dpm_symbol_exists(void* module_handle, const char* symbol_name) {
if (!module_handle || !symbol_name) return false;
// Clear any error
dlerror();
// Look up the symbol
void* symbol = dlsym(module_handle, symbol_name);
// Check for errors
const char* error = dlerror();
if (error) return false;
return (symbol != nullptr);
}
void dpm_unload_module(void* module_handle) {
if (module_handle) {
dlclose(module_handle);
}
}

View File

@@ -156,6 +156,12 @@ class ConfigManager {
*/ */
bool hasConfigKey(const char* section, const char* key) const; bool hasConfigKey(const char* section, const char* key) const;
// getter for _module_path
void setModulePath(const char * module_path);
// setter for _module_path
const char * getModulePath() const;
private: private:
/** /**
* @brief Default section name to use when none is specified * @brief Default section name to use when none is specified
@@ -199,6 +205,8 @@ class ConfigManager {
* @brief Configuration data structure: section -> key -> value * @brief Configuration data structure: section -> key -> value
*/ */
std::map<std::string, std::map<std::string, std::string>> _config_data; std::map<std::string, std::map<std::string, std::string>> _config_data;
std::string _module_path;
}; };
/** /**

View File

@@ -77,6 +77,18 @@ public:
*/ */
void log(LoggingLevels log_level, const std::string& message); void log(LoggingLevels log_level, const std::string& message);
/**
* @brief Logs a message to console only
*
* Writes a log message only to the console, skipping any file logging.
* Messages with levels FATAL, ERROR, or WARN are written to stderr,
* while others go to stdout.
*
* @param level The severity level of the message
* @param message The message to log
*/
void log_console(LoggingLevels level, const std::string& message);
/** /**
* @brief Sets the log file path * @brief Sets the log file path
* *

View File

@@ -41,6 +41,8 @@
#include "dpm_interface_helpers.hpp" #include "dpm_interface_helpers.hpp"
#include "Logger.hpp" #include "Logger.hpp"
#include <handlers.hpp>
/** /**
* @defgroup dpm_interface DPM Interface Methods * @defgroup dpm_interface DPM Interface Methods
* @brief Interface methods for the DPM command-line tool * @brief Interface methods for the DPM command-line tool

View File

@@ -32,6 +32,8 @@
#include <iostream> #include <iostream>
#include "error.hpp" #include "error.hpp"
#include "LoggingLevels.hpp"
#include "module_interface.hpp"
/** /**
* @brief Main error handler that dispatches to specific handlers * @brief Main error handler that dispatches to specific handlers

View File

@@ -121,6 +121,17 @@ extern "C" {
*/ */
void dpm_log(int level, const char* message); void dpm_log(int level, const char* message);
/**
* @brief Logs messages to console only
*
* Allows modules to log messages to the console only, skipping any file logging.
* This is useful for user-facing output that doesn't need to be recorded.
*
* @param level The log level as an integer (0=FATAL, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG)
* @param message The message to log
*/
void dpm_con(int level, const char* message);
/** /**
* @brief Sets the logging level * @brief Sets the logging level
* *
@@ -130,5 +141,16 @@ extern "C" {
* @param level The log level as an integer (0=FATAL, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG) * @param level The log level as an integer (0=FATAL, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG)
*/ */
void dpm_set_logging_level(int level); void dpm_set_logging_level(int level);
/**
* @brief Gets the derived module path from the global configuration
*
* Allows modules to retrieve the configured module path used by DPM.
* This path is determined at runtime based on CLI arguments, configuration files,
* and defaults, in order of precedence.
*
* @return The module path as a string
*/
const char* dpm_get_module_path(void);
} }
/** @} */ /** @} */

View File

@@ -5,43 +5,70 @@ set(CMAKE_CXX_STANDARD 20)
# Set DPM_ROOT_DIR based on whether this is a standalone build or part of the main build # Set DPM_ROOT_DIR based on whether this is a standalone build or part of the main build
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(DPM_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..") set(DPM_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..")
else() else()
set(DPM_ROOT_DIR "${CMAKE_SOURCE_DIR}") set(DPM_ROOT_DIR "${CMAKE_SOURCE_DIR}")
endif()
# Find OpenSSL
find_package(OpenSSL REQUIRED)
# Find LibArchive
find_package(LibArchive REQUIRED)
# Find GPGME
find_path(GPGME_INCLUDE_DIR gpgme.h)
find_library(GPGME_LIBRARY NAMES gpgme)
if(NOT GPGME_INCLUDE_DIR OR NOT GPGME_LIBRARY)
message(FATAL_ERROR "GPGME library not found. Please install libgpgme-dev or equivalent package.")
endif() endif()
# Module version - used by DPM # Module version - used by DPM
add_library(build MODULE add_library(build MODULE
build.cpp build.cpp
src/helpers.cpp src/helpers.cpp
src/cli_parsers.cpp src/cli_parsers.cpp
src/commands.cpp src/commands.cpp
src/package_staging.cpp src/staging.cpp
src/signing.cpp
src/checksums.cpp
src/metadata.cpp
src/sealing.cpp
src/archive_reader.cpp
) )
# Set output properties # Set output properties
set_target_properties( set_target_properties(
build PROPERTIES build PROPERTIES
PREFIX "" PREFIX ""
SUFFIX ".so" SUFFIX ".so"
) )
# Include directories # Include directories
target_include_directories(build PRIVATE target_include_directories(build PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${DPM_ROOT_DIR} ${DPM_ROOT_DIR}
${OPENSSL_INCLUDE_DIR}
${LibArchive_INCLUDE_DIRS}
${GPGME_INCLUDE_DIR}
) )
# Link with filesystem library # Link with libraries
target_link_libraries(build stdc++fs) target_link_libraries(build stdc++fs ${OPENSSL_LIBRARIES} ${LibArchive_LIBRARIES} ${GPGME_LIBRARY})
# Standalone version - used for debugging # Standalone version - used for debugging
add_executable(build_standalone add_executable(build_standalone
build.cpp build.cpp
src/helpers.cpp src/helpers.cpp
src/cli_parsers.cpp src/cli_parsers.cpp
src/commands.cpp src/commands.cpp
src/package_staging.cpp src/staging.cpp
src/signing.cpp
src/checksums.cpp
src/metadata.cpp
src/sealing.cpp
src/archive_reader.cpp
) )
# Define the BUILD_STANDALONE macro for the standalone build # Define the BUILD_STANDALONE macro for the standalone build
@@ -49,15 +76,18 @@ target_compile_definitions(build_standalone PRIVATE BUILD_STANDALONE)
# Include directories for standalone # Include directories for standalone
target_include_directories(build_standalone PRIVATE target_include_directories(build_standalone PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${DPM_ROOT_DIR} ${DPM_ROOT_DIR}
${OPENSSL_INCLUDE_DIR}
${LibArchive_INCLUDE_DIRS}
${GPGME_INCLUDE_DIR}
) )
# Link with filesystem library for standalone # Link with libraries for standalone
target_link_libraries(build_standalone stdc++fs) target_link_libraries(build_standalone stdc++fs ${OPENSSL_LIBRARIES} ${LibArchive_LIBRARIES} ${GPGME_LIBRARY})
# Set the output name for the standalone executable # Set the output name for the standalone executable
set_target_properties( set_target_properties(
build_standalone PROPERTIES build_standalone PROPERTIES
OUTPUT_NAME "build_debug" OUTPUT_NAME "build_debug"
) )

View File

@@ -79,6 +79,7 @@ extern "C" const char* dpm_get_description(void) {
* @param argv Array of argument strings * @param argv Array of argument strings
* @return 0 on success, non-zero on failure * @return 0 on success, non-zero on failure
*/ */
// In build.cpp, update the dpm_module_execute function
extern "C" int dpm_module_execute(const char* command, int argc, char** argv) { extern "C" int dpm_module_execute(const char* command, int argc, char** argv) {
// Parse the command // Parse the command
Command cmd = parse_command(command); Command cmd = parse_command(command);
@@ -91,6 +92,18 @@ extern "C" int dpm_module_execute(const char* command, int argc, char** argv) {
case CMD_HELP: case CMD_HELP:
return cmd_help(argc, argv); return cmd_help(argc, argv);
case CMD_METADATA:
return cmd_metadata(argc, argv);
case CMD_SIGN:
return cmd_sign(argc, argv);
case CMD_SEAL:
return cmd_seal(argc, argv);
case CMD_UNSEAL:
return cmd_unseal(argc, argv);
case CMD_UNKNOWN: case CMD_UNKNOWN:
default: default:
return cmd_unknown(command, argc, argv); return cmd_unknown(command, argc, argv);
@@ -101,3 +114,4 @@ extern "C" int dpm_module_execute(const char* command, int argc, char** argv) {
#ifdef BUILD_STANDALONE #ifdef BUILD_STANDALONE
DPM_MODULE_STANDALONE_MAIN() DPM_MODULE_STANDALONE_MAIN()
#endif // BUILD_STANDALONE #endif // BUILD_STANDALONE

View File

@@ -0,0 +1,43 @@
/**
* @file archive_reader.hpp
* @brief Functions for in-memory archive reading and verification
*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <archive.h>
#include <archive_entry.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
#include "checksums.hpp"
extern "C" {
/**
* Extracts a specific file from a package file (gzipped tarball)
*
* @param package_file_path Path to the package file (.dpm)
* @param file_path_in_archive Path of the file to extract within the archive
* @param data Pointer to buffer pointer - will be allocated by function
* @param data_size Pointer to size variable that will receive file size
* @return true on success, false on failure
*/
bool get_file_from_package_file(const char* package_file_path, const char* file_path_in_archive, unsigned char** data, size_t* data_size);
/**
* Extracts a specific file from an in-memory archive (gzipped tarball)
*
* @param archive_data Pointer to the archive data in memory
* @param archive_data_size Size of the archive data in memory
* @param file_path_in_archive Path of the file to extract within the archive
* @param result_data Pointer to buffer pointer - will be allocated by function
* @param result_data_size Pointer to size variable that will receive file size
* @return true on success, false on failure
*/
bool get_file_from_memory_loaded_archive(const unsigned char* archive_data, const size_t archive_data_size,
const char* file_path_in_archive,
unsigned char** result_data, size_t* result_data_size);
}

View File

@@ -0,0 +1,68 @@
/**
* @file checksums.hpp
* @brief Functions for generating cryptographic checksums
*
* Provides functionality for generating checksums of files using
* configurable cryptographic hash algorithms via OpenSSL.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <filesystem>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include <vector>
#include <cstring>
#include <algorithm>
/**
* @brief Gets the configured hash algorithm or defaults to SHA-256
*
* Retrieves the hash algorithm configured in the cryptography section
* or defaults to SHA-256 if not specified.
*
* @return String containing the name of the hash algorithm to use
*/
extern "C" std::string get_configured_hash_algorithm();
/**
* @brief Gets a list of available digest algorithms from OpenSSL
*
* Retrieves a list of supported digest algorithms from OpenSSL's
* internal algorithms list.
*
* @return String containing comma-separated list of available algorithms
*/
extern "C" std::string get_available_algorithms();
/**
* @brief Generates a file checksum using the configured hashing algorithm
*
* Uses OpenSSL to calculate a cryptographic hash of a file's contents
* based on the algorithm specified in the configuration.
* This method reads the file in chunks to handle large files efficiently.
*
* @param file_path Path to the file to be hashed
* @return String containing the hexadecimal representation of the checksum, or empty string on error
*/
extern "C" std::string generate_file_checksum(const std::filesystem::path& file_path);
/**
* @brief Generates a checksum of a string using the configured hashing algorithm
*
* Uses OpenSSL to calculate a cryptographic hash of a string's contents
* based on the algorithm specified in the configuration.
*
* @param input_string The string to be hashed
* @return String containing the hexadecimal representation of the checksum, or empty string on error
*/
extern "C" std::string generate_string_checksum(const std::string& input_string);

View File

@@ -12,9 +12,13 @@
* @brief Enumeration of supported commands for the build module * @brief Enumeration of supported commands for the build module
*/ */
enum Command { enum Command {
CMD_UNKNOWN, /**< Unknown or unsupported command */ CMD_UNKNOWN, /**< Unknown or unsupported command */
CMD_HELP, /**< Display help information */ CMD_HELP, /**< Display help information */
CMD_STAGE /**< Stage a new DPM package */ CMD_STAGE, /**< Stage a new DPM package */
CMD_METADATA, /**< Regenerate stage metadata */
CMD_SIGN, /**< Sign a package or stage directory */
CMD_SEAL, /**< Seal a package stage directory */
CMD_UNSEAL, /**< Unseal a package stage directory */
}; };
/** /**

View File

@@ -3,7 +3,11 @@
#include "cli_parsers.hpp" #include "cli_parsers.hpp"
#include <dpmdk/include/CommonModuleAPI.hpp> #include <dpmdk/include/CommonModuleAPI.hpp>
#include <filesystem> #include <filesystem>
#include "package_staging.hpp" #include "staging.hpp"
#include "signing.hpp"
#include "sealing.hpp" // Added this include
#include <map>
#include <sstream>
/** /**
* @brief Handler for the stage command * @brief Handler for the stage command
@@ -16,6 +20,28 @@
*/ */
int cmd_stage(int argc, char** argv); int cmd_stage(int argc, char** argv);
/**
* @brief Handler for the manifest command
*
* Generates or refreshes package manifest.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_metadata(int argc, char** argv);
/**
* @brief Handler for the sign command
*
* Signs a DPM package or package stage directory using GPG.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_sign(int argc, char** argv);
/** /**
* @brief Handler for the help command * @brief Handler for the help command
* *
@@ -27,7 +53,6 @@ int cmd_stage(int argc, char** argv);
*/ */
int cmd_help(int argc, char** argv); int cmd_help(int argc, char** argv);
/** /**
* @brief Handler for the help command * @brief Handler for the help command
* *
@@ -39,6 +64,27 @@ int cmd_help(int argc, char** argv);
*/ */
int cmd_stage_help(int argc, char** argv); int cmd_stage_help(int argc, char** argv);
/**
* @brief Handler for the sign help command
*
* Displays information about sign command options.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_sign_help(int argc, char** argv);
/**
* @brief Handler for the manifest help command
*
* Displays information about manifest command options.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_metadata_help(int argc, char** argv);
/** /**
* @brief Handler for unknown commands * @brief Handler for unknown commands
@@ -51,3 +97,50 @@ int cmd_stage_help(int argc, char** argv);
* @return 1 to indicate failure * @return 1 to indicate failure
*/ */
int cmd_unknown(const char* command, int argc, char** argv); int cmd_unknown(const char* command, int argc, char** argv);
/**
* @brief Handler for the seal command
*
* Seals a DPM package stage directory by replacing contents, metadata,
* hooks, and signatures directories with gzipped tarballs.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_seal(int argc, char** argv);
/**
* @brief Handler for the seal help command
*
* Displays information about seal command options.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_seal_help(int argc, char** argv);
/**
* @brief Handler for the unseal command
*
* Unseals a DPM package file by extracting and expanding the gzipped
* tarballs back into a package stage directory structure.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_unseal(int argc, char** argv);
/**
* @brief Handler for the unseal help command
*
* Displays information about unseal command options.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_unseal_help(int argc, char** argv);

View File

@@ -0,0 +1,112 @@
/**
* @file metadata.hpp
* @brief Functions for handling DPM package stage metadata
*
* Defines functions for creating and manipulating metadata for DPM package stages.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <filesystem>
#include <string>
#include <fstream>
#include <vector>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <map>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "checksums.hpp"
// generates the initial entries for the stage - does not populate data!
bool metadata_generate_skeleton(const std::filesystem::path& stage_dir);
// sets values in metadata files
bool metadata_set_simple_value(const std::filesystem::path& stage_dir, const std::string& key, const std::string& value);
// sets initial known values in metadata
bool metadata_set_initial_known_values(
const std::filesystem::path& stage_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture
);
/**
* @brief Updates the contents manifest file for a package stage
*
* Creates the CONTENTS_MANIFEST_DIGEST file by scanning the contents directory
* and generating a line for each file with control designation,
* checksum, permissions, ownership, and path information.
*
* @param package_dir Root directory of the package stage
* @return true if contents manifest generation was successful, false otherwise
*/
bool metadata_generate_contents_manifest_digest(const std::filesystem::path& package_dir);
/**
* @brief Refreshes the contents manifest file by updating checksums
*
* Iterates through the existing CONTENTS_MANIFEST_DIGEST file, rereads each file,
* recalculates its checksum, and updates the file with new checksums while
* preserving all other fields.
*
* @param stage_dir Directory path of the package stage
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
int metadata_refresh_contents_manifest_digest(const std::string& stage_dir, bool force);
/**
* @brief Generates the HOOKS_DIGEST file for a package stage
*
* Creates the HOOKS_DIGEST file by scanning the hooks directory
* and generating a line for each hook file with its checksum and filename.
*
* @param stage_dir Root directory of the package stage
* @return true if hooks digest generation was successful, false otherwise
*/
bool metadata_generate_hooks_digest(const std::filesystem::path& stage_dir);
// generates the dynamic entries for the stage
bool metadata_generate_dynamic_files( const std::filesystem::path& stage_dir );
// refreshes the dynamic entries for the stage
bool metadata_refresh_dynamic_files( const std::filesystem::path& stage_dir );
/**
* @brief Generates basic metadata files for a package stage
*
* Creates the necessary metadata files for a package stage with the provided information.
* Does not generate the CONTENTS_MANIFEST_DIGEST which requires a separate call
* to update_contents_manifest().
*
* @param package_dir Root directory of the package stage
* @param package_name Name of the package
* @param package_version Version of the package
* @param architecture Architecture of the package (e.g., x86_64, aarch64)
* @return true if metadata generation was successful, false otherwise
*/
bool metadata_generate_new(
const std::filesystem::path& package_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture
);
/**
* @brief Generates the PACKAGE_DIGEST for a package stage
*
* Creates the PACKAGE_DIGEST by generating checksums of CONTENTS_MANIFEST_DIGEST
* and HOOKS_DIGEST, concatenating them, and then calculating a checksum of the
* concatenation. The result is stored in the PACKAGE_DIGEST file.
*
* @param stage_dir Root directory of the package stage
* @return true if package digest generation was successful, false otherwise
*/
bool metadata_generate_package_digest(const std::filesystem::path& stage_dir);

View File

@@ -0,0 +1,77 @@
/**
* @file sealing.hpp
* @brief Functions for sealing and unsealing DPM packages
*
* Defines functions for compressing and packaging DPM package stage directories
* into the final distributable format, as well as extracting them back to the
* stage format.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <filesystem>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <zlib.h>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "helpers.hpp"
#include <archive.h>
#include <archive_entry.h>
#include <fcntl.h>
#include <unistd.h>
#include <metadata.hpp>
/**
* @brief First phase of sealing a package stage directory
*
* Replaces contents, metadata, hooks, and signatures directories with
* gzipped tarballs, creating the intermediate package format.
*
* @param stage_dir Path to the package stage directory
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
extern "C" int seal_stage_components( const std::string& stage_dir, bool force );
/**
* @brief Second phase of sealing to finalize a package
*
* Ensures all components are already sealed (compressed), then
* creates a final package by compressing the entire stage directory.
*
* @param stage_dir Path to the package stage directory
* @param output_dir Path to directory where final package should be placed (optional)
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
extern "C" int seal_final_package(const std::string &stage_dir, const std::string &output_dir, bool force);
/**
* @brief Unseals a package file back to stage format
*
* Extracts a sealed package file back to its original stage directory structure
* by expanding the gzipped tarballs.
*
* @param package_path Path to the sealed package file
* @param output_dir Path to extract the package stage to
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
extern "C" int unseal_package(const std::string& package_path, const std::string& output_dir, bool force);
/**
* @brief Unseals component files in a stage directory
*
* Finds compressed component files in a stage directory and uncompresses them
* in place to their proper directory form.
*
* @param stage_dir Path to the stage directory containing components
* @return 0 on success, non-zero on failure
*/
extern "C" int unseal_stage_components(const std::filesystem::path& stage_dir);

View File

@@ -0,0 +1,46 @@
/**
* @file signing.hpp
* @brief Functions for signing DPM packages
*
* Defines functions for signing DPM package stages and package files.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <gpgme.h>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "helpers.hpp"
#include "sealing.hpp"
/**
* @brief Signs a package stage directory
*
* Creates detached GPG signatures for the contents, hooks, and metadata
* components of a package stage directory.
*
* @param stage_dir Path to the package stage directory
* @param key_id GPG key ID or email to use for signing
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
int sign_stage_directory(const std::string& stage_dir, const std::string& key_id, bool force);
/**
* @brief Signs a package file
*
* Extracts a package file, signs its components, and creates a new signed package.
*
* @param package_path Path to the package file
* @param key_id GPG key ID or email to use for signing
* @param force Whether to force the operation even if warnings occur
* @return 0 on success, non-zero on failure
*/
int sign_package_file(const std::string& package_path, const std::string& key_id, bool force);

View File

@@ -1,5 +1,5 @@
/** /**
* @file package_staging.hpp * @file staging.hpp
* @brief Functions for staging DPM packages * @brief Functions for staging DPM packages
* *
* Defines functions for creating and manipulating DPM package staging structures. * Defines functions for creating and manipulating DPM package staging structures.
@@ -17,6 +17,10 @@
#include <fstream> #include <fstream>
#include <sys/stat.h> #include <sys/stat.h>
#include <dpmdk/include/CommonModuleAPI.hpp> #include <dpmdk/include/CommonModuleAPI.hpp>
#include <pwd.h>
#include <grp.h>
#include "checksums.hpp"
#include "metadata.hpp"
/** /**
* @brief Stages a DPM package * @brief Stages a DPM package

View File

@@ -0,0 +1,205 @@
/**
* @file archive_reader.cpp
* @brief Implementation of in-memory archive reading and verification
*/
#include "archive_reader.hpp"
#include "checksums.hpp"
#include <vector>
#include <map>
#include <archive.h>
#include <archive_entry.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
/**
* Extracts a specific file from a package file (gzipped tarball)
*
* @param package_file_path Path to the package file (.dpm)
* @param file_path_in_archive Path of the file to extract within the archive
* @param data Pointer to buffer pointer - will be allocated by function
* @param data_size Pointer to size variable that will receive file size
* @return true on success, false on failure
*/
extern "C" bool get_file_from_package_file(const char* package_file_path, const char* file_path_in_archive, unsigned char** data, size_t* data_size)
{
if (!package_file_path || !file_path_in_archive || !data || !data_size) {
dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_package_file");
return false;
}
// Initialize output parameters
*data = NULL;
*data_size = 0;
// Create a new archive for reading
struct archive* a = archive_read_new();
if (!a) {
dpm_log(LOG_ERROR, "Failed to create archive object");
return false;
}
// Enable support for gzipped tarballs
archive_read_support_filter_gzip(a);
archive_read_support_format_tar(a);
// Open the package file - using 0 for block size lets libarchive choose the optimal size
int r = archive_read_open_filename(a, package_file_path, 0);
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Failed to open package file: " + std::string(package_file_path) +
" - " + std::string(archive_error_string(a))).c_str());
archive_read_free(a);
return false;
}
// Iterate through archive entries
bool found = false;
struct archive_entry* entry;
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
const char* current_path = archive_entry_pathname(entry);
// Check if this is the file we're looking for
if (strcmp(current_path, file_path_in_archive) == 0) {
// Get the file size
size_t file_size = archive_entry_size(entry);
*data_size = file_size;
// Allocate buffer of appropriate size
*data = (unsigned char*)malloc(file_size);
if (!*data) {
dpm_log(LOG_ERROR, "Failed to allocate memory for file contents");
archive_read_free(a);
return false;
}
// Read the file content into the buffer
ssize_t bytes_read = archive_read_data(a, *data, file_size);
if (bytes_read < 0 || (size_t)bytes_read != file_size) {
dpm_log(LOG_ERROR, ("Failed to read file data: " +
std::string(archive_error_string(a))).c_str());
free(*data);
*data = NULL;
*data_size = 0;
archive_read_free(a);
return false;
}
found = true;
break;
}
// Skip to next entry
archive_read_data_skip(a);
}
// Clean up
archive_read_free(a);
if (!found) {
dpm_log(LOG_ERROR, ("File not found in package: " +
std::string(file_path_in_archive)).c_str());
return false;
}
return true;
}
/**
* Extracts a specific file from an in-memory archive (gzipped tarball)
*
* @param archive_data Pointer to the archive data in memory
* @param archive_data_size Size of the archive data in memory
* @param file_path_in_archive Path of the file to extract within the archive
* @param result_data Pointer to buffer pointer - will be allocated by function
* @param result_data_size Pointer to size variable that will receive file size
* @return true on success, false on failure
*/
extern "C" bool get_file_from_memory_loaded_archive(const unsigned char* archive_data, const size_t archive_data_size,
const char* file_path_in_archive,
unsigned char** result_data, size_t* result_data_size)
{
if (!archive_data || archive_data_size == 0 || !file_path_in_archive ||
!result_data || !result_data_size) {
dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_memory_loaded_archive");
return false;
}
// Initialize output parameters
*result_data = NULL;
*result_data_size = 0;
// Create a new archive for reading
struct archive* a = archive_read_new();
if (!a) {
dpm_log(LOG_ERROR, "Failed to create archive object");
return false;
}
// Enable support for gzipped tarballs
archive_read_support_filter_gzip(a);
archive_read_support_format_tar(a);
// Open the archive from memory
int r = archive_read_open_memory(a, (void*)archive_data, archive_data_size);
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Failed to open archive from memory: " +
std::string(archive_error_string(a))).c_str());
archive_read_free(a);
return false;
}
// Iterate through archive entries
bool found = false;
struct archive_entry* entry;
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
const char* current_path = archive_entry_pathname(entry);
// Check if this is the file we're looking for
if (strcmp(current_path, file_path_in_archive) == 0) {
// Get the file size
size_t file_size = archive_entry_size(entry);
*result_data_size = file_size;
// Allocate buffer of appropriate size
*result_data = (unsigned char*)malloc(file_size);
if (!*result_data) {
dpm_log(LOG_ERROR, "Failed to allocate memory for file contents");
archive_read_free(a);
return false;
}
// Read the file content into the buffer
ssize_t bytes_read = archive_read_data(a, *result_data, file_size);
if (bytes_read < 0 || (size_t)bytes_read != file_size) {
dpm_log(LOG_ERROR, ("Failed to read file data from memory archive: " +
std::string(archive_error_string(a))).c_str());
free(*result_data);
*result_data = NULL;
*result_data_size = 0;
archive_read_free(a);
return false;
}
found = true;
break;
}
// Skip to next entry
archive_read_data_skip(a);
}
// Clean up
archive_read_free(a);
if (!found) {
dpm_log(LOG_ERROR, ("File not found in memory archive: " +
std::string(file_path_in_archive)).c_str());
return false;
}
return true;
}

View File

@@ -0,0 +1,229 @@
/**
* @file checksums.cpp
* @brief Implementation of cryptographic checksum functions
*
* Implements functionality for generating checksums of files using
* configurable cryptographic hash algorithms via OpenSSL.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "checksums.hpp"
extern "C" std::string get_configured_hash_algorithm()
{
const char* algorithm = dpm_get_config("cryptography", "checksum_algorithm");
if (algorithm && strlen(algorithm) > 0) {
return std::string(algorithm);
}
// Default to SHA-256 if not specified or empty
return "sha256";
}
extern "C" std::string get_available_algorithms()
{
std::vector<std::string> algorithms;
std::vector<std::string> working_algorithms;
// Initialize OpenSSL
OpenSSL_add_all_digests();
// Use OBJ_NAME_do_all to get all message digest algorithms
struct AllDigestsCallback {
static void callback(const OBJ_NAME* obj, void* arg) {
if (obj->type == OBJ_NAME_TYPE_MD_METH) {
std::vector<std::string>* algs = static_cast<std::vector<std::string>*>(arg);
algs->push_back(obj->name);
}
}
};
// Get all algorithm names
OBJ_NAME_do_all(OBJ_NAME_TYPE_MD_METH, AllDigestsCallback::callback, &algorithms);
// Test each algorithm with a complete hashing process
for (const auto& algo_name : algorithms) {
const EVP_MD* md = EVP_get_digestbyname(algo_name.c_str());
if (!md) continue;
// Create context
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx) continue;
// Test full hashing sequence with dummy data
unsigned char dummy_data[] = "test";
unsigned char out_buf[EVP_MAX_MD_SIZE];
unsigned int out_len = 0;
bool success = false;
if (EVP_DigestInit_ex(ctx, md, NULL) == 1 &&
EVP_DigestUpdate(ctx, dummy_data, sizeof(dummy_data)) == 1 &&
EVP_DigestFinal_ex(ctx, out_buf, &out_len) == 1) {
success = true;
}
EVP_MD_CTX_free(ctx);
if (success) {
working_algorithms.push_back(algo_name);
}
}
// Format the list as a comma-separated string
std::stringstream result;
for (size_t i = 0; i < working_algorithms.size(); i++) {
if (i > 0) result << ", ";
result << working_algorithms[i];
}
return result.str();
}
extern "C" std::string generate_file_checksum(const std::filesystem::path& file_path)
{
// Get configured algorithm
std::string algorithm_name = get_configured_hash_algorithm();
// Initialize OpenSSL
OpenSSL_add_all_digests();
// Get the digest
const EVP_MD* md = EVP_get_digestbyname(algorithm_name.c_str());
if (!md) {
std::string available_algorithms = get_available_algorithms();
dpm_log(LOG_FATAL, ("Hash algorithm not supported: " + algorithm_name +
". Available algorithms: " + available_algorithms).c_str());
return "";
}
// Initialize OpenSSL EVP context
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
if (!mdctx) {
dpm_log(LOG_ERROR, "Failed to create OpenSSL EVP context");
return "";
}
if (EVP_DigestInit_ex(mdctx, md, nullptr) != 1) {
dpm_log(LOG_ERROR, "Failed to initialize digest context");
EVP_MD_CTX_free(mdctx);
return "";
}
// Check if the file exists
if (!std::filesystem::exists(file_path)) {
dpm_log(LOG_ERROR, ("File does not exist: " + file_path.string()).c_str());
EVP_MD_CTX_free(mdctx);
return "";
}
// Open the file for reading in binary mode
std::ifstream file(file_path, std::ios::binary);
if (!file) {
dpm_log(LOG_ERROR, ("Failed to open file for checksum: " + file_path.string()).c_str());
EVP_MD_CTX_free(mdctx);
return "";
}
// Buffer for reading file chunks
const size_t buffer_size = 8192; // 8KB chunks
unsigned char buffer[buffer_size];
// Read and update digest
while (file) {
file.read(reinterpret_cast<char*>(buffer), buffer_size);
size_t bytes_read = file.gcount();
if (bytes_read > 0) {
if (EVP_DigestUpdate(mdctx, buffer, bytes_read) != 1) {
dpm_log(LOG_ERROR, "Failed to update digest");
EVP_MD_CTX_free(mdctx);
file.close();
return "";
}
}
}
file.close();
// Finalize the digest
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len = 0;
if (EVP_DigestFinal_ex(mdctx, hash, &hash_len) != 1) {
dpm_log(LOG_ERROR, "Failed to finalize digest");
EVP_MD_CTX_free(mdctx);
return "";
}
EVP_MD_CTX_free(mdctx);
// Convert binary hash to hexadecimal string
std::stringstream ss;
for (unsigned int i = 0; i < hash_len; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
}
return ss.str();
}
extern "C" std::string generate_string_checksum(const std::string& input_string)
{
// Get configured algorithm
std::string algorithm_name = get_configured_hash_algorithm();
// Initialize OpenSSL
OpenSSL_add_all_digests();
// Get the digest
const EVP_MD* md = EVP_get_digestbyname(algorithm_name.c_str());
if (!md) {
std::string available_algorithms = get_available_algorithms();
dpm_log(LOG_FATAL, ("Hash algorithm not supported: " + algorithm_name +
". Available algorithms: " + available_algorithms).c_str());
return "";
}
// Initialize OpenSSL EVP context
EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
if (!mdctx) {
dpm_log(LOG_ERROR, "Failed to create OpenSSL EVP context");
return "";
}
if (EVP_DigestInit_ex(mdctx, md, nullptr) != 1) {
dpm_log(LOG_ERROR, "Failed to initialize digest context");
EVP_MD_CTX_free(mdctx);
return "";
}
// Update digest with the string contents
if (EVP_DigestUpdate(mdctx, input_string.c_str(), input_string.length()) != 1) {
dpm_log(LOG_ERROR, "Failed to update digest");
EVP_MD_CTX_free(mdctx);
return "";
}
// Finalize the digest
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hash_len = 0;
if (EVP_DigestFinal_ex(mdctx, hash, &hash_len) != 1) {
dpm_log(LOG_ERROR, "Failed to finalize digest");
EVP_MD_CTX_free(mdctx);
return "";
}
EVP_MD_CTX_free(mdctx);
// Convert binary hash to hexadecimal string
std::stringstream ss;
for (unsigned int i = 0; i < hash_len; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
}
return ss.str();
}

View File

@@ -237,6 +237,26 @@ Command parse_command(const char* cmd_str) {
return CMD_STAGE; return CMD_STAGE;
} }
// Check for command, including when it has additional arguments
if (strncmp(cmd_str, "metadata", 8) == 0) {
return CMD_METADATA;
}
// Check for sign command, including when it has additional arguments
if (strncmp(cmd_str, "sign", 4) == 0) {
return CMD_SIGN;
}
// Check for seal command, including when it has additional arguments
if (strncmp(cmd_str, "seal", 4) == 0) {
return CMD_SEAL;
}
// Check for unseal command, including when it has additional arguments
if (strncmp(cmd_str, "unseal", 6) == 0) {
return CMD_UNSEAL;
}
// Check if cmd_str is a help option // Check if cmd_str is a help option
if (strcmp(cmd_str, "-h") == 0 || strcmp(cmd_str, "--help") == 0) { if (strcmp(cmd_str, "-h") == 0 || strcmp(cmd_str, "--help") == 0) {
return CMD_HELP; return CMD_HELP;

View File

@@ -1,5 +1,111 @@
#include "commands.hpp" #include "commands.hpp"
int cmd_metadata(int argc, char** argv) {
// Parse command line options
bool force = false;
bool refresh = false;
bool verbose = false;
bool show_help = false;
std::string stage_dir = "";
std::string package_name = "";
std::string package_version = "";
std::string architecture = "";
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-f" || arg == "--force") {
force = true;
} else if (arg == "-r" || arg == "--refresh") {
refresh = true;
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
} else if ((arg == "-s" || arg == "--stage") && i + 1 < argc) {
stage_dir = argv[i + 1];
i++; // Skip the next argument
} else if ((arg == "-n" || arg == "--name") && i + 1 < argc) {
package_name = argv[i + 1];
i++; // Skip the next argument
} else if ((arg == "-V" || arg == "--version") && i + 1 < argc) {
package_version = argv[i + 1];
i++; // Skip the next argument
} else if ((arg == "-a" || arg == "--architecture") && i + 1 < argc) {
architecture = argv[i + 1];
i++; // Skip the next argument
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_metadata_help(argc, argv);
}
// Validate that stage directory is provided
if (stage_dir.empty()) {
dpm_log(LOG_ERROR, "Package stage directory is required (--stage/-s)");
return cmd_metadata_help(argc, argv);
}
// Expand path if needed
stage_dir = expand_path(stage_dir);
// Check if stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_log(LOG_ERROR, ("Stage directory does not exist: " + stage_dir).c_str());
return 1;
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Call the appropriate function based on the refresh flag
if (refresh) {
// For refresh mode, we only need the stage directory
bool success = metadata_refresh_dynamic_files(stage_dir);
if (!success) {
dpm_log(LOG_ERROR, "Failed to refresh metadata files.");
return 1;
}
dpm_log(LOG_INFO, "Metadata files refreshed successfully.");
return 0;
} else {
// For generate mode, we need additional parameters
if (package_name.empty()) {
dpm_log(LOG_ERROR, "Package name is required for metadata generation (--name/-n)");
return cmd_metadata_help(argc, argv);
}
if (package_version.empty()) {
dpm_log(LOG_ERROR, "Package version is required for metadata generation (--version/-V)");
return cmd_metadata_help(argc, argv);
}
if (architecture.empty()) {
dpm_log(LOG_ERROR, "Package architecture is required for metadata generation (--architecture/-a)");
return cmd_metadata_help(argc, argv);
}
bool success = metadata_generate_new(
std::filesystem::path(stage_dir),
package_name,
package_version,
architecture
);
if (!success) {
dpm_log(LOG_ERROR, "Failed to generate metadata files.");
return 1;
}
dpm_log(LOG_INFO, "Metadata files generated successfully.");
return 0;
}
}
int cmd_stage(int argc, char** argv) { int cmd_stage(int argc, char** argv) {
// Announce that the stage step is being executed (debug level) // Announce that the stage step is being executed (debug level)
@@ -41,6 +147,7 @@ int cmd_stage(int argc, char** argv) {
// Validate options // Validate options
int validate_result = validate_build_options(options); int validate_result = validate_build_options(options);
if (validate_result != 0) { if (validate_result != 0) {
cmd_stage_help(argc, argv);
return validate_result; return validate_result;
} }
@@ -76,16 +183,108 @@ int cmd_stage(int argc, char** argv) {
); );
} }
int cmd_sign(int argc, char** argv) {
// Parse command line options
std::string key_id = "";
std::string stage_dir = "";
std::string package_path = "";
bool force = false;
bool verbose = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-k" || arg == "--key-id") {
if (i + 1 < argc) {
key_id = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-s" || arg == "--stage") {
if (i + 1 < argc) {
stage_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-p" || arg == "--package") {
if (i + 1 < argc) {
package_path = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-f" || arg == "--force") {
force = true;
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_sign_help(argc, argv);
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Validate that key ID is provided
if (key_id.empty()) {
dpm_log(LOG_ERROR, "GPG key ID is required (--key-id/-k)");
return cmd_sign_help(argc, argv);
}
// Validate that either stage or package is provided, but not both
if (stage_dir.empty() && package_path.empty()) {
dpm_log(LOG_ERROR, "Either a package stage directory (--stage/-s) or a package file (--package/-p) must be specified");
return cmd_sign_help(argc, argv);
}
if (!stage_dir.empty() && !package_path.empty()) {
dpm_log(LOG_ERROR, "Cannot specify both package stage directory (--stage/-s) and package file (--package/-p)");
return cmd_sign_help(argc, argv);
}
// Expand paths if needed
if (!stage_dir.empty()) {
stage_dir = expand_path(stage_dir);
// Check if stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_log(LOG_ERROR, ("Stage directory does not exist: " + stage_dir).c_str());
return 1;
}
// Sign the stage directory
return sign_stage_directory(stage_dir, key_id, force);
} else {
package_path = expand_path(package_path);
// Check if package file exists
if (!std::filesystem::exists(package_path)) {
dpm_log(LOG_ERROR, ("Package file does not exist: " + package_path).c_str());
return 1;
}
// Sign the package file
return sign_package_file(package_path, key_id, force);
}
}
int cmd_help(int argc, char** argv) { int cmd_help(int argc, char** argv) {
dpm_log(LOG_INFO, "DPM Build Module - Creates DPM packages according to specification."); dpm_con(LOG_INFO, "DPM Build Module - Creates DPM packages.");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, "Available commands:"); dpm_con(LOG_INFO, "Available commands:");
dpm_log(LOG_INFO, " stage - Stage a new DPM package directory"); dpm_con(LOG_INFO, " stage - Stage a new DPM package directory");
dpm_log(LOG_INFO, " help - Display this help message"); dpm_con(LOG_INFO, " metadata - Generate or refresh package metadata");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, " sign - Sign a package or package stage directory");
dpm_log(LOG_INFO, "Usage: dpm build <command>"); dpm_con(LOG_INFO, " seal - Seal a package stage directory into final format");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, " unseal - Unseal a package back to stage format");
dpm_log(LOG_INFO, "For command-specific help, use: dpm build <command> --help"); dpm_con(LOG_INFO, " help - Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Usage: dpm build <command>");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "For command-specific help, use: dpm build <command> --help");
return 0; return 0;
} }
@@ -93,25 +292,273 @@ int cmd_help(int argc, char** argv) {
int cmd_unknown(const char* command, int argc, char** argv) { int cmd_unknown(const char* command, int argc, char** argv) {
std::string msg = "Unknown command: "; std::string msg = "Unknown command: ";
msg += (command ? command : ""); msg += (command ? command : "");
dpm_log(LOG_WARN, msg.c_str()); dpm_con(LOG_WARN, msg.c_str());
dpm_log(LOG_WARN, "Run 'dpm build help' for a list of available commands"); dpm_con(LOG_WARN, "Run 'dpm build help' for a list of available commands");
return 1; return 1;
} }
int cmd_stage_help(int argc, char** argv) { int cmd_metadata_help(int argc, char** argv) {
dpm_log(LOG_INFO, "Usage: dpm build stage [options]"); dpm_con(LOG_INFO, "Usage: dpm build metadata [options]");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, "Options:"); dpm_con(LOG_INFO, "Options:");
dpm_log(LOG_INFO, " -o, --output DIR Directory to save the staged package (required)"); dpm_con(LOG_INFO, " -s, --stage DIR Package stage directory path (required)");
dpm_log(LOG_INFO, " -c, --contents DIR Directory with package contents (required)"); dpm_con(LOG_INFO, " -r, --refresh Refresh existing metadata (use for updating)");
dpm_log(LOG_INFO, " -H, --hooks DIR Directory with package hooks (optional)"); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, " -n, --name NAME Package name (required)"); dpm_con(LOG_INFO, "For new metadata generation (when not using --refresh):");
dpm_log(LOG_INFO, " -V, --version VERSION Package version (required)"); dpm_con(LOG_INFO, " -n, --name NAME Package name (required for new generation)");
dpm_log(LOG_INFO, " -a, --architecture ARCH Package architecture (required, e.g., x86_64)"); dpm_con(LOG_INFO, " -V, --version VERSION Package version (required for new generation)");
dpm_log(LOG_INFO, " -O, --os OS Package OS (optional, e.g., dhl2)"); dpm_con(LOG_INFO, " -a, --architecture ARCH Package architecture (required for new generation)");
dpm_log(LOG_INFO, " -f, --force Force package staging even if warnings occur"); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); dpm_con(LOG_INFO, "Additional options:");
dpm_log(LOG_INFO, " -h, --help Display this help message"); dpm_con(LOG_INFO, " -f, --force Force operation even if warnings occur");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " # Refresh metadata in an existing package stage:");
dpm_con(LOG_INFO, " dpm build metadata --stage=./my-package-1.0.x86_64 --refresh");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, " # Generate new metadata for a package stage:");
dpm_con(LOG_INFO, " dpm build metadata --stage=./my-package-1.0.x86_64 --name=my-package --version=1.0 --architecture=x86_64");
return 0;
}
int cmd_stage_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm build stage [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -o, --output DIR Directory to save the staged package (required)");
dpm_con(LOG_INFO, " -c, --contents DIR Directory with package contents (required)");
dpm_con(LOG_INFO, " -H, --hooks DIR Directory with package hooks (optional)");
dpm_con(LOG_INFO, " -n, --name NAME Package name (required)");
dpm_con(LOG_INFO, " -V, --version VERSION Package version (required)");
dpm_con(LOG_INFO, " -a, --architecture ARCH Package architecture (required, e.g., x86_64)");
dpm_con(LOG_INFO, " -O, --os OS Package OS (optional, e.g., dhl2)");
dpm_con(LOG_INFO, " -f, --force Force package staging even if warnings occur");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
return 0;
}
int cmd_sign_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm build sign [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Sign a DPM package or package stage directory using GPG.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -k, --key-id ID GPG key ID or email to use for signing (required)");
dpm_con(LOG_INFO, " -s, --stage DIR Package stage directory to sign");
dpm_con(LOG_INFO, " -p, --package FILE Package file to sign");
dpm_con(LOG_INFO, " -f, --force Force signing even if warnings occur");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Either --stage or --package must be specified, but not both.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " dpm build sign --key-id=\"user@example.com\" --stage=./my-package-1.0.x86_64");
dpm_con(LOG_INFO, " dpm build sign --key-id=\"AB123CD456\" --package=./my-package-1.0.x86_64.dpm");
return 0;
}
int cmd_unseal(int argc, char** argv) {
// Parse command line options
std::string input_path = "";
std::string output_dir = "";
bool components_mode = false;
bool force = false;
bool verbose = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-i" || arg == "--input") {
if (i + 1 < argc) {
input_path = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-o" || arg == "--output") {
if (i + 1 < argc) {
output_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-c" || arg == "--components") {
components_mode = true;
} else if (arg == "-f" || arg == "--force") {
force = true;
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_unseal_help(argc, argv);
}
// Validate that input path is provided
if (input_path.empty()) {
dpm_con(LOG_ERROR, "Input path is required (--input/-i)");
return cmd_unseal_help(argc, argv);
}
// Check for invalid option combinations
if (components_mode && !output_dir.empty()) {
dpm_con(LOG_ERROR, "Output directory (-o/--output) cannot be specified in components mode (-c/--components)");
return cmd_unseal_help(argc, argv);
}
// Expand path if needed
input_path = expand_path(input_path);
// Check if input path exists
if (!std::filesystem::exists(input_path)) {
dpm_con(LOG_ERROR, ("Input path does not exist: " + input_path).c_str());
return 1;
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Determine which operation to perform based on components_mode flag
if (components_mode) {
// We're unsealing components of a stage directory
if (!std::filesystem::is_directory(input_path)) {
dpm_con(LOG_ERROR, ("Input path must be a directory in components mode: " + input_path).c_str());
return 1;
}
// Call unseal_stage_components with just the input path
return unseal_stage_components(input_path);
} else {
// We're unsealing a package file
if (std::filesystem::is_directory(input_path)) {
dpm_con(LOG_ERROR, ("Input path must be a file when not in components mode: " + input_path).c_str());
return 1;
}
// Call unseal_package
return unseal_package(input_path, output_dir, force);
}
}
int cmd_unseal_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm build unseal [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Unseals a DPM package file or package stage components.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -i, --input PATH Path to package file or stage directory (required)");
dpm_con(LOG_INFO, " -o, --output DIR Directory to extract package to (optional, package mode only)");
dpm_con(LOG_INFO, " -c, --components Component mode: unseal components in a stage directory");
dpm_con(LOG_INFO, " Without this flag, input is treated as a package file");
dpm_con(LOG_INFO, " -f, --force Force unsealing even if warnings occur or directory exists");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " # Unseal a package file to a directory:");
dpm_con(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64.dpm");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, " # Unseal a package file to a specific directory:");
dpm_con(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64.dpm --output=./extract");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, " # Unseal components in a stage directory:");
dpm_con(LOG_INFO, " dpm build unseal --input=./my-package-1.0.x86_64 --components");
return 0;
}
int cmd_seal(int argc, char** argv) {
// Parse command line options
std::string stage_dir = "";
std::string output_dir = "";
bool force = false;
bool verbose = false;
bool finalize = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-s" || arg == "--stage") {
if (i + 1 < argc) {
stage_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-o" || arg == "--output") {
if (i + 1 < argc) {
output_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-f" || arg == "--force") {
force = true;
} else if (arg == "-z" || arg == "--finalize") {
finalize = true;
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_seal_help(argc, argv);
}
// Validate that stage directory is provided
if (stage_dir.empty()) {
dpm_con(LOG_ERROR, "Stage directory is required (--stage/-s)");
return cmd_seal_help(argc, argv);
}
// Expand path if needed
stage_dir = expand_path(stage_dir);
// Check if stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_con(LOG_ERROR, ("Stage directory does not exist: " + stage_dir).c_str());
return 1;
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Call the appropriate sealing function based on the finalize flag
if (finalize) {
return seal_final_package(stage_dir, output_dir, force);
} else {
return seal_stage_components(stage_dir, force);
}
}
int cmd_seal_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm build seal [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Seals a package stage directory by replacing contents, metadata,");
dpm_con(LOG_INFO, "hooks, and signatures directories with gzipped tarballs.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -s, --stage DIR Package stage directory to seal (required)");
dpm_con(LOG_INFO, " -o, --output DIR Output directory for the finalized package (optional)");
dpm_con(LOG_INFO, " -f, --force Force sealing even if warnings occur");
dpm_con(LOG_INFO, " -z, --finalize Also compress the entire stage as a final package");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64");
dpm_con(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64 --finalize");
dpm_con(LOG_INFO, " dpm build seal --stage=./my-package-1.0.x86_64 --finalize --output=/tmp");
return 0; return 0;
} }

View File

@@ -0,0 +1,652 @@
/**
* @file metadata.cpp
* @brief Implementation of DPM package metadata functions
*
* Implements functions for creating and manipulating DPM package metadata.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "metadata.hpp"
// generates the initial entries for the stage - does not populate data!
bool metadata_generate_skeleton(const std::filesystem::path& stage_dir) {
// generates empty files, such as when generating a new stage
// determine the path to the metadata directory
std::filesystem::path metadata_dir = stage_dir / "metadata";
// Check if metadata directory exists and is a directory
if (!std::filesystem::exists(metadata_dir)) {
dpm_log(LOG_ERROR, ("Metadata directory does not exist: " + metadata_dir.string()).c_str());
return false;
}
if (!std::filesystem::is_directory(metadata_dir)) {
dpm_log(LOG_ERROR, ("Metadata path exists but is not a directory: " + metadata_dir.string()).c_str());
return false;
}
try {
// Create empty placeholder files for all metadata
std::vector<std::string> metadata_files = {
"NAME",
"VERSION",
"ARCHITECTURE",
"AUTHOR",
"MAINTAINER",
"DEPENDENCIES",
"DESCRIPTION",
"CONTENTS_MANIFEST_DIGEST",
"LICENSE",
"PACKAGE_DIGEST",
"HOOKS_DIGEST",
"PROVIDES",
"REPLACES",
"SOURCE",
"CHANGELOG"
};
for (const auto& file_name : metadata_files) {
std::ofstream metadata_file(metadata_dir / file_name);
metadata_file.close();
}
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to create metadata files: " + std::string(e.what())).c_str());
return false;
}
dpm_log(LOG_INFO, "Metadata skeleton generated.");
return true;
}
bool metadata_set_simple_value(const std::filesystem::path& stage_dir, const std::string& key, const std::string& value)
{
// populates single-line entries
// Determine the path to the metadata file
std::filesystem::path metadata_file_path = stage_dir / "metadata" / key;
// Check if the metadata directory exists
std::filesystem::path metadata_dir = stage_dir / "metadata";
if (!std::filesystem::exists(metadata_dir)) {
dpm_log(LOG_ERROR, ("Metadata directory does not exist: " + metadata_dir.string()).c_str());
return false;
}
if (!std::filesystem::is_directory(metadata_dir)) {
dpm_log(LOG_ERROR, ("Metadata path exists but is not a directory: " + metadata_dir.string()).c_str());
return false;
}
// Check if the metadata file exists
if (!std::filesystem::exists(metadata_file_path)) {
dpm_log(LOG_ERROR, ("Metadata file does not exist: " + metadata_file_path.string()).c_str());
return false;
}
try {
// Open the file for writing (will overwrite existing content)
std::ofstream file(metadata_file_path);
if (!file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open metadata file for writing: " + metadata_file_path.string()).c_str());
return false;
}
// Write the value to the file
file << value;
file.close();
dpm_log(LOG_INFO, ("Set metadata " + key + " to: " + value).c_str());
return true;
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to write metadata value: " + std::string(e.what())).c_str());
return false;
}
}
bool metadata_set_initial_known_values(
const std::filesystem::path& stage_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture
) {
std::filesystem::path metadata_dir = stage_dir / "metadata";
std::filesystem::path name_file = metadata_dir / "NAME";
std::filesystem::path version_file = metadata_dir / "VERSION";
std::filesystem::path architecture_file = metadata_dir / "ARCHITECTURE";
if (!metadata_set_simple_value( stage_dir, "NAME", package_name ))
{
dpm_log( LOG_FATAL, "Failed to set 'NAME'." );
return false;
}
if (!metadata_set_simple_value( stage_dir, "VERSION", package_version ))
{
dpm_log( LOG_FATAL, "Failed to set 'VERSION'." );
return false;
}
if (!metadata_set_simple_value( stage_dir, "ARCHITECTURE", architecture ))
{
dpm_log( LOG_FATAL, "Failed to set 'ARCHITECTURE'." );
return false;
}
return true;
}
bool metadata_generate_contents_manifest_digest(const std::filesystem::path& package_dir)
{
try {
std::filesystem::path contents_dir = package_dir / "contents";
std::filesystem::path manifest_path = package_dir / "metadata" / "CONTENTS_MANIFEST_DIGEST";
// Log which hash algorithm is being used
std::string hash_algorithm = get_configured_hash_algorithm();
dpm_log(LOG_INFO, ("Generating contents manifest using " + hash_algorithm + " checksums...").c_str());
// Open manifest file for writing
std::ofstream manifest_file(manifest_path);
if (!manifest_file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open manifest file for writing: " + manifest_path.string()).c_str());
return false;
}
// Process each file in the contents directory recursively
for (const auto& entry : std::filesystem::recursive_directory_iterator(contents_dir)) {
// Skip directories, we only need to record files
if (std::filesystem::is_directory(entry)) {
continue;
}
// Get file information
std::filesystem::path file_path = entry.path();
std::filesystem::path relative_path = std::filesystem::relative(file_path, contents_dir);
std::string absolute_path = "/" + relative_path.string(); // Add leading slash
// Get file stats for permissions
struct stat file_stat;
if (stat(file_path.c_str(), &file_stat) != 0) {
dpm_log(LOG_FATAL, ("Failed to get file stats for: " + file_path.string()).c_str());
return false;
}
// Format permissions as octal
char perms[5];
snprintf(perms, sizeof(perms), "%04o", file_stat.st_mode & 07777);
// Get owner and group information
struct passwd* pw = getpwuid(file_stat.st_uid);
struct group* gr = getgrgid(file_stat.st_gid);
std::string owner;
if (pw) {
owner = pw->pw_name;
} else {
owner = std::to_string(file_stat.st_uid);
}
std::string group;
if (gr) {
group = gr->gr_name;
} else {
group = std::to_string(file_stat.st_gid);
}
std::string ownership = owner + ":" + group;
// Calculate file checksum using the configured algorithm
std::string checksum = generate_file_checksum(file_path);
if (checksum.empty()) {
dpm_log(LOG_FATAL, ("Failed to generate checksum for: " + file_path.string()).c_str());
return false;
}
// By default, mark all files as controlled ('C')
char control_designation = 'C';
// Write the manifest entry
// Format: control_designation checksum permissions owner:group /absolute/path
manifest_file << control_designation << " "
<< checksum << " "
<< perms << " "
<< ownership << " "
<< absolute_path << "\n";
}
manifest_file.close();
return true;
}
catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to generate contents manifest: " + std::string(e.what())).c_str());
return false;
}
}
int metadata_refresh_contents_manifest_digest(const std::string& stage_dir, bool force) {
dpm_log(LOG_INFO, ("Refreshing package manifest for: " + stage_dir).c_str());
std::filesystem::path package_dir = std::filesystem::path(stage_dir);
std::filesystem::path contents_dir = package_dir / "contents";
std::filesystem::path manifest_path = package_dir / "metadata" / "CONTENTS_MANIFEST_DIGEST";
// Check if contents directory exists
if (!std::filesystem::exists(contents_dir)) {
dpm_log(LOG_ERROR, ("Contents directory does not exist: " + contents_dir.string()).c_str());
return 1;
}
// Map to track all files in the contents directory
std::map<std::filesystem::path, bool> all_content_files;
// Populate map with all files in contents directory
for (const auto& entry : std::filesystem::recursive_directory_iterator(contents_dir)) {
if (!std::filesystem::is_directory(entry)) {
// Store path relative to contents directory
std::filesystem::path relative_path = std::filesystem::relative(entry.path(), contents_dir);
all_content_files[relative_path] = false; // Not processed yet
}
}
// Check if manifest file exists
bool manifest_exists = std::filesystem::exists(manifest_path);
// Create a temporary file for the updated manifest
std::filesystem::path temp_manifest_path = manifest_path.string() + ".tmp";
std::ofstream temp_manifest_file(temp_manifest_path);
if (!temp_manifest_file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to create temporary manifest file: " + temp_manifest_path.string()).c_str());
return 1;
}
// Log which hash algorithm is being used
std::string hash_algorithm = get_configured_hash_algorithm();
dpm_log(LOG_INFO, ("Refreshing contents manifest using " + hash_algorithm + " checksums...").c_str());
int updated_files = 0;
int new_files = 0;
// First process existing manifest file if it exists
if (manifest_exists) {
std::ifstream manifest_file(manifest_path);
if (!manifest_file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open manifest file for reading: " + manifest_path.string()).c_str());
temp_manifest_file.close();
std::filesystem::remove(temp_manifest_path);
return 1;
}
std::string line;
int line_number = 0;
// Process each line in the manifest
while (std::getline(manifest_file, line)) {
line_number++;
// Skip empty lines
if (line.empty()) {
temp_manifest_file << line << std::endl;
continue;
}
// Parse the line into its components
std::istringstream iss(line);
char control_designation;
std::string checksum, permissions, ownership, file_path;
// Extract components (C checksum permissions owner:group /path/to/file)
iss >> control_designation >> checksum >> permissions >> ownership;
// The file path might contain spaces, so we need to get the rest of the line
std::getline(iss >> std::ws, file_path);
// Skip if we couldn't parse the line correctly
if (file_path.empty()) {
dpm_log(LOG_WARN, ("Skipping malformed line " + std::to_string(line_number) + ": " + line).c_str());
temp_manifest_file << line << std::endl;
continue;
}
// Remove leading slash from file_path if present
if (file_path[0] == '/') {
file_path = file_path.substr(1);
}
// Mark this file as processed
std::filesystem::path relative_path(file_path);
if (all_content_files.find(relative_path) != all_content_files.end()) {
all_content_files[relative_path] = true; // Mark as processed
}
// Construct the full path to the file in the contents directory
std::filesystem::path full_file_path = contents_dir / file_path;
// Check if the file exists
if (!std::filesystem::exists(full_file_path)) {
dpm_log(LOG_WARN, ("File not found in contents directory: " + full_file_path.string()).c_str());
// Keep the original line
temp_manifest_file << control_designation << " "
<< checksum << " "
<< permissions << " "
<< ownership << " "
<< "/" << file_path << std::endl;
continue;
}
// Calculate new checksum
std::string new_checksum = generate_file_checksum(full_file_path);
if (new_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to generate checksum for: " + full_file_path.string()).c_str());
manifest_file.close();
temp_manifest_file.close();
std::filesystem::remove(temp_manifest_path);
return 1;
}
// Write updated line to the temporary file
temp_manifest_file << control_designation << " "
<< new_checksum << " "
<< permissions << " "
<< ownership << " "
<< "/" << file_path << std::endl;
// Count updated files (only if checksum actually changed)
if (new_checksum != checksum) {
updated_files++;
}
}
manifest_file.close();
}
// Now process any new files not in the manifest
for (const auto& [file_path, processed] : all_content_files) {
// Skip if already processed from manifest
if (processed) {
continue;
}
// This is a new file
std::filesystem::path full_file_path = contents_dir / file_path;
// Get file stats for permissions
struct stat file_stat;
if (stat(full_file_path.c_str(), &file_stat) != 0) {
dpm_log(LOG_ERROR, ("Failed to get file stats for: " + full_file_path.string()).c_str());
continue;
}
// Format permissions as octal
char perms[5];
snprintf(perms, sizeof(perms), "%04o", file_stat.st_mode & 07777);
// Get owner and group information
struct passwd* pw = getpwuid(file_stat.st_uid);
struct group* gr = getgrgid(file_stat.st_gid);
std::string owner;
if (pw) {
owner = pw->pw_name;
} else {
owner = std::to_string(file_stat.st_uid);
}
std::string group;
if (gr) {
group = gr->gr_name;
} else {
group = std::to_string(file_stat.st_gid);
}
std::string ownership = owner + ":" + group;
// Calculate checksum
std::string checksum = generate_file_checksum(full_file_path);
if (checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to generate checksum for: " + full_file_path.string()).c_str());
continue;
}
// By default, mark new files as controlled ('C')
char control_designation = 'C';
// Write new line to the temporary file
temp_manifest_file << control_designation << " "
<< checksum << " "
<< perms << " "
<< ownership << " "
<< "/" << file_path.string() << std::endl;
new_files++;
}
temp_manifest_file.close();
// Replace the original file with the temporary file
try {
std::filesystem::rename(temp_manifest_path, manifest_path);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to update manifest file: " + std::string(e.what())).c_str());
std::filesystem::remove(temp_manifest_path);
return 1;
}
// Log results
if (updated_files > 0) {
dpm_log(LOG_INFO, ("Updated checksums for " + std::to_string(updated_files) + " existing file(s).").c_str());
}
if (new_files > 0) {
dpm_log(LOG_INFO, ("Added " + std::to_string(new_files) + " new file(s) to manifest.").c_str());
}
return 0;
}
bool metadata_generate_hooks_digest(const std::filesystem::path& stage_dir)
{
try {
std::filesystem::path hooks_dir = stage_dir / "hooks";
std::filesystem::path digest_path = stage_dir / "metadata" / "HOOKS_DIGEST";
// Check if hooks directory exists
if (!std::filesystem::exists(hooks_dir)) {
dpm_log(LOG_ERROR, ("Hooks directory does not exist: " + hooks_dir.string()).c_str());
return false;
}
// Log which hash algorithm is being used
std::string hash_algorithm = get_configured_hash_algorithm();
dpm_log(LOG_INFO, ("Generating hooks digest using " + hash_algorithm + " checksums...").c_str());
// Open digest file for writing
std::ofstream digest_file(digest_path);
if (!digest_file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open hooks digest file for writing: " + digest_path.string()).c_str());
return false;
}
// Process each file in the hooks directory
for (const auto& entry : std::filesystem::directory_iterator(hooks_dir)) {
// Skip directories, we only need to record files
if (std::filesystem::is_directory(entry)) {
continue;
}
// Get file information
std::filesystem::path file_path = entry.path();
std::string filename = entry.path().filename().string();
// Calculate file checksum using the configured algorithm
std::string checksum = generate_file_checksum(file_path);
if (checksum.empty()) {
dpm_log(LOG_FATAL, ("Failed to generate checksum for: " + file_path.string()).c_str());
return false;
}
// Write the digest entry
// Format: checksum filename
digest_file << checksum << " " << filename << "\n";
}
digest_file.close();
dpm_log(LOG_INFO, "Hooks digest generated successfully");
return true;
}
catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to generate hooks digest: " + std::string(e.what())).c_str());
return false;
}
}
bool metadata_generate_package_digest(const std::filesystem::path& stage_dir)
{
try {
std::filesystem::path contents_manifest_path = stage_dir / "metadata" / "CONTENTS_MANIFEST_DIGEST";
std::filesystem::path hooks_digest_path = stage_dir / "metadata" / "HOOKS_DIGEST";
// Check if required files exist
if (!std::filesystem::exists(contents_manifest_path)) {
dpm_log(LOG_ERROR, ("CONTENTS_MANIFEST_DIGEST not found: " + contents_manifest_path.string()).c_str());
return false;
}
if (!std::filesystem::exists(hooks_digest_path)) {
dpm_log(LOG_ERROR, ("HOOKS_DIGEST not found: " + hooks_digest_path.string()).c_str());
return false;
}
// Log which hash algorithm is being used
std::string hash_algorithm = get_configured_hash_algorithm();
dpm_log(LOG_INFO, ("Generating package digest using " + hash_algorithm + " checksums...").c_str());
// Calculate checksums of both files
std::string contents_manifest_checksum = generate_file_checksum(contents_manifest_path);
if (contents_manifest_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to generate checksum for: " + contents_manifest_path.string()).c_str());
return false;
}
std::string hooks_digest_checksum = generate_file_checksum(hooks_digest_path);
if (hooks_digest_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to generate checksum for: " + hooks_digest_path.string()).c_str());
return false;
}
// Concatenate the two checksums
std::string combined_checksums = contents_manifest_checksum + hooks_digest_checksum;
// Calculate checksum of the combined string
std::string package_digest = generate_string_checksum(combined_checksums);
if (package_digest.empty()) {
dpm_log(LOG_ERROR, "Failed to generate checksum of combined checksums");
return false;
}
// Set the package digest in the metadata
if (!metadata_set_simple_value(stage_dir, "PACKAGE_DIGEST", package_digest)) {
dpm_log(LOG_ERROR, "Failed to set PACKAGE_DIGEST value");
return false;
}
dpm_log(LOG_INFO, "Package digest generated successfully");
return true;
}
catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to generate package digest: " + std::string(e.what())).c_str());
return false;
}
}
// generates the dynamic entries for the stage
bool metadata_generate_dynamic_files(const std::filesystem::path& stage_dir)
{
// Generate contents manifest
dpm_log(LOG_INFO, "Generating contents manifest digest...");
if (!metadata_generate_contents_manifest_digest(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to generate contents manifest digest");
return false;
}
// Generate hooks digest
dpm_log(LOG_INFO, "Generating hooks digest...");
if (!metadata_generate_hooks_digest(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to generate hooks digest");
return false;
}
// Generate package digest
dpm_log(LOG_INFO, "Generating package digest...");
if (!metadata_generate_package_digest(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to generate package digest");
return false;
}
dpm_log(LOG_INFO, "Dynamic metadata generation completed successfully");
return true;
}
// refreshes the dynamic entries for the stage
bool metadata_refresh_dynamic_files(const std::filesystem::path& stage_dir)
{
// Refresh contents manifest
dpm_log(LOG_INFO, "Refreshing contents manifest digest...");
if (metadata_refresh_contents_manifest_digest(stage_dir, false) != 0) {
dpm_log(LOG_ERROR, "Failed to refresh contents manifest digest");
return false;
}
// Generate hooks digest
dpm_log(LOG_INFO, "Regenerating hooks digest...");
if (!metadata_generate_hooks_digest(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to regenerate hooks digest");
return false;
}
// Generate package digest
dpm_log(LOG_INFO, "Regenerating package digest...");
if (!metadata_generate_package_digest(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to regenerate package digest");
return false;
}
dpm_log(LOG_INFO, "Dynamic metadata refresh completed successfully");
return true;
}
bool metadata_generate_new(
const std::filesystem::path& stage_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture
)
{
// Step 1: Generate metadata skeleton
dpm_log(LOG_INFO, "Generating metadata skeleton...");
if (!metadata_generate_skeleton(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to generate metadata skeleton");
return false;
}
// Step 2: Set initial known values
dpm_log(LOG_INFO, "Setting initial metadata values...");
if (!metadata_set_initial_known_values(stage_dir, package_name, package_version, architecture)) {
dpm_log(LOG_ERROR, "Failed to set initial metadata values");
return false;
}
// Step 3: Generate dynamic files
dpm_log(LOG_INFO, "Generating dynamic metadata files...");
if (!metadata_generate_dynamic_files(stage_dir)) {
dpm_log(LOG_ERROR, "Failed to generate dynamic metadata files");
return false;
}
dpm_log(LOG_INFO, "Metadata generation completed successfully");
return true;
}

View File

@@ -1,243 +0,0 @@
/**
* @file package_staging.cpp
* @brief Implementation of DPM package staging functions
*
* Implements functions for staging DPM packages according to specification.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "package_staging.hpp"
int build_package_stage(
const std::string& output_dir,
const std::string& contents_dir,
const std::string& hooks_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture,
const std::string& os,
bool force
) {
// Log start of package staging
dpm_log(LOG_INFO, "Starting package staging...");
// Create the basic package structure in the output directory
std::string package_dirname = package_name + "-" + package_version + "." + os + "." + architecture;
std::filesystem::path package_dir = std::filesystem::path(output_dir) / package_dirname;
// Check if the package directory already exists
if (std::filesystem::exists(package_dir)) {
if (!force) {
dpm_log(LOG_ERROR, ("Package directory already exists: " + package_dir.string() +
". Use --force to overwrite.").c_str());
return 1;
}
// If force flag is set, try to remove the existing directory
dpm_log(LOG_WARN, ("Removing existing package directory: " + package_dir.string()).c_str());
try {
std::filesystem::remove_all(package_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to remove existing directory: " + std::string(e.what())).c_str());
return 1;
}
}
// Create the package directory structure
try {
// Create main package directory
std::filesystem::create_directory(package_dir);
// Create subdirectories
std::filesystem::create_directory(package_dir / "contents");
std::filesystem::create_directory(package_dir / "hooks");
std::filesystem::create_directory(package_dir / "metadata");
std::filesystem::create_directory(package_dir / "signatures");
dpm_log(LOG_INFO, ("Created package directory structure at: " + package_dir.string()).c_str());
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create package directory structure: " + std::string(e.what())).c_str());
return 1;
}
// Copy contents from source directory to package contents directory
try {
std::filesystem::path contents_source = std::filesystem::path(contents_dir);
std::filesystem::path contents_dest = package_dir / "contents";
dpm_log(LOG_INFO, ("Copying contents from: " + contents_source.string() +
" to: " + contents_dest.string()).c_str());
// If the contents source is a directory, copy its contents
if (std::filesystem::is_directory(contents_source)) {
for (const auto& entry : std::filesystem::directory_iterator(contents_source)) {
// Get the relative path from the source directory
std::filesystem::path relative = entry.path().lexically_relative(contents_source);
std::filesystem::path dest_path = contents_dest / relative;
if (entry.is_directory()) {
std::filesystem::create_directories(dest_path);
// Copy the directory contents recursively
for (const auto& subentry : std::filesystem::recursive_directory_iterator(entry)) {
std::filesystem::path subrelative = subentry.path().lexically_relative(contents_source);
std::filesystem::path subdest_path = contents_dest / subrelative;
if (subentry.is_directory()) {
std::filesystem::create_directories(subdest_path);
} else {
std::filesystem::copy_file(subentry.path(), subdest_path,
std::filesystem::copy_options::overwrite_existing);
}
}
} else {
std::filesystem::copy_file(entry.path(), dest_path,
std::filesystem::copy_options::overwrite_existing);
}
}
}
// If the contents source is a file (like a tarball), just copy it
else if (std::filesystem::is_regular_file(contents_source)) {
std::filesystem::path dest_path = contents_dest / contents_source.filename();
std::filesystem::copy_file(contents_source, dest_path,
std::filesystem::copy_options::overwrite_existing);
}
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to copy contents: " + std::string(e.what())).c_str());
return 1;
}
// Copy hooks if provided
if (!hooks_dir.empty()) {
try {
std::filesystem::path hooks_source = std::filesystem::path(hooks_dir);
std::filesystem::path hooks_dest = package_dir / "hooks";
dpm_log(LOG_INFO, ("Copying hooks from: " + hooks_source.string() +
" to: " + hooks_dest.string()).c_str());
// If hooks source is a directory, copy its contents
if (std::filesystem::is_directory(hooks_source)) {
for (const auto& entry : std::filesystem::directory_iterator(hooks_source)) {
// Get the relative path from the source directory
std::filesystem::path relative = entry.path().lexically_relative(hooks_source);
std::filesystem::path dest_path = hooks_dest / relative;
if (entry.is_directory()) {
std::filesystem::create_directories(dest_path);
} else {
std::filesystem::copy_file(entry.path(), dest_path,
std::filesystem::copy_options::overwrite_existing);
// Make hook files executable
chmod(dest_path.c_str(), 0755);
}
}
}
// If hooks source is a file, just copy it
else if (std::filesystem::is_regular_file(hooks_source)) {
std::filesystem::path dest_path = hooks_dest / hooks_source.filename();
std::filesystem::copy_file(hooks_source, dest_path,
std::filesystem::copy_options::overwrite_existing);
// Make hook file executable
chmod(dest_path.c_str(), 0755);
}
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to copy hooks: " + std::string(e.what())).c_str());
return 1;
}
} else {
// If no hooks directory provided, create empty hook script templates
std::vector<std::string> hook_names = {
"PRE-INSTALL", "PRE-INSTALL_ROLLBACK",
"POST-INSTALL", "POST-INSTALL_ROLLBACK",
"PRE-UPDATE", "PRE-UPDATE_ROLLBACK",
"POST-UPDATE", "POST-UPDATE_ROLLBACK",
"PRE-REMOVE", "PRE-REMOVE_ROLLBACK",
"POST-REMOVE", "POST-REMOVE_ROLLBACK"
};
dpm_log(LOG_INFO, "Creating empty hook templates");
for (const auto& hook_name : hook_names) {
std::filesystem::path hook_path = package_dir / "hooks" / hook_name;
std::ofstream hook_file(hook_path);
if (hook_file.is_open()) {
hook_file << "#!/bin/sh\n";
hook_file << "# " << hook_name << " hook for " << package_name << "\n";
hook_file << "# This is a template. Modify as needed.\n\n";
hook_file << "# Exit with non-zero status to indicate failure\n";
hook_file << "exit 0\n";
hook_file.close();
// Make the hook file executable
chmod(hook_path.c_str(), 0755);
} else {
dpm_log(LOG_ERROR, ("Failed to create hook file: " + hook_path.string()).c_str());
}
}
}
// Create basic metadata files
try {
std::filesystem::path metadata_dir = package_dir / "metadata";
// Create NAME file
{
std::ofstream name_file(metadata_dir / "NAME");
if (name_file.is_open()) {
name_file << package_name;
name_file.close();
}
}
// Create VERSION file
{
std::ofstream version_file(metadata_dir / "VERSION");
if (version_file.is_open()) {
version_file << package_version;
version_file.close();
}
}
// Create ARCHITECTURE file
{
std::ofstream arch_file(metadata_dir / "ARCHITECTURE");
if (arch_file.is_open()) {
arch_file << architecture;
arch_file.close();
}
}
// Create empty placeholder files for other metadata
std::vector<std::string> metadata_files = {
"AUTHOR", "MAINTAINER", "DEPENDENCIES", "DESCRIPTION",
"CONTENTS_MANIFEST_DIGEST", "LICENSE", "PACKAGE_DIGEST",
"HOOKS_DIGEST", "PROVIDES", "REPLACES", "SOURCE", "CHANGELOG"
};
for (const auto& file_name : metadata_files) {
std::ofstream metadata_file(metadata_dir / file_name);
metadata_file.close();
}
dpm_log(LOG_INFO, "Created metadata files");
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Failed to create metadata files: " + std::string(e.what())).c_str());
return 1;
}
dpm_log(LOG_INFO, "Package staging completed successfully");
dpm_log(LOG_INFO, ("Package staged at: " + package_dir.string()).c_str());
dpm_log(LOG_INFO, "Next steps:");
dpm_log(LOG_INFO, "1. Edit metadata files to provide package information");
dpm_log(LOG_INFO, "2. Update CONTENTS_MANIFEST_DIGEST to mark controlled/non-controlled files");
dpm_log(LOG_INFO, "3. Customize hook scripts as needed");
return 0;
}

View File

@@ -0,0 +1,703 @@
#include "sealing.hpp"
// TODO finalize's seal currently assumes unsealed components which is not necessarily the case.
// it should unseal if sealed and seal whole so that the metadata refresh is always accurate
bool file_already_compressed(const std::string& path)
{
// Convert string to filesystem path
std::filesystem::path fs_path(path);
// Check if it's a regular file (not a directory)
if (!std::filesystem::is_regular_file(fs_path))
{
return false;
}
// Open the file and check the magic number
std::ifstream file(fs_path, std::ios::binary);
if (!file.is_open())
{
return false;
}
unsigned char header[2];
if (!file.read(reinterpret_cast<char*>(header), 2))
{
file.close();
return false;
}
// Close the file after reading
file.close();
// Check for gzip magic number (0x1F 0x8B)
return (header[0] == 0x1F && header[1] == 0x8B);
}
// transform a directory at source_dir into a gzipped tarball at output_path
// source_dir and output_path cannot match
bool compress_directory( const std::string source_dir, const std::string output_path )
{
// Verify source directory exists
std::filesystem::path src_path(source_dir);
if ( !std::filesystem::exists(src_path) )
{
// path to compress doesn't exist, so bail
dpm_log(LOG_ERROR, ("Source directory does not exist: " + source_dir).c_str());
return false;
}
// Check if source is actually a directory
if ( !std::filesystem::is_directory(src_path) )
{
// it's not a directory, so bail
dpm_log(LOG_ERROR, ("Source is not a directory: " + source_dir).c_str());
return false;
}
// Check if source and output paths are the same
if ( source_dir == output_path )
{
// they match, so bail
dpm_log(LOG_ERROR, "Source directory and output path cannot be the same");
return false;
}
// if the output path is empty, bail
if ( output_path.empty() )
{
dpm_log(LOG_ERROR, "Output path is empty. Refusing to write a non-existant archive.");
return false;
}
// convert the output path to a path object
std::filesystem::path out_path(output_path);
// get the parent path directory
std::filesystem::path parent_path = out_path.parent_path();
// if the parent path is not empty and it does not exist
if ( !parent_path.empty() && !std::filesystem::exists(parent_path) )
{
// can't write to output path so bail
dpm_log( LOG_ERROR, ( "Output path parent directory does not exist: " + parent_path.string()).c_str() );
return false;
}
dpm_log( LOG_INFO, ("Compressing directory " + source_dir + " to archive " + out_path.string()).c_str() );
// Use libarchive to create a compressed tarball
struct archive * a;
struct archive_entry * entry;
char buff[8192];
int len;
// Create a new archive
a = archive_write_new();
// Set the compression format to gzip
archive_write_add_filter_gzip(a);
// Set the archive format to tar
archive_write_set_format_pax_restricted(a);
// Open the output file
if ( archive_write_open_filename( a, out_path.string().c_str()) != ARCHIVE_OK )
{
dpm_log( LOG_ERROR, ("Failed to create archive: " + out_path.string()).c_str() );
archive_write_free(a);
return false;
}
// Get the directory name to use as parent in the archive
std::string output_parent_dir = src_path.filename().string();
// First add the parent directory entry
entry = archive_entry_new();
archive_entry_set_pathname( entry, output_parent_dir.c_str() );
archive_entry_set_filetype( entry, AE_IFDIR );
archive_entry_set_perm( entry, 0755 ); // Standard directory permissions
archive_write_header( a, entry );
archive_entry_free( entry );
// Create a vector to store all entries in the directory for proper empty directory handling
std::vector<std::filesystem::path> all_entries;
// First collect all entries including empty directories
try
{
for ( const auto& dir_entry : std::filesystem::recursive_directory_iterator(src_path) )
{
all_entries.push_back(dir_entry.path());
}
}
catch (const std::exception& e)
{
dpm_log(LOG_ERROR, ("Error scanning directory: " + std::string(e.what())).c_str());
archive_write_close(a);
archive_write_free(a);
return false;
}
// Walk through all collected entries and add them to the archive
try
{
for ( const auto& full_path : all_entries )
{
// Get the relative path from the component path
std::string relative_path = std::filesystem::relative( full_path, src_path ).string();
// Path in archive with parent directory
std::string archive_path_entry = output_parent_dir + "/" + relative_path;
// Create a new entry for this file/directory
entry = archive_entry_new();
// Set the entry path with parent directory
archive_entry_set_pathname(entry, archive_path_entry.c_str());
// Handle different file types
if ( std::filesystem::is_symlink(full_path) )
{
// For symbolic links, set the link target
std::filesystem::path target = std::filesystem::read_symlink(full_path);
archive_entry_set_symlink(entry, target.c_str());
archive_entry_set_filetype(entry, AE_IFLNK);
// Get file information using lstat for the symlink itself
struct stat st;
lstat(full_path.string().c_str(), &st);
archive_entry_copy_stat(entry, &st);
// Write the entry header
archive_write_header(a, entry);
}
else if ( std::filesystem::is_directory(full_path) )
{
// For directories, set the directory type
archive_entry_set_filetype(entry, AE_IFDIR);
// Get file information using stat
struct stat st;
stat(full_path.string().c_str(), &st);
archive_entry_copy_stat(entry, &st);
// Write the entry header
archive_write_header(a, entry);
}
else if ( std::filesystem::is_regular_file(full_path) )
{
// For regular files, add the file content
archive_entry_set_filetype(entry, AE_IFREG);
// Get file information using stat
struct stat st;
stat(full_path.string().c_str(), &st);
archive_entry_copy_stat(entry, &st);
// Write the entry header
archive_write_header(a, entry);
// Write file contents
std::ifstream file(full_path, std::ios::binary);
if (file.is_open())
{
while (!file.eof())
{
file.read(buff, sizeof(buff));
len = file.gcount();
if (len > 0)
{
archive_write_data(a, buff, len);
}
}
file.close();
}
else
{
dpm_log(LOG_ERROR, ("Failed to open file for archiving: " + full_path.string()).c_str());
}
}
// Free the entry
archive_entry_free(entry);
}
}
catch (const std::exception& e)
{
dpm_log(LOG_ERROR, ("Error archiving directory: " + std::string(e.what())).c_str());
archive_write_close(a);
archive_write_free(a);
return false;
}
// Close and free the archive
archive_write_close(a);
archive_write_free(a);
dpm_log(LOG_INFO, ("Archive created at: " + out_path.string()).c_str());
return true;
}
// Uncompress a gzipped tarball at source_path to a directory at output_dir
bool uncompress_archive(const std::string& source_path, const std::string& output_dir)
{
dpm_log(LOG_INFO, ("Extracting archive " + source_path + " to directory " + output_dir).c_str());
// Verify source file exists
std::filesystem::path src_path(source_path);
if (!std::filesystem::exists(src_path)) {
dpm_log(LOG_ERROR, ("Source archive does not exist: " + source_path).c_str());
return false;
}
// Check if source is actually a regular file
if (!std::filesystem::is_regular_file(src_path)) {
dpm_log(LOG_ERROR, ("Source is not a file: " + source_path).c_str());
return false;
}
// Convert the output directory to a path object
std::filesystem::path out_path(output_dir);
// Get the parent path directory
std::filesystem::path parent_path = out_path.parent_path();
// If the parent path is not empty and it does not exist
if (!parent_path.empty() && !std::filesystem::exists(parent_path)) {
// Can't write to output path so bail
dpm_log(LOG_ERROR, ("Output path parent directory does not exist: " + parent_path.string()).c_str());
return false;
}
// Use libarchive to extract the archive
struct archive* a;
struct archive* ext;
struct archive_entry* entry;
int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS;
int r;
a = archive_read_new();
archive_read_support_format_tar(a);
archive_read_support_filter_gzip(a);
ext = archive_write_disk_new();
archive_write_disk_set_options(ext, flags);
archive_write_disk_set_standard_lookup(ext);
// Open the archive
if ((r = archive_read_open_filename(a, source_path.c_str(), 10240)) != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Failed to open archive: " + source_path).c_str());
archive_read_free(a);
archive_write_free(ext);
return false;
}
// Extract all entries
bool success = true;
while (success) {
r = archive_read_next_header(a, &entry);
if (r == ARCHIVE_EOF) {
break;
}
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Archive read error: " + std::string(archive_error_string(a))).c_str());
success = false;
break;
}
// Modify entry pathname to extract to output directory
std::string entry_path = archive_entry_pathname(entry);
// Skip parent directory entries if present (directory with no '/' in path)
if (entry_path.find('/') == std::string::npos &&
archive_entry_filetype(entry) == AE_IFDIR) {
continue;
}
// Remove parent directory from path if present
size_t first_slash = entry_path.find('/');
if (first_slash != std::string::npos) {
entry_path = entry_path.substr(first_slash + 1);
}
// Skip empty paths after removing parent directory
if (entry_path.empty()) {
continue;
}
// Set the new path for this entry
std::string full_path = (out_path / entry_path).string();
archive_entry_set_pathname(entry, full_path.c_str());
// Write the entry to disk
r = archive_write_header(ext, entry);
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Archive write error: " + std::string(archive_error_string(ext))).c_str());
success = false;
break;
}
// Copy the file data if it's a regular file
if (archive_entry_size(entry) > 0) {
const void* buff;
size_t size;
la_int64_t offset;
while (true) {
r = archive_read_data_block(a, &buff, &size, &offset);
if (r == ARCHIVE_EOF) {
break;
}
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Archive read data error: " + std::string(archive_error_string(a))).c_str());
success = false;
break;
}
r = archive_write_data_block(ext, buff, size, offset);
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Archive write data error: " + std::string(archive_error_string(ext))).c_str());
success = false;
break;
}
}
if (!success) {
break;
}
}
r = archive_write_finish_entry(ext);
if (r != ARCHIVE_OK) {
dpm_log(LOG_ERROR, ("Archive finish entry error: " + std::string(archive_error_string(ext))).c_str());
success = false;
break;
}
}
// Clean up
archive_read_close(a);
archive_read_free(a);
archive_write_close(ext);
archive_write_free(ext);
if (success) {
dpm_log(LOG_INFO, ("Successfully extracted archive to: " + output_dir).c_str());
}
return success;
}
// compresses a directory component in a pacakge stage
bool smart_compress_component( const std::filesystem::path& stage_dir, const std::filesystem::path& component )
{
std::filesystem::path component_path = stage_dir / component.string().c_str();
// check if it's not a directory
if ( ! std::filesystem::is_directory(component_path) )
{
// it's not a directory.
// has it already been compressed?
if ( file_already_compressed(component_path.string() ) )
{
// that component has already been compressed, so behave idempotently
dpm_log(LOG_INFO, ( component_path.string() + " is already compressed, nothing to do." ).c_str() );
return true;
} else {
// it's not a directory and it's not a compressed archive, so bail
dpm_log(LOG_ERROR, ("Component is not a directory and not a compressed archive: " + component_path.string() ).c_str() );
return false;
}
} else {
// it's a directory so compress it
dpm_log(LOG_INFO, ("Compressing directory: " + component_path.string()).c_str());
bool result = compress_directory( component_path, component_path.string() + ".tmp" );
if ( ! result ) {
dpm_log( LOG_ERROR, ("Failed to compress component directory: " + component_path.string() ).c_str() );
return false;
}
}
// clean up the evidence
try {
std::filesystem::remove_all(component_path);
std::filesystem::rename( component_path.string() + ".tmp", component_path.string() );
}
catch ( const std::exception& e ) {
dpm_log(LOG_FATAL, ("Error placing new archive: " + std::string(e.what())).c_str());
std::filesystem::remove( component_path.string() + ".tmp" );
return false;
}
dpm_log( LOG_INFO, ( "Successfully created archive at: " + component_path.string() ).c_str() ); ;
return true;
}
extern "C" int seal_stage_components(const std::string& stage_dir, bool force)
{
dpm_con(LOG_INFO, ("Sealing package stage: " + stage_dir).c_str());
// First refresh the metadata to ensure it's up-to-date
dpm_con(LOG_INFO, "Refreshing metadata before sealing...");
bool metadata_refresh_result = metadata_refresh_dynamic_files(stage_dir);
if (!metadata_refresh_result) {
dpm_con(LOG_ERROR, "Failed to refresh metadata files before sealing. Aborting.");
return 1;
}
// Verify the stage directory structure
std::filesystem::path stage_path(stage_dir);
if (!smart_compress_component(stage_dir, "contents")) {
dpm_con(LOG_FATAL, ("Failed to compress contents: " + stage_dir).c_str());
return 1;
}
if (!smart_compress_component(stage_dir, "hooks")) {
dpm_con(LOG_FATAL, ("Failed to compress hooks: " + stage_dir).c_str());
return 1;
}
if (!smart_compress_component(stage_dir, "metadata")) {
dpm_con(LOG_FATAL, ("Failed to compress metadata: " + stage_dir).c_str());
return 1;
}
// Handle signatures component - check if it's an empty directory
if (std::filesystem::is_directory(stage_path / "signatures")) {
bool signatures_empty = true;
// Check if signatures directory is empty
for (const auto& entry : std::filesystem::directory_iterator(stage_path / "signatures")) {
signatures_empty = false;
break;
}
if (signatures_empty) {
dpm_con(LOG_INFO, "Signatures directory is empty, not compressing.");
} else {
dpm_con(LOG_INFO, "Compressing signatures component.");
if (!smart_compress_component(stage_dir, "signatures")) {
dpm_con(LOG_FATAL, ("Failed to compress signatures: " + stage_dir).c_str());
return 1;
}
}
}
dpm_con(LOG_INFO, "Package stage sealed successfully.");
return 0;
}
extern "C" int seal_final_package(const std::string &stage_dir, const std::string &output_dir, bool force)
{
int stage_seal_result = seal_stage_components( stage_dir, force );
if ( stage_seal_result != 0 ) {
dpm_log( LOG_FATAL, "Component sealing stage failed. Exiting." );
return 1;
}
std::filesystem::path stage_path( stage_dir );
if ( ! std::filesystem::is_directory( stage_path ) ) {
dpm_log( LOG_FATAL, "Stage is not a directory. Refusing to continue.");
return 1;
}
std::filesystem::path output_path;
if ( output_dir.empty() ) {
// the user didn't supply an output directory, so put the dpm next to the stage
output_path = stage_path.string() + ".dpm";
} else {
// the user supplied an output directory so call it stage_name.dpm and prefix the path
// with the output dir
std::string stage_basename = stage_path.filename().string();
// it's here
output_path = std::filesystem::path(output_dir) / std::filesystem::path(stage_basename + ".dpm");
}
dpm_log( LOG_INFO, "Sealing DPM Package." );
bool result = compress_directory( stage_path.string(), output_path.string() );
if ( ! result ) {
dpm_log( LOG_FATAL, "Could not create DPM package from stage." );
return 1;
}
dpm_log( LOG_INFO, ("Package written to: " + output_path.string() ).c_str() );
return 0;
}
extern "C" int unseal_package(const std::string& package_filepath, const std::string& output_dir, bool force)
{
dpm_log(LOG_INFO, ("Unsealing package: " + package_filepath).c_str());
// Extract filename from package path
std::filesystem::path supplied_package_path(package_filepath);
std::string package_filename = supplied_package_path.filename().string();
// Verify it has .dpm extension
const std::string dpm_extension = ".dpm";
if (!package_filename.ends_with(dpm_extension)) {
dpm_log(LOG_FATAL, "Refusing to unseal package: file must have .dpm extension");
return 1;
}
// Remove .dpm extension to establish the tatget stage directory to extract to
std::string target_stage_name = package_filename.substr(0, package_filename.length() - dpm_extension.length());
// Determine the output directory path based on whether output_dir was supplied or not
std::filesystem::path output_directory;
if (output_dir.empty()) {
// output_dir was not supplied, so set it to the supplied package path's parent dir + target_stage_name
// Set output path to parent_directory/filename_without_extension
output_directory = supplied_package_path.parent_path() / target_stage_name;
} else {
// output_dir was supplied, so use that
// Use the provided output directory
output_directory = std::filesystem::path(output_dir) / target_stage_name;
}
// Check if target path already exists
if (std::filesystem::exists(output_directory)) {
if (!force) {
// a stage dir already exists with this name at that path so it can't be used unless --force is used
// to overwrite it
dpm_log(LOG_ERROR, ("Output directory already exists: " + output_directory.string() +
". Use --force to overwrite.").c_str());
return 1;
}
// If force flag is set, remove the existing directory
try {
std::filesystem::remove_all(output_directory);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to remove existing directory: " + std::string(e.what())).c_str());
return 1;
}
}
// Create the output directory
try {
std::filesystem::create_directories(output_directory);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create output directory: " + std::string(e.what())).c_str());
return 1;
}
// Extract the package to the output directory
bool result = uncompress_archive(package_filepath, output_directory.string());
if (!result) {
dpm_log(LOG_ERROR, "Failed to extract package");
return 1;
}
dpm_log(LOG_INFO, ("Package unsealed successfully to: " + output_directory.string()).c_str());
return 0;
}
// Uncompress a package component if needed
bool smart_uncompress_component(const std::filesystem::path& stage_dir, const std::filesystem::path& component)
{
std::filesystem::path component_path = stage_dir / component.string().c_str();
// Check if component exists
if (!std::filesystem::exists(component_path)) {
dpm_log(LOG_ERROR, ("Component not found: " + component_path.string()).c_str());
return false;
}
// Check if it's already a directory (already uncompressed)
if (std::filesystem::is_directory(component_path)) {
// Component is already a directory, so nothing to do
dpm_log(LOG_INFO, (component_path.string() + " is already a directory, nothing to do.").c_str());
return true;
}
// Create a temporary directory for extraction
std::filesystem::path temp_dir = component_path.string() + ".tmp";
// Clean up any existing temp directory
if (std::filesystem::exists(temp_dir)) {
try {
std::filesystem::remove_all(temp_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to clean up existing temp directory: " + std::string(e.what())).c_str());
return false;
}
}
// Create the temp directory
try {
std::filesystem::create_directory(temp_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create temp directory: " + std::string(e.what())).c_str());
return false;
}
// Extract the component to the temp directory
dpm_log(LOG_INFO, ("Uncompressing component: " + component_path.string()).c_str());
bool result = uncompress_archive(component_path.string(), temp_dir.string());
if (!result) {
dpm_log(LOG_ERROR, ("Failed to uncompress component: " + component_path.string()).c_str());
std::filesystem::remove_all(temp_dir);
return false;
}
// Remove the compressed file and rename the temp directory to take its place
try {
std::filesystem::remove(component_path);
std::filesystem::rename(temp_dir, component_path);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_FATAL, ("Error replacing compressed component with uncompressed directory: " + std::string(e.what())).c_str());
return false;
}
dpm_log(LOG_INFO, ("Successfully uncompressed component: " + component_path.string()).c_str());
return true;
}
extern "C" int unseal_stage_components(const std::filesystem::path& stage_dir)
{
dpm_log(LOG_INFO, ("Unsealing package components in: " + stage_dir.string()).c_str());
// Verify the stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_log(LOG_ERROR, ("Stage directory does not exist: " + stage_dir.string()).c_str());
return 1;
}
// Check if the required components exist (including signatures)
std::vector<std::filesystem::path> components = {
"contents",
"metadata",
"hooks",
"signatures"
};
bool all_components_exist = true;
for (const auto& component : components) {
if (!std::filesystem::exists(stage_dir / component)) {
dpm_log(LOG_ERROR, ("Missing required component: " + component.string()).c_str());
all_components_exist = false;
}
}
if (!all_components_exist) {
dpm_log(LOG_FATAL, "Cannot unseal package: missing required components");
return 1;
}
// Uncompress each component in order
for (const auto& component : components) {
if (!smart_uncompress_component(stage_dir, component)) {
dpm_log(LOG_FATAL, ("Failed to uncompress component: " + component.string()).c_str());
return 1;
}
}
dpm_log(LOG_INFO, "Package components unsealed successfully");
return 0;
}

View File

@@ -0,0 +1,256 @@
#include "signing.hpp"
/**
* @brief Signs a component archive using GPGME
*
* Creates a detached GPG signature for a component archive
*
* @param stage_path Path to the stage directory
* @param key_id GPG key ID or email to use for signing
* @param component_name Name of the component to sign (contents, hooks, metadata)
* @return 0 on success, non-zero on failure
*/
static int sign_component(const std::filesystem::path& stage_path, const std::string& key_id,
const std::string& component_name) {
dpm_log(LOG_INFO, ("Signing " + component_name + " component...").c_str());
std::filesystem::path component_path = stage_path / component_name;
std::filesystem::path signature_path = stage_path / "signatures" / (component_name + ".signature");
// Initialize GPGME
gpgme_ctx_t ctx;
gpgme_error_t err;
// Initialize GPGME library
const char* version = gpgme_check_version(NULL);
if (version == NULL) {
dpm_log(LOG_ERROR, "Failed to initialize GPGME library");
return 1;
}
// Create a new GPGME context
err = gpgme_new(&ctx);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, "Failed to create GPGME context");
return 1;
}
// Set protocol to OpenPGP
err = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, "Failed to set GPGME protocol");
gpgme_release(ctx);
return 1;
}
// Set armor mode (for ASCII-armored output)
gpgme_set_armor(ctx, 1);
// Set signing key
gpgme_key_t key;
err = gpgme_get_key(ctx, key_id.c_str(), &key, 1);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, ("Failed to find signing key: " + key_id).c_str());
gpgme_release(ctx);
return 1;
}
// Add the key to the context
gpgme_signers_clear(ctx);
err = gpgme_signers_add(ctx, key);
gpgme_key_unref(key);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, "Failed to add signing key to context");
gpgme_release(ctx);
return 1;
}
// Open the component file
FILE* component_file = fopen(component_path.string().c_str(), "rb");
if (!component_file) {
dpm_log(LOG_ERROR, ("Failed to open component file: " + component_path.string()).c_str());
gpgme_release(ctx);
return 1;
}
// Open the signature file
FILE* signature_file = fopen(signature_path.string().c_str(), "wb");
if (!signature_file) {
dpm_log(LOG_ERROR, ("Failed to create signature file: " + signature_path.string()).c_str());
fclose(component_file);
gpgme_release(ctx);
return 1;
}
// Create data objects for input and output
gpgme_data_t in_data, out_data;
err = gpgme_data_new_from_stream(&in_data, component_file);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, "Failed to create input data object");
fclose(component_file);
fclose(signature_file);
gpgme_release(ctx);
return 1;
}
err = gpgme_data_new_from_stream(&out_data, signature_file);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, "Failed to create output data object");
gpgme_data_release(in_data);
fclose(component_file);
fclose(signature_file);
gpgme_release(ctx);
return 1;
}
// Sign the data
err = gpgme_op_sign(ctx, in_data, out_data, GPGME_SIG_MODE_DETACH);
if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) {
dpm_log(LOG_ERROR, ("Failed to sign component: " + std::string(gpgme_strerror(err))).c_str());
gpgme_data_release(in_data);
gpgme_data_release(out_data);
fclose(component_file);
fclose(signature_file);
gpgme_release(ctx);
return 1;
}
// Clean up
gpgme_data_release(in_data);
gpgme_data_release(out_data);
fclose(component_file);
fclose(signature_file);
gpgme_release(ctx);
return 0;
}
int sign_stage_directory(const std::string& stage_dir, const std::string& key_id, bool force) {
dpm_log(LOG_INFO, ("Signing package stage: " + stage_dir).c_str());
// Verify the stage directory structure
std::filesystem::path stage_path(stage_dir);
std::filesystem::path contents_path = stage_path / "contents";
std::filesystem::path hooks_path = stage_path / "hooks";
std::filesystem::path metadata_path = stage_path / "metadata";
std::filesystem::path signatures_path = stage_path / "signatures";
// Check if required directories exist
if (!std::filesystem::exists(contents_path)) {
dpm_log(LOG_ERROR, ("Invalid stage directory: contents not found in " + stage_dir).c_str());
return 1;
}
if (!std::filesystem::exists(hooks_path)) {
dpm_log(LOG_ERROR, ("Invalid stage directory: hooks not found in " + stage_dir).c_str());
return 1;
}
if (!std::filesystem::exists(metadata_path)) {
dpm_log(LOG_ERROR, ("Invalid stage directory: metadata not found in " + stage_dir).c_str());
return 1;
}
// Create signatures directory if it doesn't exist
if (!std::filesystem::exists(signatures_path)) {
dpm_log(LOG_INFO, ("Creating signatures directory in " + stage_dir).c_str());
try {
std::filesystem::create_directory(signatures_path);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create signatures directory: " + std::string(e.what())).c_str());
return 1;
}
}
// Sign each component
int result = 0;
// Sign contents
if (sign_component(stage_path, key_id, "contents") != 0) {
dpm_log(LOG_ERROR, "Failed to sign contents component");
result = 1;
}
// Sign hooks
if (sign_component(stage_path, key_id, "hooks") != 0) {
dpm_log(LOG_ERROR, "Failed to sign hooks component");
result = 1;
}
// Sign metadata
if (sign_component(stage_path, key_id, "metadata") != 0) {
dpm_log(LOG_ERROR, "Failed to sign metadata component");
result = 1;
}
if (result == 0) {
dpm_log(LOG_INFO, "Package stage signed successfully.");
}
return result;
}
int sign_package_file(const std::string& package_path, const std::string& key_id, bool force) {
dpm_log(LOG_INFO, ("Signing package file: " + package_path).c_str());
// Get the temporary stage path by removing .dpm extension
std::string tmp_stage_path = package_path;
if (tmp_stage_path.ends_with(".dpm")) {
tmp_stage_path = tmp_stage_path.substr(0, tmp_stage_path.length() - 4);
}
// Check if temporary stage path already exists - fail if it does
if (std::filesystem::exists(tmp_stage_path)) {
dpm_log(LOG_ERROR, ("Temporary stage directory already exists: " + tmp_stage_path).c_str());
return 1;
}
// 1. Unseal the package to the stage parent path
std::filesystem::path stage_parent_path = std::filesystem::path(tmp_stage_path).parent_path();
dpm_log(LOG_INFO, "Unsealing package file...");
int result = unseal_package(package_path, stage_parent_path, force);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to unseal package file");
return result;
}
// 2. Sign the stage directory components
dpm_log(LOG_INFO, "Signing package components...");
result = sign_stage_directory(tmp_stage_path, key_id, force);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to sign package components");
return result;
}
std::string backup_path = package_path + ".old";
// Back up the original package
try {
std::filesystem::rename(package_path, backup_path);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to backup original package: " + std::string(e.what())).c_str());
return 1;
}
// 3. Create a new sealed package at the original location
dpm_log(LOG_INFO, "Creating signed package file...");
// seal the package path
result = seal_final_package(tmp_stage_path, stage_parent_path.string(), force);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to create signed package");
return result;
}
// 4. Clean up
try {
std::filesystem::remove_all(tmp_stage_path);
std::filesystem::remove(backup_path);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_WARN, ("Failed to clean up temporary files: " + std::string(e.what())).c_str());
}
dpm_log(LOG_INFO, ("Successfully signed package: " + package_path).c_str());
return 0;
}

View File

@@ -0,0 +1,317 @@
/**
* @file package_staging.cpp
* @brief Implementation of DPM package staging functions
*
* Implements functions for staging DPM packages according to specification.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "staging.hpp"
// generates a directory for the stage according to naming convention
std::filesystem::path stage_determine_rootdir_path(
const std::string& output_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture,
const std::string& os
) {
std::string package_dirname = package_name + "-" + package_version + "." + os + "." + architecture;
std::filesystem::path package_dir = std::filesystem::path(output_dir) / package_dirname;
return package_dir;
}
// check if a directory exists
bool stage_directory_exists( const std::filesystem::path& package_dir )
{
return std::filesystem::exists(package_dir);
}
bool stage_create_rootdir( const std::filesystem::path& package_dir, bool force )
{
// Check if the package directory already exists
if (stage_directory_exists(package_dir))
{
if (!force) {
dpm_log(LOG_ERROR, ("Package directory already exists: " + package_dir.string() +
". Use --force to overwrite.").c_str());
return false;
}
// If force flag is set, try to remove the existing directory
dpm_log(LOG_WARN, ("Removing existing package directory: " + package_dir.string()).c_str());
try {
std::filesystem::remove_all(package_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to remove existing directory: " + std::string(e.what())).c_str());
return false;
}
}
// Create the directory
try {
std::filesystem::create_directories(package_dir);
return true;
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create directory: " + std::string(e.what())).c_str());
return false;
}
}
bool stage_create_subdir(const std::filesystem::path& package_dir, const std::string& subdir_name)
{
try {
std::filesystem::create_directory(package_dir / subdir_name);
return true;
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create subdirectory '" + subdir_name +
"': " + std::string(e.what())).c_str());
return false;
}
}
static bool stage_build_stage_skeleton( const std::filesystem::path& package_dir, bool force )
{
// Check if the package directory already exists and handle appropriately
if (!stage_create_rootdir(package_dir, force)) {
dpm_log( LOG_FATAL, ("Failed to create root directory: " + package_dir.string()).c_str());
return false;
}
// Create subdirectories
if (!stage_create_subdir(package_dir, "contents")) { return false; }
if (!stage_create_subdir(package_dir, "hooks")) { return false; }
if (!stage_create_subdir(package_dir, "metadata")) { return false; }
if (!stage_create_subdir(package_dir, "signatures")) { return false; }
// assume success if we made it to this point
dpm_log(LOG_INFO, ("Created package directory structure at: " + package_dir.string()).c_str());
return true;
}
static bool stage_copy_dir( const std::filesystem::path& source_path, const std::filesystem::path& dest_path )
{
try {
dpm_log(LOG_INFO, ("Copying from: " + source_path.string() +
" to: " + dest_path.string()).c_str());
// Check if source exists
if (!std::filesystem::exists(source_path)) {
dpm_log(LOG_ERROR, ("Source path does not exist: " + source_path.string()).c_str());
return false;
}
// If the contents source is a directory, copy its contents
if (std::filesystem::is_directory(source_path)) {
for (const auto& entry : std::filesystem::directory_iterator(source_path)) {
// Get the relative path from the source directory
std::filesystem::path relative = entry.path().lexically_relative(source_path);
std::filesystem::path dest_path_item = dest_path / relative;
if (entry.is_directory()) {
std::filesystem::create_directories(dest_path_item);
// Copy the directory contents recursively
for (const auto& subentry : std::filesystem::recursive_directory_iterator(entry)) {
std::filesystem::path subrelative = subentry.path().lexically_relative(source_path);
std::filesystem::path subdest_path = dest_path / subrelative;
if (subentry.is_directory()) {
std::filesystem::create_directories(subdest_path);
} else {
std::filesystem::copy_file(subentry.path(), subdest_path,
std::filesystem::copy_options::overwrite_existing);
}
}
} else {
std::filesystem::copy_file(entry.path(), dest_path_item,
std::filesystem::copy_options::overwrite_existing);
}
}
} else {
dpm_log(LOG_ERROR, ("Source is not a directory: " + source_path.string()).c_str());
return false;
}
return true;
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to copy: " + std::string(e.what())).c_str());
return false;
}
}
static bool stage_populate_contents(
const std::filesystem::path& package_dir,
const std::string& contents_dir
) {
std::filesystem::path contents_source = std::filesystem::path(contents_dir);
std::filesystem::path contents_dest = package_dir / "contents";
if (!stage_copy_dir(contents_source, contents_dest))
{
dpm_log( LOG_FATAL, "Failed to copy the contents directory to the package stage. Exiting." );
return false;
}
return true;
}
static bool stage_populate_hooks(
const std::filesystem::path& package_dir,
const std::string& hooks_dir,
const std::string& package_name
) {
// Define the required hook names
std::vector<std::string> hook_names = {
"PRE-INSTALL", "PRE-INSTALL_ROLLBACK",
"POST-INSTALL", "POST-INSTALL_ROLLBACK",
"PRE-UPDATE", "PRE-UPDATE_ROLLBACK",
"POST-UPDATE", "POST-UPDATE_ROLLBACK",
"PRE-REMOVE", "PRE-REMOVE_ROLLBACK",
"POST-REMOVE", "POST-REMOVE_ROLLBACK"
};
// Copy hooks if provided
if (!hooks_dir.empty())
{
std::filesystem::path hooks_source = std::filesystem::path(hooks_dir);
std::filesystem::path hooks_dest = package_dir / "hooks";
// Validate the hooks directory before copying
if (std::filesystem::exists(hooks_source) && std::filesystem::is_directory(hooks_source)) {
// Check if all required hooks are present
for (const auto& hook_name : hook_names) {
std::filesystem::path hook_path = hooks_source / hook_name;
if (!std::filesystem::exists(hook_path)) {
dpm_log(LOG_ERROR, ("Missing required hook file: " + hook_name).c_str());
return false;
}
}
// Check for unexpected files or directories
for (const auto& entry : std::filesystem::directory_iterator(hooks_source)) {
std::string filename = entry.path().filename().string();
// Check if entry is a directory and reject it
if (std::filesystem::is_directory(entry)) {
dpm_log(LOG_ERROR, ("Unexpected directory in hooks directory: " + filename).c_str());
return false;
}
// Check if the file is a valid hook
bool is_valid_hook = false;
for (const auto& hook_name : hook_names) {
if (filename == hook_name) {
is_valid_hook = true;
break;
}
}
if (!is_valid_hook) {
dpm_log(LOG_ERROR, ("Unexpected file in hooks directory: " + filename).c_str());
return false;
}
}
} else {
dpm_log(LOG_ERROR, ("Hooks directory does not exist or is not a directory: " + hooks_source.string()).c_str());
return false;
}
// Now copy the directory after validation
if (!stage_copy_dir(hooks_source, hooks_dest))
{
dpm_log(LOG_FATAL, "Failed to copy the hooks directory to the package stage. Exiting.");
return false;
}
// Make hook files executable
for (const auto& entry : std::filesystem::recursive_directory_iterator(hooks_dest)) {
if (!entry.is_directory()) {
chmod(entry.path().c_str(), 0755);
}
}
} else {
// If no hooks directory provided, create empty hook script templates
dpm_log(LOG_INFO, "Creating empty hook templates");
for (const auto& hook_name : hook_names) {
std::filesystem::path hook_path = package_dir / "hooks" / hook_name;
std::ofstream hook_file(hook_path);
if (hook_file.is_open()) {
hook_file << "#!/bin/sh\n";
hook_file << "# " << hook_name << " hook for " << package_name << "\n";
hook_file << "# This is a template. Modify as needed.\n\n";
hook_file << "# Exit with non-zero status to indicate failure\n";
hook_file << "exit 0\n";
hook_file.close();
// Make the hook file executable
chmod(hook_path.c_str(), 0755);
} else {
dpm_log(LOG_ERROR, ("Failed to create hook file: " + hook_path.string()).c_str());
return false;
}
}
}
return true;
}
int build_package_stage(
const std::string& output_dir,
const std::string& contents_dir,
const std::string& hooks_dir,
const std::string& package_name,
const std::string& package_version,
const std::string& architecture,
const std::string& os,
bool force
) {
// Log start of package staging
dpm_log(LOG_INFO, "Starting package staging...");
// Generate package directory path
std::filesystem::path package_dir = stage_determine_rootdir_path(
output_dir, package_name, package_version, architecture, os
);
// Build the package skeleton
if (!stage_build_stage_skeleton(package_dir, force))
{
return 1;
}
// copy the contents dir to the contents part of the package stage
if (!stage_populate_contents(package_dir, contents_dir))
{
return 1;
}
// copy the supplied hooks or create a new blank hooks dir in the stage
if (!stage_populate_hooks(package_dir, hooks_dir, package_name))
{
return 1;
}
// Populate metadata files
if (!metadata_generate_new(package_dir, package_name, package_version, architecture))
{
return 1;
}
dpm_log(LOG_INFO, "Package staging completed successfully");
dpm_log(LOG_INFO, ("Package staged at: " + package_dir.string()).c_str());
dpm_log(LOG_INFO, "Next steps:");
dpm_log(LOG_INFO, "1. Edit metadata files to provide package information");
dpm_log(LOG_INFO, "2. Update CONTENTS_MANIFEST_DIGEST to mark controlled/non-controlled files");
dpm_log(LOG_INFO, "3. Customize hook scripts as needed");
return 0;
}

View File

@@ -82,16 +82,17 @@ std::string detect_os() {
/** /**
* Command handler for help command * Command handler for help command
*/ */
int cmd_help(int argc, char** argv) { int cmd_help(int argc, char** argv) {
dpm_log(LOG_INFO, "DPM Info Module - Provides information about the system."); dpm_con(LOG_INFO, "DPM Info Module - Provides information about the system.");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, "Available commands:"); dpm_con(LOG_INFO, "Available commands:");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, "");
dpm_log(LOG_INFO, " version - Display DPM version information"); dpm_con(LOG_INFO, " version - Display DPM version information");
dpm_log(LOG_INFO, " system - Display system information"); dpm_con(LOG_INFO, " system - Display system information");
dpm_log(LOG_INFO, " config - Display configuration information"); dpm_con(LOG_INFO, " config - Display configuration information");
dpm_log(LOG_INFO, " help - Display this help message"); dpm_con(LOG_INFO, " help - Display this help message");
dpm_log(LOG_INFO, ""); dpm_con(LOG_INFO, "");
return 0; return 0;
} }
@@ -102,15 +103,15 @@ int cmd_help(int argc, char** argv) {
int cmd_version(int argc, char** argv) { int cmd_version(int argc, char** argv) {
std::string version_msg = "DPM Version: "; std::string version_msg = "DPM Version: ";
version_msg += DPM_VERSION; version_msg += DPM_VERSION;
dpm_log(LOG_INFO, version_msg.c_str()); dpm_con(LOG_INFO, version_msg.c_str());
std::string date_msg = "Build Date: "; std::string date_msg = "Build Date: ";
date_msg += __DATE__; date_msg += __DATE__;
dpm_log(LOG_INFO, date_msg.c_str()); dpm_con(LOG_INFO, date_msg.c_str());
std::string time_msg = "Build Time: "; std::string time_msg = "Build Time: ";
time_msg += __TIME__; time_msg += __TIME__;
dpm_log(LOG_INFO, time_msg.c_str()); dpm_con(LOG_INFO, time_msg.c_str());
return 0; return 0;
} }
@@ -119,15 +120,15 @@ int cmd_version(int argc, char** argv) {
* Command handler for system command * Command handler for system command
*/ */
int cmd_system(int argc, char** argv) { int cmd_system(int argc, char** argv) {
dpm_log(LOG_INFO, "System Information:"); dpm_con(LOG_INFO, "System Information:");
std::string os_msg = " OS: "; std::string os_msg = " OS: ";
os_msg += detect_os(); os_msg += detect_os();
dpm_log(LOG_INFO, os_msg.c_str()); dpm_con(LOG_INFO, os_msg.c_str());
std::string arch_msg = " Architecture: "; std::string arch_msg = " Architecture: ";
arch_msg += detect_architecture(); arch_msg += detect_architecture();
dpm_log(LOG_INFO, arch_msg.c_str()); dpm_con(LOG_INFO, arch_msg.c_str());
return 0; return 0;
} }
@@ -138,11 +139,11 @@ int cmd_system(int argc, char** argv) {
int cmd_config(int argc, char** argv) { int cmd_config(int argc, char** argv) {
const char* module_path = dpm_get_config("modules", "module_path"); const char* module_path = dpm_get_config("modules", "module_path");
dpm_log(LOG_INFO, "Configuration Information:"); dpm_con(LOG_INFO, "Configuration Information:");
std::string path_msg = " Module Path: "; std::string path_msg = " Module Path: ";
path_msg += (module_path ? module_path : "Not configured"); path_msg += (module_path ? module_path : "Not configured");
dpm_log(LOG_INFO, path_msg.c_str()); dpm_con(LOG_INFO, path_msg.c_str());
return 0; return 0;
} }
@@ -153,8 +154,8 @@ int cmd_config(int argc, char** argv) {
int cmd_unknown(const char* command, int argc, char** argv) { int cmd_unknown(const char* command, int argc, char** argv) {
std::string msg = "Unknown command: "; std::string msg = "Unknown command: ";
msg += (command ? command : ""); msg += (command ? command : "");
dpm_log(LOG_WARN, msg.c_str()); dpm_con(LOG_WARN, msg.c_str());
dpm_log(LOG_WARN, "Run 'dpm info help' for a list of available commands"); dpm_con(LOG_WARN, "Run 'dpm info help' for a list of available commands");
return 1; return 1;
} }

View File

@@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.22)
project(verify_module)
set(CMAKE_CXX_STANDARD 20)
# Set DPM_ROOT_DIR based on whether this is a standalone build or part of the main build
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(DPM_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..")
else()
set(DPM_ROOT_DIR "${CMAKE_SOURCE_DIR}")
endif()
# Create shared library - add CommonModuleAPI.cpp to the sources
add_library(verify MODULE
verify.cpp
src/commands.cpp
${DPM_ROOT_DIR}/dpmdk/src/CommonModuleAPI.cpp
src/cli_parsers.cpp
src/verification.cpp
src/checksum.cpp
../../dpmdk/src/ModuleOperations.cpp
src/package_operations.cpp
src/checksum_memory.cpp
)
# Set output properties
set_target_properties(
verify PROPERTIES
PREFIX ""
SUFFIX ".so"
)
# Include directories
target_include_directories(verify PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${DPM_ROOT_DIR}
${DPM_ROOT_DIR}/dpmdk/include
)
# Link with required libraries
target_link_libraries(verify dl)
# Standalone version - used for debugging
add_executable(verify_standalone
verify.cpp
src/commands.cpp
${DPM_ROOT_DIR}/dpmdk/src/CommonModuleAPI.cpp
src/cli_parsers.cpp
src/verification.cpp
src/checksum.cpp
../../dpmdk/src/ModuleOperations.cpp
src/package_operations.cpp
src/checksum_memory.cpp
)
# Define the BUILD_STANDALONE macro for the standalone build
target_compile_definitions(verify_standalone PRIVATE BUILD_STANDALONE)
# Include directories for standalone
target_include_directories(verify_standalone PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${DPM_ROOT_DIR}
${DPM_ROOT_DIR}/dpmdk/include
)
# Link with required libraries for standalone too
target_link_libraries(verify_standalone dl)
# Set the output name for the standalone executable
set_target_properties(
verify_standalone PROPERTIES
OUTPUT_NAME "verify_debug"
)

View File

@@ -0,0 +1,51 @@
/**
* @file checksum.hpp
* @brief Header file for package checksum verification functions
*
* Defines functions for verifying checksums of DPM package components.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <filesystem>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "checksum.hpp"
/**
* @brief Verify the CONTENTS_MANIFEST_DIGEST file
*
* Compares checksums in manifest with actual file checksums
*
* @param stage_dir Path to the stage directory
* @param build_module Handle to the loaded build module
* @return 0 on success, non-zero on failure
*/
int checksum_verify_contents_digest(const std::string& stage_dir, void* build_module);
/**
* @brief Verify the HOOKS_DIGEST file
*
* Compares checksums in hooks digest with actual file checksums
*
* @param stage_dir Path to the stage directory
* @param build_module Handle to the loaded build module
* @return 0 on success, non-zero on failure
*/
int checksum_verify_hooks_digest(const std::string& stage_dir, void* build_module);
/**
* @brief Verify the PACKAGE_DIGEST file
*
* Calculates the combined checksum of CONTENTS_MANIFEST_DIGEST and HOOKS_DIGEST
* and compares it with the value in PACKAGE_DIGEST
*
* @param stage_dir Path to the stage directory
* @param build_module Handle to the loaded build module
* @return 0 on success, non-zero on failure
*/
int checksum_verify_package_digest(const std::string& stage_dir, void* build_module);

View File

@@ -0,0 +1,75 @@
/**
* @file checksum_memory.hpp
* @brief In-memory package checksum verification functions
*
* Defines functions for verifying checksums of DPM package components in memory
* without requiring them to be extracted to disk first.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "package_operations.hpp"
#include <filesystem>
#include <dlfcn.h>
/**
* @brief Verifies the package digest from in-memory metadata
*
* Calculates the package digest from in-memory CONTENTS_MANIFEST_DIGEST and
* HOOKS_DIGEST files and compares it to the value in PACKAGE_DIGEST.
*
* @param data Pointer to the metadata file data
* @param data_size Size of the metadata file data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_package_digest_memory(
const unsigned char* data,
size_t data_size,
void* build_module);
/**
* @brief Verifies the contents manifest digest from in-memory data
*
* Compares checksums in the contents manifest with actual file checksums
* using in-memory data rather than extracting files to disk.
*
* @param contents_data Pointer to the contents component data
* @param contents_data_size Size of the contents component data
* @param metadata_data Pointer to the metadata component data
* @param metadata_data_size Size of the metadata component data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_contents_digest_memory(
const unsigned char* contents_data,
size_t contents_data_size,
const unsigned char* metadata_data,
size_t metadata_data_size,
void* build_module);
/**
* @brief Verifies the hooks digest from in-memory data
*
* Calculates the digest of the hooks archive and compares it with the
* value stored in HOOKS_DIGEST metadata file.
*
* @param hooks_data Pointer to the hooks component data
* @param hooks_data_size Size of the hooks component data
* @param metadata_data Pointer to the metadata component data
* @param metadata_data_size Size of the metadata component data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_hooks_digest_memory(
const unsigned char* hooks_data,
size_t hooks_data_size,
const unsigned char* metadata_data,
size_t metadata_data_size,
void* build_module);

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <cstring>
/**
* @enum Command
* @brief Enumeration of supported commands for the verify module
*/
enum Command {
CMD_UNKNOWN, /**< Unknown or unsupported command */
CMD_HELP, /**< Display help information */
CMD_CHECKSUM, /**< Verify package checksums */
CMD_SIGNATURE, /**< Verify package signatures */
CMD_CHECK /**< Check build module integration */
};
/**
* @brief Parses a command string into a Command enum value
*
* Converts a command string to the appropriate Command enum value
* for internal routing.
*
* @param cmd_str The command string to parse
* @return The corresponding Command enum value
*/
Command parse_command(const char* cmd_str);

View File

@@ -0,0 +1,185 @@
/**
* @file commands.hpp
* @brief Header file for the verify module command handlers
*
* Defines functions and enumerations for the verify module which verifies
* the integrity and signatures of package files and stage directories.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <cstring>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include <dlfcn.h>
#include <sys/stat.h>
#include <filesystem>
#include "checksum_memory.hpp"
#include "package_operations.hpp"
/**
* @brief Handler for the checksum command
*
* Verifies the checksums of package files or stage directories.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_checksum(int argc, char** argv);
/**
* @brief Handler for the checksum help command
*
* Displays help information for the checksum command.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success
*/
int cmd_checksum_help(int argc, char** argv);
/**
* @brief Handler for the signature command
*
* Verifies the signatures of package files or stage directories.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_signature(int argc, char** argv);
/**
* @brief Handler for the signature help command
*
* Displays help information for the signature command.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success
*/
int cmd_signature_help(int argc, char** argv);
/**
* @brief Handler for the help command
*
* Displays information about available commands in the verify module.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_help(int argc, char** argv);
/**
* @brief Handler for the check command
*
* Checks if the build module can be loaded. This validates that the integration
* between the verify module and build module is working correctly.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success, non-zero on failure
*/
int cmd_check(int argc, char** argv);
/**
* @brief Handler for the check help command
*
* Displays help information for the check command.
*
* @param argc Number of arguments
* @param argv Array of arguments
* @return 0 on success
*/
int cmd_check_help(int argc, char** argv);
/**
* @brief Handler for unknown commands
*
* Displays an error message for unrecognized commands.
*
* @param command The unrecognized command string
* @param argc Number of arguments
* @param argv Array of arguments
* @return 1 to indicate failure
*/
int cmd_unknown(const char* command, int argc, char** argv);
/**
* @brief Helper function to check and load the build module
*
* Checks if the build module exists and can be loaded.
*
* @param module_handle Reference to a void pointer that will hold the module handle
* @return 0 on success, non-zero on failure
*/
int check_and_load_build_module(void*& module_handle);
/**
* @brief Verifies checksums for a package file
*
* Checks the integrity of a package file by verifying its checksums.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_checksums_package(const std::string& package_path);
/**
* @brief Verifies checksums for a package stage directory
*
* Checks the integrity of a package stage directory by verifying its checksums.
*
* @param stage_dir Path to the stage directory
* @return 0 on success, non-zero on failure
*/
int verify_checksums_stage(const std::string& stage_dir);
/**
* @brief Verifies signatures for a package file
*
* Checks the signatures of a package file.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_signature_package(const std::string& package_path);
/**
* @brief Verifies signatures for a package stage directory
*
* Checks the signatures of a package stage directory.
*
* @param stage_dir Path to the stage directory
* @return 0 on success, non-zero on failure
*/
int verify_signature_stage(const std::string& stage_dir);
/**
* @brief Verifies checksums of a package file in memory
*
* Loads the components of a package file into memory and verifies their checksums
* without extracting them to disk.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_checksums_package_memory(const std::string& package_path);
/**
* @brief Converts binary data to a C++ string
*
* Takes a buffer of binary data and its size, creates a properly
* null-terminated string, and returns it as an std::string.
*
* @param data Pointer to the binary data
* @param data_size Size of the binary data
* @return std::string containing the data, or empty string on error
*/
std::string binary_to_string(const unsigned char* data, size_t data_size);

View File

@@ -0,0 +1,53 @@
/**
* @file package_operations.hpp
* @brief Functions for operating on DPM packages
*
* Defines functions for extracting and verifying components from DPM packages.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "commands.hpp"
#include <filesystem>
/**
* @brief Extracts a component from a package file
*
* Loads a component (metadata, contents, hooks, signatures) from a package file
* by calling into the build module's get_file_from_package_file function.
*
* @param package_path Path to the package file
* @param component_name Name of the component to extract (metadata, contents, hooks, signatures)
* @param data Pointer to a pointer that will be populated with the component data
* @param data_size Pointer to a size_t that will be populated with the size of the component data
* @return 0 on success, non-zero on failure
*/
int get_component_from_package(const std::string& package_path,
const std::string& component_name,
unsigned char** data,
size_t* data_size);
/**
* @brief Extracts a file from a component archive
*
* Extracts a specific file from a component archive that has already been loaded into memory.
* Uses the build module's get_file_from_memory_loaded_archive function.
*
* @param component_data Pointer to the component archive data in memory
* @param component_size Size of the component archive in memory
* @param filename Name of the file to extract from the component
* @param data Pointer to a pointer that will be populated with the file data
* @param data_size Pointer to a size_t that will be populated with the size of the file data
* @return 0 on success, non-zero on failure
*/
int get_file_from_component(const unsigned char* component_data,
size_t component_size,
const std::string& filename,
unsigned char** data,
size_t* data_size);

View File

@@ -0,0 +1,59 @@
/**
* @file verification.hpp
* @brief Functions for verifying package integrity and signatures
*
* Defines functions for verifying checksums and signatures of DPM packages
* and package stage directories.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#pragma once
#include <string>
#include <filesystem>
#include <dpmdk/include/CommonModuleAPI.hpp>
#include "commands.hpp"
#include "checksum.hpp"
/**
* @brief Verifies checksums for a package file
*
* Checks the integrity of a package file by verifying its checksums.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_checksums_package(const std::string& package_path);
/**
* @brief Verifies checksums for a package stage directory
*
* Checks the integrity of a package stage directory by verifying its checksums.
*
* @param stage_dir Path to the stage directory
* @return 0 on success, non-zero on failure
*/
int verify_checksums_stage(const std::string& stage_dir);
/**
* @brief Verifies signatures for a package file
*
* Checks the signatures of a package file.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_signature_package(const std::string& package_path);
/**
* @brief Verifies signatures for a package stage directory
*
* Checks the signatures of a package stage directory.
*
* @param stage_dir Path to the stage directory
* @return 0 on success, non-zero on failure
*/
int verify_signature_stage(const std::string& stage_dir);

View File

@@ -0,0 +1,301 @@
/**
* @file checksum.cpp
* @brief Implementation of package checksum verification functions
*
* Implements functions for verifying checksums of DPM package components.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "checksum.hpp"
#include <fstream>
#include <sstream>
#include <dlfcn.h>
int checksum_verify_contents_digest(const std::string& stage_dir, void* build_module) {
dpm_log(LOG_INFO, "Verifying contents manifest digest...");
std::filesystem::path manifest_file = std::filesystem::path(stage_dir) / "metadata" / "CONTENTS_MANIFEST_DIGEST";
if (!std::filesystem::exists(manifest_file)) {
dpm_log(LOG_ERROR, "CONTENTS_MANIFEST_DIGEST file not found");
return 1;
}
// Get the generate_file_checksum function from the build module
typedef std::string (*FileChecksumFunc)(const std::filesystem::path&);
dlerror(); // Clear any previous error
FileChecksumFunc generate_checksum = (FileChecksumFunc)dlsym(build_module, "generate_file_checksum");
const char* dlsym_error = dlerror();
if (dlsym_error) {
dpm_log(LOG_ERROR, ("Failed to find generate_file_checksum function: " +
std::string(dlsym_error)).c_str());
return 1;
}
try {
std::ifstream manifest(manifest_file);
if (!manifest.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open manifest file: " + manifest_file.string()).c_str());
return 1;
}
std::string line;
int line_number = 0;
int errors = 0;
while (std::getline(manifest, line)) {
line_number++;
// Skip empty lines
if (line.empty()) continue;
// Parse the line: control_designation checksum permissions owner:group /path/to/file
std::istringstream iss(line);
char control_designation;
std::string checksum, permissions, ownership, file_path;
if (!(iss >> control_designation >> checksum >> permissions >> ownership)) {
dpm_log(LOG_WARN, ("Malformed manifest line " + std::to_string(line_number) +
": " + line).c_str());
continue;
}
// Get the rest of the line as the file path
std::getline(iss >> std::ws, file_path);
if (file_path.empty()) {
dpm_log(LOG_WARN, ("Missing file path in manifest line " +
std::to_string(line_number)).c_str());
continue;
}
// Remove leading slash if present
if (file_path[0] == '/') {
file_path = file_path.substr(1);
}
// Build the full path to the file in the contents directory
std::filesystem::path full_file_path = std::filesystem::path(stage_dir) / "contents" / file_path;
// Check if the file exists
if (!std::filesystem::exists(full_file_path)) {
dpm_log(LOG_ERROR, ("File not found: " + full_file_path.string()).c_str());
errors++;
continue;
}
// Calculate the checksum of the file and compare with the manifest
std::string calculated_checksum = generate_checksum(full_file_path);
if (calculated_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to calculate checksum for: " +
full_file_path.string()).c_str());
errors++;
continue;
}
if (calculated_checksum != checksum) {
dpm_log(LOG_ERROR, ("Checksum mismatch for " + full_file_path.string() +
"\n Expected: " + checksum +
"\n Actual: " + calculated_checksum).c_str());
errors++;
}
}
manifest.close();
if (errors > 0) {
dpm_log(LOG_ERROR, (std::to_string(errors) + " checksum errors found in contents manifest").c_str());
return 1;
}
dpm_log(LOG_INFO, "Contents manifest checksum verification successful");
return 0;
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Error processing contents manifest: " + std::string(e.what())).c_str());
return 1;
}
}
int checksum_verify_hooks_digest(const std::string& stage_dir, void* build_module) {
dpm_log(LOG_INFO, "Verifying hooks digest...");
std::filesystem::path hooks_digest_file = std::filesystem::path(stage_dir) / "metadata" / "HOOKS_DIGEST";
if (!std::filesystem::exists(hooks_digest_file)) {
dpm_log(LOG_ERROR, "HOOKS_DIGEST file not found");
return 1;
}
// Get the generate_file_checksum function from the build module
typedef std::string (*FileChecksumFunc)(const std::filesystem::path&);
dlerror(); // Clear any previous error
FileChecksumFunc generate_checksum = (FileChecksumFunc)dlsym(build_module, "generate_file_checksum");
const char* dlsym_error = dlerror();
if (dlsym_error) {
dpm_log(LOG_ERROR, ("Failed to find generate_file_checksum function: " +
std::string(dlsym_error)).c_str());
return 1;
}
try {
std::ifstream hooks_digest(hooks_digest_file);
if (!hooks_digest.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open hooks digest file: " +
hooks_digest_file.string()).c_str());
return 1;
}
std::string line;
int errors = 0;
while (std::getline(hooks_digest, line)) {
// Skip empty lines
if (line.empty()) continue;
// Parse the line: checksum filename
std::istringstream iss(line);
std::string checksum, filename;
if (!(iss >> checksum >> filename)) {
dpm_log(LOG_WARN, ("Malformed hooks digest line: " + line).c_str());
continue;
}
// Build the full path to the hook file
std::filesystem::path hook_path = std::filesystem::path(stage_dir) / "hooks" / filename;
// Check if the file exists
if (!std::filesystem::exists(hook_path)) {
dpm_log(LOG_ERROR, ("Hook file not found: " + hook_path.string()).c_str());
errors++;
continue;
}
// Calculate the checksum of the hook file
std::string calculated_checksum = generate_checksum(hook_path);
if (calculated_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to calculate checksum for: " +
hook_path.string()).c_str());
errors++;
continue;
}
if (calculated_checksum != checksum) {
dpm_log(LOG_ERROR, ("Checksum mismatch for " + hook_path.string() +
"\n Expected: " + checksum +
"\n Actual: " + calculated_checksum).c_str());
errors++;
}
}
hooks_digest.close();
if (errors > 0) {
dpm_log(LOG_ERROR, (std::to_string(errors) + " checksum errors found in hooks digest").c_str());
return 1;
}
dpm_log(LOG_INFO, "Hooks digest checksum verification successful");
return 0;
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Error processing hooks digest: " + std::string(e.what())).c_str());
return 1;
}
}
int checksum_verify_package_digest(const std::string& stage_dir, void* build_module) {
dpm_log(LOG_INFO, "Verifying package digest...");
std::filesystem::path metadata_dir = std::filesystem::path(stage_dir) / "metadata";
std::filesystem::path package_digest_file = metadata_dir / "PACKAGE_DIGEST";
std::filesystem::path manifest_file = metadata_dir / "CONTENTS_MANIFEST_DIGEST";
std::filesystem::path hooks_digest_file = metadata_dir / "HOOKS_DIGEST";
if (!std::filesystem::exists(package_digest_file)) {
dpm_log(LOG_ERROR, "PACKAGE_DIGEST file not found");
return 1;
}
if (!std::filesystem::exists(manifest_file)) {
dpm_log(LOG_ERROR, "CONTENTS_MANIFEST_DIGEST file not found");
return 1;
}
if (!std::filesystem::exists(hooks_digest_file)) {
dpm_log(LOG_ERROR, "HOOKS_DIGEST file not found");
return 1;
}
// Get the checksum functions from the build module
typedef std::string (*FileChecksumFunc)(const std::filesystem::path&);
typedef std::string (*StringChecksumFunc)(const std::string&);
dlerror(); // Clear any previous error
FileChecksumFunc generate_file_checksum = (FileChecksumFunc)dlsym(build_module, "generate_file_checksum");
const char* dlsym_error = dlerror();
if (dlsym_error) {
dpm_log(LOG_ERROR, ("Failed to find generate_file_checksum function: " +
std::string(dlsym_error)).c_str());
return 1;
}
dlerror(); // Clear any previous error
StringChecksumFunc generate_string_checksum = (StringChecksumFunc)dlsym(build_module, "generate_string_checksum");
dlsym_error = dlerror();
if (dlsym_error) {
dpm_log(LOG_ERROR, ("Failed to find generate_string_checksum function: " +
std::string(dlsym_error)).c_str());
return 1;
}
// Read the package digest from the file
std::string package_digest;
try {
std::ifstream digest_file(package_digest_file);
if (!digest_file.is_open()) {
dpm_log(LOG_ERROR, ("Failed to open digest file: " + package_digest_file.string()).c_str());
return 1;
}
std::getline(digest_file, package_digest);
digest_file.close();
} catch (const std::exception& e) {
dpm_log(LOG_ERROR, ("Error reading package digest: " + std::string(e.what())).c_str());
return 1;
}
// Calculate checksums of the digest files
std::string contents_manifest_checksum = generate_file_checksum(manifest_file);
std::string hooks_digest_checksum = generate_file_checksum(hooks_digest_file);
if (contents_manifest_checksum.empty() || hooks_digest_checksum.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate checksums for digest files");
return 1;
}
// Combine checksums and calculate package digest
std::string combined_checksums = contents_manifest_checksum + hooks_digest_checksum;
std::string calculated_package_digest = generate_string_checksum(combined_checksums);
if (calculated_package_digest.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate package digest");
return 1;
}
// Compare with the stored package digest
if (calculated_package_digest != package_digest) {
dpm_log(LOG_ERROR, ("Package digest mismatch\n Expected: " + package_digest +
"\n Actual: " + calculated_package_digest).c_str());
return 1;
}
dpm_log(LOG_INFO, "Package digest verification successful");
return 0;
}

View File

@@ -0,0 +1,427 @@
/**
* @file checksum_memory.cpp
* @brief Implementation of in-memory package checksum verification functions
*
* Implements functions for verifying checksums of DPM package components in memory
* without requiring them to be extracted to disk first.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "checksum_memory.hpp"
/**
* @brief Converts binary data to a C++ string
*
* Takes a buffer of binary data and its size, creates a properly
* null-terminated string, and returns it as an std::string.
*
* @param data Pointer to the binary data
* @param data_size Size of the binary data
* @return std::string containing the data, or empty string on error
*/
std::string binary_to_string(const unsigned char* data, size_t data_size) {
if (!data || data_size == 0) {
return std::string();
}
// Create a temporary C-string with null termination
char* temp = (char*)malloc(data_size + 1);
if (!temp) {
return std::string();
}
memcpy(temp, data, data_size);
temp[data_size] = '\0';
// Create std::string from the C-string
std::string result(temp);
// Free the temporary buffer
free(temp);
return result;
}
/**
* @brief Verifies the package digest from in-memory metadata
*
* Calculates the package digest from in-memory CONTENTS_MANIFEST_DIGEST and
* HOOKS_DIGEST files and compares it to the value in PACKAGE_DIGEST.
*
* @param package_data Pointer to the metadata component data
* @param package_data_size Size of the metadata component data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_package_digest_memory(
const unsigned char* package_data,
size_t package_data_size,
void* build_module)
{
// Validate input parameters
if (!package_data || package_data_size == 0 || !build_module) {
dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_package_digest_memory");
return 1;
}
dpm_log(LOG_INFO, "Verifying package digest from in-memory data...");
// First, extract PACKAGE_DIGEST, CONTENTS_MANIFEST_DIGEST, and HOOKS_DIGEST from the metadata component
unsigned char* package_digest_data = nullptr;
size_t package_digest_size = 0;
unsigned char* contents_manifest_data = nullptr;
size_t contents_manifest_size = 0;
unsigned char* hooks_digest_data = nullptr;
size_t hooks_digest_size = 0;
// Get PACKAGE_DIGEST from the metadata component
int result = get_file_from_component(
package_data,
package_data_size,
"PACKAGE_DIGEST",
&package_digest_data,
&package_digest_size
);
if (result != 0 || !package_digest_data || package_digest_size == 0) {
dpm_log(LOG_ERROR, "Failed to extract PACKAGE_DIGEST from metadata component");
return 1;
}
// Get CONTENTS_MANIFEST_DIGEST from the metadata component
result = get_file_from_component(
package_data,
package_data_size,
"CONTENTS_MANIFEST_DIGEST",
&contents_manifest_data,
&contents_manifest_size
);
if (result != 0 || !contents_manifest_data || contents_manifest_size == 0) {
dpm_log(LOG_ERROR, "Failed to extract CONTENTS_MANIFEST_DIGEST from metadata component");
free(package_digest_data);
return 1;
}
// Get HOOKS_DIGEST from the metadata component
result = get_file_from_component(
package_data,
package_data_size,
"HOOKS_DIGEST",
&hooks_digest_data,
&hooks_digest_size
);
if (result != 0 || !hooks_digest_data || hooks_digest_size == 0) {
dpm_log(LOG_ERROR, "Failed to extract HOOKS_DIGEST from metadata component");
free(package_digest_data);
free(contents_manifest_data);
return 1;
}
// Convert binary data to strings using our utility function
std::string package_digest_str = binary_to_string(package_digest_data, package_digest_size);
std::string contents_manifest_str = binary_to_string(contents_manifest_data, contents_manifest_size);
std::string hooks_digest_str = binary_to_string(hooks_digest_data, hooks_digest_size);
// Check if any conversion failed
if (package_digest_str.empty() || contents_manifest_str.empty() || hooks_digest_str.empty()) {
dpm_log(LOG_ERROR, "Failed to convert binary data to strings");
free(package_digest_data);
free(contents_manifest_data);
free(hooks_digest_data);
return 1;
}
// Calculate checksums using the build module's functions through dpm_execute_symbol
std::string contents_manifest_checksum;
result = dpm_execute_symbol(build_module, "generate_string_checksum",
contents_manifest_str, &contents_manifest_checksum);
if (result != 0 || contents_manifest_checksum.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate checksum for contents manifest");
free(package_digest_data);
free(contents_manifest_data);
free(hooks_digest_data);
return 1;
}
std::string hooks_digest_checksum;
result = dpm_execute_symbol(build_module, "generate_string_checksum",
hooks_digest_str, &hooks_digest_checksum);
if (result != 0 || hooks_digest_checksum.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate checksum for hooks digest");
free(package_digest_data);
free(contents_manifest_data);
free(hooks_digest_data);
return 1;
}
// Combine checksums and calculate package digest
std::string combined_checksums = contents_manifest_checksum + hooks_digest_checksum;
std::string calculated_package_digest;
result = dpm_execute_symbol(build_module, "generate_string_checksum",
combined_checksums, &calculated_package_digest);
if (result != 0 || calculated_package_digest.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate package digest");
free(package_digest_data);
free(contents_manifest_data);
free(hooks_digest_data);
return 1;
}
// Compare with the stored package digest
bool match = (calculated_package_digest == package_digest_str);
// Clean up
free(package_digest_data);
free(contents_manifest_data);
free(hooks_digest_data);
if (!match) {
dpm_log(LOG_ERROR, ("Package digest mismatch\n Expected: " + package_digest_str +
"\n Actual: " + calculated_package_digest).c_str());
return 1;
}
dpm_log(LOG_INFO, "Package digest verification successful");
return 0;
}
/**
* @brief Verifies the contents manifest digest from in-memory data
*
* Compares checksums in the contents manifest with actual file checksums
* using in-memory data rather than extracting files to disk.
*
* @param contents_data Pointer to the contents component data
* @param contents_data_size Size of the contents component data
* @param metadata_data Pointer to the metadata component data
* @param metadata_data_size Size of the metadata component data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_contents_digest_memory(
const unsigned char* contents_data,
size_t contents_data_size,
const unsigned char* metadata_data,
size_t metadata_data_size,
void* build_module)
{
// Validate input parameters
if (!contents_data || contents_data_size == 0 ||
!metadata_data || metadata_data_size == 0 || !build_module) {
dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_contents_digest_memory");
return 1;
}
dpm_log(LOG_INFO, "Verifying contents manifest digest from in-memory data...");
// Extract CONTENTS_MANIFEST_DIGEST from the metadata component
unsigned char* manifest_data = nullptr;
size_t manifest_size = 0;
int result = get_file_from_component(
metadata_data,
metadata_data_size,
"CONTENTS_MANIFEST_DIGEST",
&manifest_data,
&manifest_size
);
if (result != 0 || !manifest_data || manifest_size == 0) {
dpm_log(LOG_ERROR, "Failed to extract CONTENTS_MANIFEST_DIGEST from metadata component");
return 1;
}
// Convert binary data to string
std::string manifest_str = binary_to_string(manifest_data, manifest_size);
if (manifest_str.empty()) {
dpm_log(LOG_ERROR, "Failed to convert manifest data to string");
free(manifest_data);
return 1;
}
// Parse the manifest lines
std::istringstream manifest_stream(manifest_str);
std::string line;
int errors = 0;
int line_number = 0;
// Process each line in the manifest
while (std::getline(manifest_stream, line)) {
line_number++;
// Skip empty lines
if (line.empty()) {
continue;
}
// Parse the line into its components
std::istringstream iss(line);
char control_designation;
std::string expected_checksum, permissions, ownership, file_path;
// Extract components (C checksum permissions owner:group /path/to/file)
if (!(iss >> control_designation >> expected_checksum >> permissions >> ownership)) {
dpm_log(LOG_WARN, ("Malformed manifest line " + std::to_string(line_number) +
": " + line).c_str());
continue;
}
// Get the rest of the line as the file path
std::getline(iss >> std::ws, file_path);
if (file_path.empty()) {
dpm_log(LOG_WARN, ("Missing file path in manifest line " +
std::to_string(line_number)).c_str());
continue;
}
// Remove leading slash if present
if (file_path[0] == '/') {
file_path = file_path.substr(1);
}
// Extract the file from the contents component
unsigned char* file_data = nullptr;
size_t file_size = 0;
result = get_file_from_component(
contents_data,
contents_data_size,
file_path,
&file_data,
&file_size
);
if (result != 0 || !file_data) {
dpm_log(LOG_ERROR, ("Failed to extract file from contents: " + file_path).c_str());
errors++;
continue;
}
// Calculate the checksum of the file data
std::string calculated_checksum;
result = dpm_execute_symbol(build_module, "generate_string_checksum",
std::string(reinterpret_cast<char*>(file_data), file_size),
&calculated_checksum);
// Free the file data now that we're done with it
free(file_data);
if (result != 0 || calculated_checksum.empty()) {
dpm_log(LOG_ERROR, ("Failed to calculate checksum for file: " + file_path).c_str());
errors++;
continue;
}
// Compare with the expected checksum
if (calculated_checksum != expected_checksum) {
dpm_log(LOG_ERROR, ("Checksum mismatch for " + file_path +
"\n Expected: " + expected_checksum +
"\n Actual: " + calculated_checksum).c_str());
errors++;
}
}
// Clean up
free(manifest_data);
if (errors > 0) {
dpm_log(LOG_ERROR, (std::to_string(errors) + " checksum errors found in contents manifest").c_str());
return 1;
}
dpm_log(LOG_INFO, "Contents manifest checksum verification successful");
return 0;
}
/**
* @brief Verifies the hooks digest from in-memory data
*
* Calculates the digest of the hooks archive and compares it with the
* value stored in HOOKS_DIGEST metadata file.
*
* @param hooks_data Pointer to the hooks component data
* @param hooks_data_size Size of the hooks component data
* @param metadata_data Pointer to the metadata component data
* @param metadata_data_size Size of the metadata component data
* @param build_module Handle to the loaded build module
* @return 0 on successful verification, non-zero on failure
*/
int checksum_verify_hooks_digest_memory(
const unsigned char* hooks_data,
size_t hooks_data_size,
const unsigned char* metadata_data,
size_t metadata_data_size,
void* build_module)
{
// Validate input parameters
if (!hooks_data || hooks_data_size == 0 ||
!metadata_data || metadata_data_size == 0 || !build_module) {
dpm_log(LOG_ERROR, "Invalid parameters passed to checksum_verify_hooks_digest_memory");
return 1;
}
dpm_log(LOG_INFO, "Verifying hooks digest from in-memory data...");
// Extract HOOKS_DIGEST from the metadata component
unsigned char* hooks_digest_data = nullptr;
size_t hooks_digest_size = 0;
int result = get_file_from_component(
metadata_data,
metadata_data_size,
"HOOKS_DIGEST",
&hooks_digest_data,
&hooks_digest_size
);
if (result != 0 || !hooks_digest_data || hooks_digest_size == 0) {
dpm_log(LOG_ERROR, "Failed to extract HOOKS_DIGEST from metadata component");
return 1;
}
// Convert binary data to string
std::string stored_hooks_digest = binary_to_string(hooks_digest_data, hooks_digest_size);
if (stored_hooks_digest.empty()) {
dpm_log(LOG_ERROR, "Failed to convert hooks digest data to string");
free(hooks_digest_data);
return 1;
}
// Trim whitespace and newlines
stored_hooks_digest = stored_hooks_digest.substr(0, stored_hooks_digest.find_first_of("\r\n"));
// Calculate the checksum for the hooks archive data
std::string calculated_hooks_digest;
result = dpm_execute_symbol(build_module, "generate_string_checksum",
std::string(reinterpret_cast<const char*>(hooks_data), hooks_data_size),
&calculated_hooks_digest);
// Clean up
free(hooks_digest_data);
if (result != 0 || calculated_hooks_digest.empty()) {
dpm_log(LOG_ERROR, "Failed to calculate hooks digest");
return 1;
}
// Compare with the stored digest
if (calculated_hooks_digest != stored_hooks_digest) {
dpm_log(LOG_ERROR, ("Hooks digest mismatch\n Expected: " + stored_hooks_digest +
"\n Actual: " + calculated_hooks_digest).c_str());
return 1;
}
dpm_log(LOG_INFO, "Hooks digest verification successful");
return 0;
}

View File

@@ -0,0 +1,22 @@
#include "../include/cli_parsers.hpp"
Command parse_command(const char* cmd_str) {
if (cmd_str == nullptr || strlen(cmd_str) == 0) {
return CMD_HELP;
}
if (strcmp(cmd_str, "help") == 0) {
return CMD_HELP;
}
else if (strcmp(cmd_str, "checksum") == 0) {
return CMD_CHECKSUM;
}
else if (strcmp(cmd_str, "signature") == 0) {
return CMD_SIGNATURE;
}
else if (strcmp(cmd_str, "check") == 0) {
return CMD_CHECK;
}
return CMD_UNKNOWN;
}

View File

@@ -0,0 +1,399 @@
/**
* @file commands.cpp
* @brief Implementation of command handlers for the verify module
*
* Implements the command handlers for verifying package checksums and signatures.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "commands.hpp"
int check_and_load_build_module(void*& module_handle) {
// Check if build module exists
if (!dpm_module_exists("build")) {
dpm_log(LOG_ERROR, "Build module not found");
return 1;
}
// Load the build module
int result = dpm_load_module("build", &module_handle);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to load build module");
return result;
}
return 0;
}
int cmd_checksum_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm verify checksum [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Verifies the checksums of packages or package stage directories.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -p, --package PATH Path to a package file (.dpm)");
dpm_con(LOG_INFO, " -s, --stage DIR Path to a package stage directory");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Note: --package and --stage are mutually exclusive options.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " dpm verify checksum --package=mypackage-1.0.x86_64.dpm");
dpm_con(LOG_INFO, " dpm verify checksum --stage=./mypackage-1.0.x86_64");
return 0;
}
int cmd_checksum(int argc, char** argv) {
// Parse command line arguments
std::string package_path = "";
std::string stage_dir = "";
bool verbose = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-p" || arg == "--package") {
if (i + 1 < argc) {
package_path = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-s" || arg == "--stage") {
if (i + 1 < argc) {
stage_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_checksum_help(argc, argv);
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Validate that either package_path or stage_dir is provided, but not both
if (package_path.empty() && stage_dir.empty()) {
dpm_con(LOG_ERROR, "Either --package or --stage must be specified");
return cmd_checksum_help(argc, argv);
}
if (!package_path.empty() && !stage_dir.empty()) {
dpm_con(LOG_ERROR, "Cannot specify both --package and --stage");
return cmd_checksum_help(argc, argv);
}
// Call the appropriate verification function
if (!package_path.empty()) {
return verify_checksums_package(package_path);
} else {
return verify_checksums_stage(stage_dir);
}
}
int cmd_signature_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm verify signature [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Verifies the signatures of packages or package stage directories.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -p, --package PATH Path to a package file (.dpm)");
dpm_con(LOG_INFO, " -s, --stage DIR Path to a package stage directory");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Note: --package and --stage are mutually exclusive options.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Examples:");
dpm_con(LOG_INFO, " dpm verify signature --package=mypackage-1.0.x86_64.dpm");
dpm_con(LOG_INFO, " dpm verify signature --stage=./mypackage-1.0.x86_64");
return 0;
}
int cmd_signature(int argc, char** argv) {
// Parse command line arguments
std::string package_path = "";
std::string stage_dir = "";
bool verbose = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-p" || arg == "--package") {
if (i + 1 < argc) {
package_path = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-s" || arg == "--stage") {
if (i + 1 < argc) {
stage_dir = argv[i + 1];
i++; // Skip the next argument
}
} else if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_signature_help(argc, argv);
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
// Validate that either package_path or stage_dir is provided, but not both
if (package_path.empty() && stage_dir.empty()) {
dpm_con(LOG_ERROR, "Either --package or --stage must be specified");
return cmd_signature_help(argc, argv);
}
if (!package_path.empty() && !stage_dir.empty()) {
dpm_con(LOG_ERROR, "Cannot specify both --package and --stage");
return cmd_signature_help(argc, argv);
}
// Call the appropriate verification function
if (!package_path.empty()) {
return verify_signature_package(package_path);
} else {
return verify_signature_stage(stage_dir);
}
}
int cmd_check_help(int argc, char** argv) {
dpm_con(LOG_INFO, "Usage: dpm verify check [options]");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Checks build module integration.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Options:");
dpm_con(LOG_INFO, " -v, --verbose Enable verbose output");
dpm_con(LOG_INFO, " -h, --help Display this help message");
return 0;
}
int cmd_help(int argc, char** argv) {
dpm_con(LOG_INFO, "DPM Verify Module - Verifies the integrity and signatures of package files and stage directories.");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Available commands:");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, " checksum - Verify checksums of package files or stage directories");
dpm_con(LOG_INFO, " signature - Verify signatures of package files or stage directories");
dpm_con(LOG_INFO, " check - Check build module integration");
dpm_con(LOG_INFO, " help - Display this help message");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "Usage: dpm verify <command>");
dpm_con(LOG_INFO, "");
dpm_con(LOG_INFO, "For command-specific help, use: dpm verify <command> --help");
return 0;
}
int cmd_unknown(const char* command, int argc, char** argv) {
std::string msg = "Unknown command: ";
msg += (command ? command : "");
dpm_con(LOG_WARN, msg.c_str());
dpm_con(LOG_WARN, "Run 'dpm verify help' for a list of available commands");
return 1;
}
int cmd_check(int argc, char** argv) {
// Parse command line arguments
bool verbose = false;
bool show_help = false;
// Process command-line arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-v" || arg == "--verbose") {
verbose = true;
} else if (arg == "-h" || arg == "--help" || arg == "help") {
show_help = true;
}
}
// If help was requested, show it and return
if (show_help) {
return cmd_check_help(argc, argv);
}
// Set verbose logging if requested
if (verbose) {
dpm_set_logging_level(LOG_DEBUG);
}
dpm_log(LOG_INFO, "Checking build module integration...");
void* module_handle = nullptr;
int result = check_and_load_build_module(module_handle);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to load build module.");
return 1;
}
dpm_log(LOG_INFO, "Successfully loaded build module.");
// Check if the dpm_module_execute symbol exists
if (!dpm_symbol_exists(module_handle, "dpm_module_execute")) {
dpm_log(LOG_ERROR, "Symbol 'dpm_module_execute' not found in build module.");
dpm_unload_module(module_handle);
return 1;
}
dpm_log(LOG_INFO, "Symbol 'dpm_module_execute' found in build module.");
// Clean up
if (module_handle) {
dpm_unload_module(module_handle);
}
return 0;
}
/**
* @brief Verifies checksums of a package file in memory
*
* Loads the components of a package file into memory and verifies their checksums
* without extracting them to disk.
*
* @param package_path Path to the package file
* @return 0 on success, non-zero on failure
*/
int verify_checksums_package_memory(const std::string& package_path) {
// Check if the package file exists
if (!std::filesystem::exists(package_path)) {
dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str());
return 1;
}
dpm_log(LOG_INFO, ("Verifying checksums for package in memory: " + package_path).c_str());
// Load the build module
void* build_module = nullptr;
int result = check_and_load_build_module(build_module);
if (result != 0 || build_module == nullptr) {
dpm_log(LOG_ERROR, "Failed to load build module");
return 1;
}
// Extract package components into memory
unsigned char* metadata_data = nullptr;
size_t metadata_data_size = 0;
unsigned char* contents_data = nullptr;
size_t contents_data_size = 0;
unsigned char* hooks_data = nullptr;
size_t hooks_data_size = 0;
// Load metadata component
dpm_log(LOG_INFO, "Loading metadata component...");
result = get_component_from_package(package_path, "metadata", &metadata_data, &metadata_data_size);
if (result != 0 || !metadata_data || metadata_data_size == 0) {
dpm_log(LOG_ERROR, "Failed to load metadata component");
dpm_unload_module(build_module);
return 1;
}
// Load contents component
dpm_log(LOG_INFO, "Loading contents component...");
result = get_component_from_package(package_path, "contents", &contents_data, &contents_data_size);
if (result != 0 || !contents_data || contents_data_size == 0) {
dpm_log(LOG_ERROR, "Failed to load contents component");
free(metadata_data);
dpm_unload_module(build_module);
return 1;
}
// Load hooks component
dpm_log(LOG_INFO, "Loading hooks component...");
result = get_component_from_package(package_path, "hooks", &hooks_data, &hooks_data_size);
if (result != 0 || !hooks_data || hooks_data_size == 0) {
dpm_log(LOG_ERROR, "Failed to load hooks component");
free(metadata_data);
free(contents_data);
dpm_unload_module(build_module);
return 1;
}
// Verify package digest
dpm_log(LOG_INFO, "Verifying package digest...");
result = checksum_verify_package_digest_memory(
metadata_data,
metadata_data_size,
build_module
);
if (result != 0) {
dpm_log(LOG_ERROR, "Package digest verification failed");
free(metadata_data);
free(contents_data);
free(hooks_data);
dpm_unload_module(build_module);
return 1;
}
// Verify contents manifest digest
dpm_log(LOG_INFO, "Verifying contents manifest digest...");
result = checksum_verify_contents_digest_memory(
contents_data,
contents_data_size,
metadata_data,
metadata_data_size,
build_module
);
if (result != 0) {
dpm_log(LOG_ERROR, "Contents manifest verification failed");
free(metadata_data);
free(contents_data);
free(hooks_data);
dpm_unload_module(build_module);
return 1;
}
// Verify hooks digest
dpm_log(LOG_INFO, "Verifying hooks digest...");
result = checksum_verify_hooks_digest_memory(
hooks_data,
hooks_data_size,
metadata_data,
metadata_data_size,
build_module
);
if (result != 0) {
dpm_log(LOG_ERROR, "Hooks digest verification failed");
free(metadata_data);
free(contents_data);
free(hooks_data);
dpm_unload_module(build_module);
return 1;
}
// Clean up
free(metadata_data);
free(contents_data);
free(hooks_data);
dpm_unload_module(build_module);
dpm_log(LOG_INFO, "All in-memory checksums verified successfully");
return 0;
}

View File

@@ -0,0 +1,125 @@
/**
* @file package_operations.cpp
* @brief Implementation of package operation functions
*
* Implements functions for extracting and verifying components from DPM packages.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "package_operations.hpp"
int get_component_from_package(const std::string& package_path,
const std::string& component_name,
unsigned char** data,
size_t* data_size)
{
// Validate input parameters
if (package_path.empty() || component_name.empty() || !data || !data_size) {
dpm_log(LOG_ERROR, "Invalid parameters passed to get_component_from_package");
return 1;
}
// Initialize output parameters
*data = nullptr;
*data_size = 0;
// Check if the package file exists
if (!std::filesystem::exists(package_path)) {
dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str());
return 1;
}
// Load the build module
void* build_module = nullptr;
int result = check_and_load_build_module(build_module);
if (result != 0 || build_module == nullptr) {
dpm_log(LOG_ERROR, "Failed to load build module");
return 1;
}
dpm_log(LOG_DEBUG, ("Extracting " + component_name + " from package: " + package_path).c_str());
// Call the function from the build module
bool success = dpm_execute_symbol(build_module, "get_file_from_package_file",
package_path.c_str(), component_name.c_str(),
data, data_size);
// Unload the build module
dpm_unload_module(build_module);
// Check if the function call was successful
if (!success || *data == nullptr || *data_size == 0) {
dpm_log(LOG_ERROR, ("Failed to extract " + component_name + " from package").c_str());
return 1;
}
dpm_log(LOG_DEBUG, ("Successfully extracted " + component_name + " (" +
std::to_string(*data_size) + " bytes)").c_str());
return 0;
}
/**
* @brief Extracts a file from a component archive
*
* Extracts a specific file from a component archive that has already been loaded into memory.
* Uses the build module's get_file_from_memory_loaded_archive function.
*
* @param component_data Pointer to the component archive data in memory
* @param component_size Size of the component archive in memory
* @param filename Name of the file to extract from the component
* @param data Pointer to a pointer that will be populated with the file data
* @param data_size Pointer to a size_t that will be populated with the size of the file data
* @return 0 on success, non-zero on failure
*/
int get_file_from_component(const unsigned char* component_data,
size_t component_size,
const std::string& filename,
unsigned char** data,
size_t* data_size)
{
// Validate input parameters
if (!component_data || component_size == 0 || filename.empty() || !data || !data_size) {
dpm_log(LOG_ERROR, "Invalid parameters passed to get_file_from_component");
return 1;
}
// Initialize output parameters
*data = nullptr;
*data_size = 0;
// Load the build module
void* build_module = nullptr;
int result = check_and_load_build_module(build_module);
if (result != 0 || build_module == nullptr) {
dpm_log(LOG_ERROR, "Failed to load build module");
return 1;
}
dpm_log(LOG_DEBUG, ("Extracting file '" + filename + "' from component archive").c_str());
// Call the function from the build module
bool success = dpm_execute_symbol(build_module, "get_file_from_memory_loaded_archive",
component_data, component_size,
filename.c_str(),
data, data_size);
// Unload the build module
dpm_unload_module(build_module);
// Check if the function call was successful
if (!success || *data == nullptr || *data_size == 0) {
dpm_log(LOG_ERROR, ("Failed to extract file '" + filename + "' from component archive").c_str());
return 1;
}
dpm_log(LOG_DEBUG, ("Successfully extracted file '" + filename + "' (" +
std::to_string(*data_size) + " bytes)").c_str());
return 0;
}

View File

@@ -0,0 +1,212 @@
/**
* @file verification.cpp
* @brief Implementation of package verification functions
*
* Implements functions for verifying checksums and signatures of DPM packages
* and package stage directories.
*
* @copyright Copyright (c) 2025 SILO GROUP LLC
* @author Chris Punches <chris.punches@silogroup.org>
*
* Part of the Dark Horse Linux Package Manager (DPM)
*/
#include "verification.hpp"
int verify_checksums_package(const std::string& package_path) {
// Check if the package file exists
if (!std::filesystem::exists(package_path)) {
dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str());
return 1;
}
dpm_log(LOG_INFO, ("Verifying checksums for package: " + package_path).c_str());
// Load the build module to access functions
void* build_module = nullptr;
int result = check_and_load_build_module(build_module);
if (result != 0 || build_module == nullptr) {
dpm_log(LOG_ERROR, "Failed to load build module");
return 1;
}
// Create a temporary directory for extraction
std::filesystem::path temp_dir = std::filesystem::temp_directory_path() / "dpm_verify_tmp";
// Remove temp directory if it already exists
if (std::filesystem::exists(temp_dir)) {
try {
std::filesystem::remove_all(temp_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to clean up existing temp directory: " + std::string(e.what())).c_str());
dpm_unload_module(build_module);
return 1;
}
}
// Create the temp directory
try {
std::filesystem::create_directory(temp_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_ERROR, ("Failed to create temp directory: " + std::string(e.what())).c_str());
dpm_unload_module(build_module);
return 1;
}
// Unseal the package to the temp directory using dpm_execute_symbol
dpm_log(LOG_INFO, "Unsealing package to temporary directory for verification...");
std::string output_dir = temp_dir.string();
bool force = true; // Force overwrite if directory exists
result = dpm_execute_symbol(build_module, "unseal_package", package_path, output_dir, force);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to unseal package for verification");
dpm_unload_module(build_module);
// Clean up temp directory
try {
std::filesystem::remove_all(temp_dir);
} catch (const std::filesystem::filesystem_error&) {
// Ignore cleanup errors
}
return 1;
}
// Get the extracted stage directory name (package name without .dpm)
std::string package_filename = std::filesystem::path(package_path).filename().string();
std::string stage_name = package_filename;
// Remove .dpm extension if present
const std::string dpm_extension = ".dpm";
if (stage_name.ends_with(dpm_extension)) {
stage_name = stage_name.substr(0, stage_name.length() - dpm_extension.length());
}
std::filesystem::path stage_dir = temp_dir / stage_name;
// Verify checksums in the extracted stage directory
dpm_log(LOG_INFO, ("Verifying checksums in extracted stage: " + stage_dir.string()).c_str());
// Now verify the stage directory checksums
result = verify_checksums_stage(stage_dir.string());
// Clean up temp directory
dpm_log(LOG_INFO, "Cleaning up temporary extraction directory...");
try {
std::filesystem::remove_all(temp_dir);
} catch (const std::filesystem::filesystem_error& e) {
dpm_log(LOG_WARN, ("Failed to clean up temp directory: " + std::string(e.what())).c_str());
// Continue execution - this is just a cleanup warning
}
// Clean up module handle
dpm_unload_module(build_module);
if (result == 0) {
dpm_log(LOG_INFO, "Package checksum verification completed successfully");
} else {
dpm_log(LOG_ERROR, "Package checksum verification failed");
}
return result;
}
int verify_checksums_stage(const std::string& stage_dir) {
// Check if the stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_log(LOG_ERROR, ("Stage directory not found: " + stage_dir).c_str());
return 1;
}
// Check if it's actually a directory
if (!std::filesystem::is_directory(stage_dir)) {
dpm_log(LOG_ERROR, ("Path is not a directory: " + stage_dir).c_str());
return 1;
}
dpm_log(LOG_INFO, ("Verifying checksums for stage directory: " + stage_dir).c_str());
// First, ensure the components are unsealed (uncompressed)
dpm_log(LOG_INFO, "Ensuring stage components are unsealed...");
// Load the build module to access functions
void* build_module = nullptr;
int result = check_and_load_build_module(build_module);
if (result != 0 || build_module == nullptr) {
dpm_log(LOG_ERROR, "Failed to load build module");
return 1;
}
// Use dpm_execute_symbol to call the unseal_stage_components function
std::filesystem::path stage_path(stage_dir);
result = dpm_execute_symbol(build_module, "unseal_stage_components", stage_path);
if (result != 0) {
dpm_log(LOG_ERROR, "Failed to unseal stage components");
dpm_unload_module(build_module);
return 1;
}
// Verify checksums
result = checksum_verify_package_digest(stage_dir, build_module);
if (result != 0) {
dpm_log(LOG_ERROR, "Package digest verification failed");
dpm_unload_module(build_module);
return 1;
}
result = checksum_verify_contents_digest(stage_dir, build_module);
if (result != 0) {
dpm_log(LOG_ERROR, "Contents manifest verification failed");
dpm_unload_module(build_module);
return 1;
}
result = checksum_verify_hooks_digest(stage_dir, build_module);
if (result != 0) {
dpm_log(LOG_ERROR, "Hooks digest verification failed");
dpm_unload_module(build_module);
return 1;
}
// Clean up
dpm_unload_module(build_module);
dpm_log(LOG_INFO, "All checksums verified successfully");
return 0;
}
int verify_signature_package(const std::string& package_path) {
// Check if the package file exists
if (!std::filesystem::exists(package_path)) {
dpm_log(LOG_ERROR, ("Package file not found: " + package_path).c_str());
return 1;
}
// Placeholder implementation
dpm_log(LOG_INFO, ("Verifying signatures for package: " + package_path).c_str());
dpm_log(LOG_INFO, "Package signature verification not yet implemented");
return 0;
}
int verify_signature_stage(const std::string& stage_dir) {
// Check if the stage directory exists
if (!std::filesystem::exists(stage_dir)) {
dpm_log(LOG_ERROR, ("Stage directory not found: " + stage_dir).c_str());
return 1;
}
// Check if it's actually a directory
if (!std::filesystem::is_directory(stage_dir)) {
dpm_log(LOG_ERROR, ("Path is not a directory: " + stage_dir).c_str());
return 1;
}
// Placeholder implementation
dpm_log(LOG_INFO, ("Verifying signatures for stage directory: " + stage_dir).c_str());
dpm_log(LOG_INFO, "Stage directory signature verification not yet implemented");
return 0;
}

104
modules/verify/verify.cpp Normal file
View File

@@ -0,0 +1,104 @@
/**
* @file verify.cpp
* @brief DPM verify module implementation
*
* Implements a DPM module that verifies the integrity and signatures
* of installed packages.
*
* @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 "include/commands.hpp"
#include "include/cli_parsers.hpp"
/**
* @def MODULE_VERSION
* @brief Version information for the verify module
*
* Defines the version string that will be returned by dpm_module_get_version()
*/
#define MODULE_VERSION "0.1.0"
/**
* @brief Returns the module version string
*
* Required implementation of the DPM module interface that provides
* version information for the verify module.
*
* @return Const char pointer to the module version string
*/
extern "C" const char* dpm_module_get_version(void) {
return MODULE_VERSION;
}
/**
* @brief Returns the module description string
*
* Required implementation of the DPM module interface that provides
* a human-readable description of the verify module and its functionality.
*
* @return Const char pointer to the module description string
*/
extern "C" const char* dpm_get_description(void) {
return "Verifies the integrity and signatures of installed packages.";
}
/**
* @brief Main entry point for the verify module
*
* Required implementation of the DPM module interface that serves as the
* primary execution point for the module. Parses the command and routes
* execution to the appropriate handler function.
*
* @param command The command string to execute
* @param argc Number of arguments
* @param argv Array of argument strings
* @return 0 on success, non-zero on failure
*/
extern "C" int dpm_module_execute(const char* command, int argc, char** argv) {
// Parse the command
Command cmd = parse_command(command);
// Route to the appropriate command handler
switch (cmd) {
case CMD_CHECKSUM:
return cmd_checksum(argc, argv);
case CMD_SIGNATURE:
return cmd_signature(argc, argv);
case CMD_CHECK:
return cmd_check(argc, argv);
case CMD_HELP:
return cmd_help(argc, argv);
case CMD_UNKNOWN:
default:
return cmd_unknown(command, argc, argv);
}
}
// If we're building in standalone mode, include the main function
#ifdef BUILD_STANDALONE
DPM_MODULE_STANDALONE_MAIN()
#endif // BUILD_STANDALONE

View File

@@ -320,3 +320,11 @@ bool ConfigManager::getConfigBool(const char* section, const char* key, bool def
// If not recognized, return default // If not recognized, return default
return defaultValue; return defaultValue;
} }
void ConfigManager::setModulePath(const char * module_path) {
_module_path = module_path;
}
const char * ConfigManager::getModulePath() const {
return _module_path.c_str();
}

View File

@@ -1,4 +1,3 @@
// Logger.cpp
#include "Logger.hpp" #include "Logger.hpp"
// Global logger instance // Global logger instance
@@ -28,12 +27,16 @@ void Logger::setLogFile(const std::string& new_log_file)
if (!log_dir.empty() && !std::filesystem::exists(log_dir)) { if (!log_dir.empty() && !std::filesystem::exists(log_dir)) {
try { try {
if (!std::filesystem::create_directories(log_dir)) { if (!std::filesystem::create_directories(log_dir)) {
std::cerr << "FATAL: Failed to create log directory: " << log_dir.string() << std::endl; std::cerr << "Warning: Failed to create log directory: " << log_dir.string() << std::endl;
exit(1); // Continue execution, just disable file logging
log_to_file = false;
return;
} }
} catch (const std::filesystem::filesystem_error& e) { } catch (const std::filesystem::filesystem_error& e) {
std::cerr << "FATAL: Error creating log directory: " << e.what() << std::endl; std::cerr << "Warning: Error creating log directory: " << e.what() << std::endl;
exit(1); // Continue execution, just disable file logging
log_to_file = false;
return;
} }
} }
@@ -41,13 +44,17 @@ void Logger::setLogFile(const std::string& new_log_file)
try { try {
std::ofstream test_log_file(log_file, std::ios::app); std::ofstream test_log_file(log_file, std::ios::app);
if (!test_log_file.is_open()) { if (!test_log_file.is_open()) {
std::cerr << "FATAL: Cannot open log file for writing: " << log_file << std::endl; std::cerr << "Warning: Cannot open log file for writing: " << log_file << std::endl;
exit(1); // Continue execution, just disable file logging
log_to_file = false;
return;
} }
test_log_file.close(); test_log_file.close();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "FATAL: Error validating log file access: " << e.what() << std::endl; std::cerr << "Warning: Error validating log file access: " << e.what() << std::endl;
exit(1); // Continue execution, just disable file logging
log_to_file = false;
return;
} }
} }
} }
@@ -55,11 +62,6 @@ void Logger::setLogFile(const std::string& new_log_file)
void Logger::setWriteToLog(bool new_write_to_log) void Logger::setWriteToLog(bool new_write_to_log)
{ {
log_to_file = new_write_to_log; log_to_file = new_write_to_log;
// If logging was just enabled, validate the log file
if (log_to_file) {
setLogFile(log_file);
}
} }
void Logger::setLogLevel(LoggingLevels new_log_level) void Logger::setLogLevel(LoggingLevels new_log_level)
@@ -137,11 +139,41 @@ void Logger::log(LoggingLevels message_level, const std::string& message)
log_stream << formatted_message << std::endl; log_stream << formatted_message << std::endl;
log_stream.close(); log_stream.close();
} else { } else {
std::cerr << "Failed to write to log file: " << log_file << std::endl; // Just log to console, don't error out the program just for log file issues
if (message_level != LoggingLevels::FATAL && message_level != LoggingLevels::ERROR) {
std::cerr << "Warning: Failed to write to log file: " << log_file << std::endl;
}
// Disable file logging for future messages
log_to_file = false;
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Error writing to log file: " << e.what() << std::endl; // Just log to console, don't error out the program just for log file issues
if (message_level != LoggingLevels::FATAL && message_level != LoggingLevels::ERROR) {
std::cerr << "Warning: Error writing to log file: " << e.what() << std::endl;
}
// Disable file logging for future messages
log_to_file = false;
} }
} }
} }
} }
void Logger::log_console(LoggingLevels level, const std::string& message)
{
// Only process if the message level is less than or equal to the configured level
if (level <= log_level) {
// Convert log level to string
std::string level_str = LogLevelToString(level);
// Console output without timestamp
if (level == LoggingLevels::FATAL ||
level == LoggingLevels::ERROR ||
level == LoggingLevels::WARN) {
// Send to stderr
std::cerr << level_str << ": " << message << std::endl;
} else {
// Send to stdout
std::cout << message << std::endl;
}
}
}

View File

@@ -37,6 +37,8 @@
#include "error.hpp" #include "error.hpp"
#include "ConfigManager.hpp" #include "ConfigManager.hpp"
#include "Logger.hpp" #include "Logger.hpp"
#include "LoggingLevels.hpp"
#include "module_interface.hpp"
/* /*
* DPM serves three functions: * DPM serves three functions:
@@ -80,11 +82,11 @@ int main( int argc, char* argv[] )
} }
// Load configuration files // Load configuration files
if ( !g_config_manager.loadConfigurations() ) bool config_loaded = g_config_manager.loadConfigurations();
if (!config_loaded)
{ {
// failed to load any configuration files, so alert the user // failed to load any configuration files, so alert the user
std::cerr << "Warning: No configuration files present or loaded from '" dpm_con( ERROR, ("Warning: No configuration files present or loaded from '" + g_config_manager.getConfigDir() + "*.conf', reverting to defaults.").c_str());
<< g_config_manager.getConfigDir() << "*.conf', reverting to defaults." << std::endl;
} }
// Configure logger (CLI args > config > defaults) // Configure logger (CLI args > config > defaults)
@@ -101,17 +103,58 @@ int main( int argc, char* argv[] )
g_logger.setWriteToLog(config_write_to_log); g_logger.setWriteToLog(config_write_to_log);
g_logger.setLogFile(config_log_file); g_logger.setLogFile(config_log_file);
// If help is requested, show it and exit - handle this early before any logging is needed
if (args.show_help) {
return main_show_help();
}
// If list modules is requested, handle it early too
if (args.list_modules) {
// Determine the module path (CLI arg > config > default)
std::string module_path;
// If CLI argument was provided, use it
if (!args.module_path.empty())
{
module_path = args.module_path;
} else {
// Otherwise, check configuration file
const char* config_module_path = g_config_manager.getConfigValue("modules", "module_path");
if (config_module_path)
{
module_path = config_module_path;
} else {
// use default if nothing else is available
module_path = DPMDefaults::MODULE_PATH;
}
}
// create a module loader object with the determined path
ModuleLoader loader(module_path);
// check the module path for the loader object
int path_check_result = main_check_module_path(loader);
if (path_check_result != 0)
{
// exit if there's an error and ensure
// it has an appropriate return code
return path_check_result;
}
return main_list_modules(loader);
}
// Determine the module path (CLI arg > config > default) // Determine the module path (CLI arg > config > default)
std::string module_path; std::string module_path;
// If CLI argument was provided, use it // If CLI argument was provided, use it
if ( !args.module_path.empty() ) if (!args.module_path.empty())
{ {
module_path = args.module_path; module_path = args.module_path;
} else { } else {
// Otherwise, check configuration file // Otherwise, check configuration file
const char * config_module_path = g_config_manager.getConfigValue( "modules", "module_path" ); const char* config_module_path = g_config_manager.getConfigValue("modules", "module_path");
if ( config_module_path ) if (config_module_path)
{ {
module_path = config_module_path; module_path = config_module_path;
} else { } else {
@@ -120,28 +163,20 @@ int main( int argc, char* argv[] )
} }
} }
g_config_manager.setModulePath(module_path.c_str());
// create a module loader object with the determined path // create a module loader object with the determined path
ModuleLoader loader( module_path ); ModuleLoader loader(module_path);
// check the module path for the loader object // check the module path for the loader object
int path_check_result = main_check_module_path( loader ); int path_check_result = main_check_module_path(loader);
if ( path_check_result != 0 ) if (path_check_result != 0)
{ {
// exit if there's an error and ensure // exit if there's an error and ensure
// it has an appropriate return code // it has an appropriate return code
return path_check_result; return path_check_result;
} }
// If help is requested, show it and exit
if (args.show_help) {
return main_show_help();
}
// If list modules is requested, show the list and exit
if (args.list_modules) {
return main_list_modules(loader);
}
// if no module is provided to execute, then trigger the default // if no module is provided to execute, then trigger the default
// behaviour (show help) // behaviour (show help)
if (args.module_name.empty()) { if (args.module_name.empty()) {
@@ -149,7 +184,7 @@ int main( int argc, char* argv[] )
} }
// execute the module // execute the module
int return_code = main_execute_module( loader, args.module_name, args.command ); int return_code = main_execute_module(loader, args.module_name, args.command);
return return_code; return return_code;
} }

View File

@@ -30,7 +30,6 @@
#include "dpm_interface.hpp" #include "dpm_interface.hpp"
#include <handlers.hpp>
/* /*
* DPM Interface methods. * DPM Interface methods.
@@ -49,32 +48,30 @@ int main_check_module_path(const ModuleLoader& loader)
loader.get_module_path(path); loader.get_module_path(path);
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
g_logger.log(LoggingLevels::FATAL, "modules.modules_path does not exist: " + path); dpm_con(FATAL, ("modules.modules_path does not exist: " + path).c_str());
return 1; return 1;
} }
if (!std::filesystem::is_directory(path)) { if (!std::filesystem::is_directory(path)) {
g_logger.log(LoggingLevels::FATAL, "modules.modules_path is not a directory: " + path); dpm_con(FATAL, ("modules.modules_path is not a directory: " + path).c_str());
return 1; return 1;
} }
try { try {
auto perms = std::filesystem::status(path).permissions(); auto perms = std::filesystem::status(path).permissions();
if ((perms & std::filesystem::perms::owner_read) == std::filesystem::perms::none) { if ((perms & std::filesystem::perms::owner_read) == std::filesystem::perms::none) {
g_logger.log(LoggingLevels::FATAL, "Permission denied: " + path); dpm_con(FATAL, ("Permission denied: " + path).c_str());
return 1; return 1;
} }
} catch (const std::filesystem::filesystem_error&) { } catch (const std::filesystem::filesystem_error&) {
g_logger.log(LoggingLevels::FATAL, "Permission denied: " + path); dpm_con(FATAL, ("Permission denied: " + path).c_str());
return 1; return 1;
} }
return 0; return 0;
} }
int main_list_modules(const ModuleLoader& loader) {
int main_list_modules(const ModuleLoader& loader)
{
// initialize an empty modules list // initialize an empty modules list
std::vector<std::string> modules; std::vector<std::string> modules;
@@ -83,19 +80,19 @@ int main_list_modules(const ModuleLoader& loader)
// set the module path // set the module path
DPMErrorCategory get_path_error = loader.get_module_path(path); DPMErrorCategory get_path_error = loader.get_module_path(path);
if ( get_path_error != DPMErrorCategory::SUCCESS ) { if (get_path_error != DPMErrorCategory::SUCCESS) {
g_logger.log(LoggingLevels::FATAL, "Failed to get modules.modules_path"); dpm_con(LoggingLevels::FATAL, "Failed to get modules.modules_path");
return 1; return 1;
} }
DPMErrorCategory list_error = loader.list_available_modules(modules); DPMErrorCategory list_error = loader.list_available_modules(modules);
if (list_error != DPMErrorCategory::SUCCESS) { if (list_error != DPMErrorCategory::SUCCESS) {
g_logger.log(LoggingLevels::FATAL, "No modules found in modules.modules_path: " + path); dpm_con(LoggingLevels::FATAL, ("No modules found in modules.modules_path: " + path).c_str());
return 1; return 1;
} }
if (modules.empty()) { if (modules.empty()) {
g_logger.log(LoggingLevels::FATAL, "No modules found in modules.modules_path: '" + path + "'."); dpm_con(LoggingLevels::FATAL, ("No modules found in modules.modules_path: '" + path + "'.").c_str());
return 0; return 0;
} }
@@ -116,8 +113,8 @@ int main_list_modules(const ModuleLoader& loader)
dlclose(handle); dlclose(handle);
} }
if ( valid_modules.empty() ) { if (valid_modules.empty()) {
g_logger.log(LoggingLevels::FATAL, "No valid modules found in modules.modules_path: '" + path + "'."); dpm_con(LoggingLevels::FATAL, "No valid DPM commands available.");
return 0; return 0;
} }
@@ -154,9 +151,11 @@ int main_list_modules(const ModuleLoader& loader)
const int column_spacing = 4; const int column_spacing = 4;
// Print header with proper spacing
std::cout << "Available DPM commands:\n" << std::endl;
// Display the table header // Display the table header
g_logger.log(LoggingLevels::DEBUG, "\nAvailable modules in modules.modules_path: '" + path + "':\n"); std::cout << std::left << std::setw(max_name_length + column_spacing) << "COMMAND"
std::cout << std::left << std::setw(max_name_length + column_spacing) << "MODULE"
<< std::setw(max_version_length + column_spacing) << "VERSION" << std::setw(max_version_length + column_spacing) << "VERSION"
<< "DESCRIPTION" << std::endl; << "DESCRIPTION" << std::endl;
@@ -167,6 +166,11 @@ int main_list_modules(const ModuleLoader& loader)
<< descriptions[i] << std::endl; << descriptions[i] << std::endl;
} }
// Add a blank line before the usage note
std::cout << std::endl;
std::cout << "Use 'dpm <command> help' for detailed information about a specific command." << std::endl;
std::cout << std::endl;
return 0; return 0;
} }

View File

@@ -30,12 +30,13 @@
#include "handlers.hpp" #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) void validate_field(FlexDPMError context, const char* field_name, const void* field_value)
{ {
if (!field_value) { if (!field_value) {
std::cerr << "Error: Incomplete error context. Missing required field: " << field_name; std::string error_msg = "Error category " + std::to_string(static_cast<int>(context.error)) +
std::cerr << " (Error category: " << static_cast<int>(context.error) << ")" << std::endl; ": Incomplete error context. Missing required field: " + field_name;
dpm_log(FATAL, error_msg.c_str());
// Hard exit when a required field is missing // Hard exit when a required field is missing
exit(1); exit(1);
@@ -100,68 +101,78 @@ int handle_error(FlexDPMError context) {
} }
// Now the individual handlers can be simplified since required fields are guaranteed // Now the individual handlers can be simplified since required fields are guaranteed
int handle_path_not_found( FlexDPMError context ) { int handle_path_not_found(FlexDPMError context) {
std::cerr << "Fatal error: The module directory '" << context.module_path << "' was not found. Exiting." << std::endl; std::string error_msg = "Fatal error: The module directory '" + std::string(context.module_path) + "' was not found. Exiting.";
dpm_log(FATAL, error_msg.c_str());
return 1; return 1;
} }
int handle_path_not_directory( FlexDPMError context ) { int handle_path_not_directory(FlexDPMError context) {
std::cerr << "Fatal error: The module path '" << context.module_path << "' is not a directory. Exiting." << std::endl; std::string error_msg = "Fatal error: The module path '" + std::string(context.module_path) + "' is not a directory. Exiting.";
dpm_log(FATAL, error_msg.c_str());
return 1; return 1;
} }
int handle_path_too_long( FlexDPMError context ) { int handle_path_too_long(FlexDPMError context) {
std::cerr << "Error: Module path is too long: '" << context.module_path << "'. Exiting." << std::endl; std::string error_msg = "Error: Module path is too long: '" + std::string(context.module_path) + "'. Exiting.";
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_permission_denied( FlexDPMError context ) { int handle_permission_denied(FlexDPMError context) {
std::cerr << "Error: Permission denied accessing the modules path: '" << context.module_path << "'. Exiting." << std::endl; std::string error_msg = "Error: Permission denied accessing the modules path: '" + std::string(context.module_path) + "'. Exiting.";
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_module_not_found( FlexDPMError context ) { int handle_module_not_found(FlexDPMError context) {
std::cerr << "Error: Module '"<< context.module_name << "' not found in '" << context.module_path << "'. Exiting." << std::endl; std::string error_msg = "Error: Module '" + std::string(context.module_name) + "' not found in '" + std::string(context.module_path) + "'. Exiting.";
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_module_not_loaded( FlexDPMError context ) { int handle_module_not_loaded(FlexDPMError context) {
std::cerr << "Error: Attempted to execute module before loading it: " << context.module_name << std::endl; std::string error_msg = "Error: Attempted to execute module before loading it: " + std::string(context.module_name);
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_module_load_failed( FlexDPMError context ) { int handle_module_load_failed(FlexDPMError context) {
std::cerr << "Error: Failed to load module: " << context.module_name << std::endl; std::string error_msg = "Error: Failed to load module: " + std::string(context.module_name);
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_invalid_module( FlexDPMError context ) { int handle_invalid_module(FlexDPMError context) {
std::cerr << "Error: Invalid module format: " << context.module_name << std::endl; std::string error_msg = "Error: Invalid module format: " + std::string(context.module_name);
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_symbol_not_found( FlexDPMError context ) {
std::cerr << "Error: Symbol not found in module: " << context.module_name; int handle_symbol_not_found(FlexDPMError context) {
std::string error_msg = "Error: Symbol not found in module: " + std::string(context.module_name);
if (context.message) { if (context.message) {
std::cerr << " (" << context.message << ")"; error_msg += " (" + std::string(context.message) + ")";
} }
std::cerr << std::endl; dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_symbol_execution_failed(FlexDPMError context) { int handle_symbol_execution_failed(FlexDPMError context) {
std::cerr << "Error: Module execution failed: " << context.module_name << std::endl; std::string error_msg = "Error: Module execution failed: " + std::string(context.module_name);
dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }
int handle_undefined_error(FlexDPMError context) { int handle_undefined_error(FlexDPMError context) {
std::cerr << "Error: Undefined error occurred"; std::string error_msg = "Error: Undefined error occurred";
if (context.module_name) { if (context.module_name) {
std::cerr << " with module: " << context.module_name; error_msg += " with module: " + std::string(context.module_name);
} }
if (context.message) { if (context.message) {
std::cerr << " (" << context.message << ")"; error_msg += " (" + std::string(context.message) + ")";
} }
std::cerr << std::endl; dpm_log(ERROR, error_msg.c_str());
return 1; return 1;
} }

View File

@@ -64,6 +64,37 @@ extern "C" void dpm_log(int level, const char* message) {
g_logger.log(log_level, message); g_logger.log(log_level, message);
} }
extern "C" void dpm_con(int level, const char* message) {
if (!message) {
return;
}
// Convert integer level to LoggingLevels enum
LoggingLevels log_level;
switch (level) {
case 0:
log_level = LoggingLevels::FATAL;
break;
case 1:
log_level = LoggingLevels::ERROR;
break;
case 2:
log_level = LoggingLevels::WARN;
break;
case 3:
log_level = LoggingLevels::INFO;
break;
case 4:
log_level = LoggingLevels::DEBUG;
break;
default:
log_level = LoggingLevels::INFO;
break;
}
g_logger.log_console(log_level, message);
}
extern "C" void dpm_set_logging_level(int level) { extern "C" void dpm_set_logging_level(int level) {
// Convert integer level to LoggingLevels enum // Convert integer level to LoggingLevels enum
LoggingLevels log_level; LoggingLevels log_level;
@@ -90,3 +121,9 @@ extern "C" void dpm_set_logging_level(int level) {
g_logger.setLogLevel(log_level); g_logger.setLogLevel(log_level);
} }
extern "C" const char* dpm_get_module_path(void) {
static const char * module_path;
module_path = g_config_manager.getModulePath();
return module_path;
}