Compare commits
29 Commits
4588691faf
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b3c86aa93 | ||
|
|
7ebc3ebad3 | ||
|
|
d41c9a65e3 | ||
|
|
81645c6d09 | ||
|
|
045294aeb6 | ||
|
|
25f9afd1c8 | ||
|
|
56e471e227 | ||
|
|
7b29824e40 | ||
|
|
448dc0cdfd | ||
|
|
49c73d1876 | ||
|
|
15360edc42 | ||
|
|
1d34a62e38 | ||
|
|
b8ee0f9eff | ||
|
|
c27d91a573 | ||
|
|
8b7e594d33 | ||
|
|
78891a1881 | ||
|
|
9642581509 | ||
|
|
c733bb634c | ||
|
|
ac0d91f240 | ||
|
|
58089f234f | ||
|
|
7ac80d12f3 | ||
|
|
2623bcf2b3 | ||
|
|
172bc2e9c8 | ||
|
|
6d34c69b9f | ||
|
|
28555d5773 | ||
|
|
5ec3c0ca83 | ||
|
|
aad077a24a | ||
|
|
1f4f73ff0f | ||
|
|
cf860503c9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.idea
|
.idea
|
||||||
cmake-build-debug
|
cmake-build-debug
|
||||||
|
design
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
2
data/security.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[cryptography]
|
||||||
|
checksum_algorithm=sha256
|
||||||
@@ -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
|
||||||
88
dpmdk/include/ModuleOperations.hpp
Normal file
88
dpmdk/include/ModuleOperations.hpp
Normal 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);
|
||||||
123
dpmdk/include/StandaloneModuleImpl.hpp
Normal file
123
dpmdk/include/StandaloneModuleImpl.hpp
Normal 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 */
|
||||||
@@ -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
|
||||||
69
dpmdk/src/ModuleOperations.cpp
Normal file
69
dpmdk/src/ModuleOperations.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
||||||
@@ -10,13 +10,32 @@ else()
|
|||||||
set(DPM_ROOT_DIR "${CMAKE_SOURCE_DIR}")
|
set(DPM_ROOT_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
endif()
|
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()
|
||||||
|
|
||||||
# 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
|
||||||
@@ -30,10 +49,13 @@ set_target_properties(
|
|||||||
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
|
||||||
@@ -41,7 +63,12 @@ add_executable(build_standalone
|
|||||||
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
|
||||||
@@ -51,10 +78,13 @@ target_compile_definitions(build_standalone PRIVATE BUILD_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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
43
modules/build/include/archive_reader.hpp
Normal file
43
modules/build/include/archive_reader.hpp
Normal 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);
|
||||||
|
}
|
||||||
68
modules/build/include/checksums.hpp
Normal file
68
modules/build/include/checksums.hpp
Normal 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);
|
||||||
@@ -14,7 +14,11 @@
|
|||||||
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 */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
112
modules/build/include/metadata.hpp
Normal file
112
modules/build/include/metadata.hpp
Normal 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);
|
||||||
77
modules/build/include/sealing.hpp
Normal file
77
modules/build/include/sealing.hpp
Normal 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);
|
||||||
46
modules/build/include/signing.hpp
Normal file
46
modules/build/include/signing.hpp
Normal 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);
|
||||||
@@ -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
|
||||||
205
modules/build/src/archive_reader.cpp
Normal file
205
modules/build/src/archive_reader.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
229
modules/build/src/checksums.cpp
Normal file
229
modules/build/src/checksums.cpp
Normal 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();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
652
modules/build/src/metadata.cpp
Normal file
652
modules/build/src/metadata.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
703
modules/build/src/sealing.cpp
Normal file
703
modules/build/src/sealing.cpp
Normal 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;
|
||||||
|
}
|
||||||
256
modules/build/src/signing.cpp
Normal file
256
modules/build/src/signing.cpp
Normal 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;
|
||||||
|
}
|
||||||
317
modules/build/src/staging.cpp
Normal file
317
modules/build/src/staging.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
73
modules/verify/CMakeLists.txt
Normal file
73
modules/verify/CMakeLists.txt
Normal 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"
|
||||||
|
)
|
||||||
51
modules/verify/include/checksum.hpp
Normal file
51
modules/verify/include/checksum.hpp
Normal 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);
|
||||||
75
modules/verify/include/checksum_memory.hpp
Normal file
75
modules/verify/include/checksum_memory.hpp
Normal 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);
|
||||||
27
modules/verify/include/cli_parsers.hpp
Normal file
27
modules/verify/include/cli_parsers.hpp
Normal 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);
|
||||||
|
|
||||||
185
modules/verify/include/commands.hpp
Normal file
185
modules/verify/include/commands.hpp
Normal 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);
|
||||||
53
modules/verify/include/package_operations.hpp
Normal file
53
modules/verify/include/package_operations.hpp
Normal 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);
|
||||||
59
modules/verify/include/verification.hpp
Normal file
59
modules/verify/include/verification.hpp
Normal 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);
|
||||||
301
modules/verify/src/checksum.cpp
Normal file
301
modules/verify/src/checksum.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
427
modules/verify/src/checksum_memory.cpp
Normal file
427
modules/verify/src/checksum_memory.cpp
Normal 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;
|
||||||
|
}
|
||||||
22
modules/verify/src/cli_parsers.cpp
Normal file
22
modules/verify/src/cli_parsers.cpp
Normal 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;
|
||||||
|
}
|
||||||
399
modules/verify/src/commands.cpp
Normal file
399
modules/verify/src/commands.cpp
Normal 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;
|
||||||
|
}
|
||||||
125
modules/verify/src/package_operations.cpp
Normal file
125
modules/verify/src/package_operations.cpp
Normal 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;
|
||||||
|
}
|
||||||
212
modules/verify/src/verification.cpp
Normal file
212
modules/verify/src/verification.cpp
Normal 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
104
modules/verify/verify.cpp
Normal 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
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/dpm.cpp
53
src/dpm.cpp
@@ -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,6 +103,13 @@ 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)
|
// Determine the module path (CLI arg > config > default)
|
||||||
std::string module_path;
|
std::string module_path;
|
||||||
|
|
||||||
@@ -132,14 +141,40 @@ int main( int argc, char* argv[] )
|
|||||||
return path_check_result;
|
return path_check_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If help is requested, show it and exit
|
return main_list_modules(loader);
|
||||||
if (args.show_help) {
|
|
||||||
return main_show_help();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If list modules is requested, show the list and exit
|
// Determine the module path (CLI arg > config > default)
|
||||||
if (args.list_modules) {
|
std::string module_path;
|
||||||
return main_list_modules(loader);
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_config_manager.setModulePath(module_path.c_str());
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no module is provided to execute, then trigger the default
|
// if no module is provided to execute, then trigger the default
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -84,18 +81,18 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +114,7 @@ int main_list_modules(const ModuleLoader& loader)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -101,67 +102,77 @@ 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) {
|
int handle_symbol_not_found(FlexDPMError context) {
|
||||||
std::cerr << "Error: Symbol not found in module: " << context.module_name;
|
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;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user