diff --git a/modules/build/CMakeLists.txt b/modules/build/CMakeLists.txt index f62e1c8..62010fa 100644 --- a/modules/build/CMakeLists.txt +++ b/modules/build/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(build MODULE src/commands.cpp src/package_staging.cpp src/checksums.cpp + src/metadata.cpp ) # Set output properties @@ -48,6 +49,7 @@ add_executable(build_standalone src/commands.cpp src/package_staging.cpp src/checksums.cpp + src/metadata.cpp ) # Define the BUILD_STANDALONE macro for the standalone build diff --git a/modules/build/include/metadata.hpp b/modules/build/include/metadata.hpp new file mode 100644 index 0000000..bdf20bd --- /dev/null +++ b/modules/build/include/metadata.hpp @@ -0,0 +1,55 @@ +/** +* @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 + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "checksums.hpp" + +/** + * @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 update_contents_manifest(const std::filesystem::path& package_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 +); \ No newline at end of file diff --git a/modules/build/include/package_staging.hpp b/modules/build/include/package_staging.hpp index 6f41a15..f83afbf 100644 --- a/modules/build/include/package_staging.hpp +++ b/modules/build/include/package_staging.hpp @@ -20,6 +20,7 @@ #include #include #include "checksums.hpp" +#include "metadata.hpp" /** * @brief Stages a DPM package diff --git a/modules/build/src/commands.cpp b/modules/build/src/commands.cpp index ca781b7..0a4c5fa 100644 --- a/modules/build/src/commands.cpp +++ b/modules/build/src/commands.cpp @@ -5,18 +5,35 @@ static int refresh_package_manifest(const std::string& stage_dir, bool force) { return 0; } -static int generate_package_manifest(const std::string& stage_dir, bool force) { - dpm_log(LOG_INFO, ("Generating package manifest for: " + stage_dir).c_str()); +static int generate_package_manifest( + const std::string& package_dir, + const std::string& package_name, + const std::string& package_version, + const std::string& architecture, + bool force +) { + dpm_log(LOG_INFO, ("Generating package manifest for: " + package_dir).c_str()); + + // Generate the metadata files using the provided information + if (!metadata_generate_new(std::filesystem::path(package_dir), package_name, package_version, architecture)) { + dpm_log(LOG_ERROR, "Failed to generate metadata."); + return 1; + } + + dpm_log(LOG_INFO, "Package content manifest generated successfully."); return 0; } int cmd_manifest(int argc, char** argv) { // Parse command line options bool force = false; - bool refresh_only = false; + bool replace = false; bool verbose = false; bool show_help = false; - std::string stage_dir = ""; + std::string package_dir = ""; + std::string package_name = ""; + std::string package_version = ""; + std::string architecture = ""; // Process command-line arguments for (int i = 1; i < argc; i++) { @@ -24,14 +41,23 @@ int cmd_manifest(int argc, char** argv) { if (arg == "-f" || arg == "--force") { force = true; - } else if (arg == "-r" || arg == "--refresh-only") { - refresh_only = true; + } else if (arg == "-r" || arg == "--replace") { + replace = true; } else if (arg == "-v" || arg == "--verbose") { verbose = true; - } else if (arg == "-h" || arg == "--help" || "help" ) { + } 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]; + } else if ((arg == "-p" || arg == "--package-dir") && i + 1 < argc) { + package_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 } } @@ -41,18 +67,18 @@ int cmd_manifest(int argc, char** argv) { return cmd_manifest_help(argc, argv); } - // Validate that stage directory is provided - if (stage_dir.empty()) { - dpm_log(LOG_ERROR, "Stage directory is required (--stage/-s)"); + // Validate that package directory is provided + if (package_dir.empty()) { + dpm_log(LOG_ERROR, "Package directory is required (--package-dir/-p)"); return cmd_manifest_help(argc, argv); } // Expand path if needed - stage_dir = expand_path(stage_dir); + package_dir = expand_path(package_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()); + // Check if package directory exists + if (!std::filesystem::exists(package_dir)) { + dpm_log(LOG_ERROR, ("Package directory does not exist: " + package_dir).c_str()); return 1; } @@ -62,10 +88,16 @@ int cmd_manifest(int argc, char** argv) { } // Log the operation being performed - if (refresh_only) { - return refresh_package_manifest(stage_dir, force); + if (replace) { + // When replacing a manifest, we need name, version, and architecture + if (package_name.empty() || package_version.empty() || architecture.empty()) { + dpm_log(LOG_ERROR, "Package name, version, and architecture are required for replacing a manifest"); + return cmd_manifest_help(argc, argv); + } + + return generate_package_manifest(package_dir, package_name, package_version, architecture, force); } else { - return generate_package_manifest(stage_dir, force); + return refresh_package_manifest(package_dir, force); } } @@ -168,6 +200,23 @@ int cmd_unknown(const char* command, int argc, char** argv) { } +int cmd_manifest_help(int argc, char** argv) { + dpm_log(LOG_INFO, "Usage: dpm build manifest [options]"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Options:"); + dpm_log(LOG_INFO, " -p, --package-dir DIR Package directory path (required)"); + dpm_log(LOG_INFO, " -n, --name NAME Package name (required when replacing)"); + dpm_log(LOG_INFO, " -V, --version VERSION Package version (required when replacing)"); + dpm_log(LOG_INFO, " -a, --architecture ARCH Package architecture (required when replacing)"); + dpm_log(LOG_INFO, " -r, --replace Replace manifest with new one (default: refresh existing)"); + dpm_log(LOG_INFO, " -f, --force Force manifest operation even if warnings occur"); + dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); + dpm_log(LOG_INFO, " -h, --help Display this help message"); + dpm_log(LOG_INFO, ""); + return 0; +} + + int cmd_stage_help(int argc, char** argv) { dpm_log(LOG_INFO, "Usage: dpm build stage [options]"); dpm_log(LOG_INFO, ""); @@ -184,16 +233,3 @@ int cmd_stage_help(int argc, char** argv) { dpm_log(LOG_INFO, " -h, --help Display this help message"); return 0; } - -int cmd_manifest_help(int argc, char** argv) { - dpm_log(LOG_INFO, "Usage: dpm build manifest [options]"); - dpm_log(LOG_INFO, ""); - dpm_log(LOG_INFO, "Options:"); - dpm_log(LOG_INFO, " -s, --stage DIR Stage directory path (required)"); - dpm_log(LOG_INFO, " -r, --refresh-only Refresh existing manifest instead of generating a new one"); - dpm_log(LOG_INFO, " -f, --force Force manifest operation even if warnings occur"); - dpm_log(LOG_INFO, " -v, --verbose Enable verbose output"); - dpm_log(LOG_INFO, " -h, --help Display this help message"); - dpm_log(LOG_INFO, ""); - return 0; -} \ No newline at end of file diff --git a/modules/build/src/metadata.cpp b/modules/build/src/metadata.cpp new file mode 100644 index 0000000..cfff889 --- /dev/null +++ b/modules/build/src/metadata.cpp @@ -0,0 +1,184 @@ +/** + * @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 + * + * Part of the Dark Horse Linux Package Manager (DPM) + */ + +#include "metadata.hpp" + +bool metadata_generate_new( + const std::filesystem::path& package_dir, + const std::string& package_name, + const std::string& package_version, + const std::string& architecture +) { + 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 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"); + + // Update the contents manifest + if (!update_contents_manifest(package_dir)) { + dpm_log(LOG_ERROR, "Failed to update contents manifest"); + return false; + } + + return true; + } catch (const std::exception& e) { + dpm_log(LOG_ERROR, ("Failed to create metadata files: " + std::string(e.what())).c_str()); + return false; + } +} + +/** + * @brief Updates the contents manifest file for a package + * + * 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 being staged + * @return true if manifest generation was successful, false otherwise + */ +bool update_contents_manifest(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(); + dpm_log(LOG_INFO, "Contents manifest generated successfully"); + return true; + } + catch (const std::exception& e) { + dpm_log(LOG_ERROR, ("Failed to generate contents manifest: " + std::string(e.what())).c_str()); + return false; + } +} \ No newline at end of file diff --git a/modules/build/src/package_staging.cpp b/modules/build/src/package_staging.cpp index d7e90bc..baa4b34 100644 --- a/modules/build/src/package_staging.cpp +++ b/modules/build/src/package_staging.cpp @@ -264,170 +264,6 @@ static bool stage_populate_hooks( return true; } -static bool stage_populate_basic_metadata( - const std::filesystem::path& package_dir, - const std::string& package_name, - const std::string& package_version, - const std::string& architecture -) { - 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 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"); - return true; - } catch (const std::exception& e) { - dpm_log(LOG_ERROR, ("Failed to create metadata files: " + std::string(e.what())).c_str()); - return false; - } -} - -/** - * @brief Updates the contents manifest file for a package - * - * 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 being staged - * @return true if manifest generation was successful, false otherwise - */ -static bool update_contents_manifest(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(); - dpm_log(LOG_INFO, "Contents manifest generated successfully"); - 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 build_package_stage( const std::string& output_dir, const std::string& contents_dir, @@ -465,7 +301,7 @@ int build_package_stage( } // Populate metadata files - if (!stage_populate_basic_metadata(package_dir, package_name, package_version, architecture)) + if (!metadata_generate_new(package_dir, package_name, package_version, architecture)) { return 1; }