diff --git a/modules/build/build.cpp b/modules/build/build.cpp index 57bd782..4a2992c 100644 --- a/modules/build/build.cpp +++ b/modules/build/build.cpp @@ -92,8 +92,8 @@ extern "C" int dpm_module_execute(const char* command, int argc, char** argv) { case CMD_HELP: return cmd_help(argc, argv); - case CMD_MANIFEST: - return cmd_manifest(argc, argv); + case CMD_METADATA: + return cmd_metadata(argc, argv); case CMD_SIGN: return cmd_sign(argc, argv); diff --git a/modules/build/include/checksums.hpp b/modules/build/include/checksums.hpp index a8d4288..b259ad1 100644 --- a/modules/build/include/checksums.hpp +++ b/modules/build/include/checksums.hpp @@ -55,3 +55,14 @@ std::string get_available_algorithms(); * @return String containing the hexadecimal representation of the checksum, or empty string on error */ 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 + */ +std::string generate_string_checksum(const std::string& input_string); \ No newline at end of file diff --git a/modules/build/include/cli_parsers.hpp b/modules/build/include/cli_parsers.hpp index 110f300..57477fe 100644 --- a/modules/build/include/cli_parsers.hpp +++ b/modules/build/include/cli_parsers.hpp @@ -15,7 +15,7 @@ enum Command { CMD_UNKNOWN, /**< Unknown or unsupported command */ CMD_HELP, /**< Display help information */ CMD_STAGE, /**< Stage a new DPM package */ - CMD_MANIFEST, /**< Regenerate a stage manifest */ + 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 */ diff --git a/modules/build/include/commands.hpp b/modules/build/include/commands.hpp index bd71ebb..e00dab7 100644 --- a/modules/build/include/commands.hpp +++ b/modules/build/include/commands.hpp @@ -29,7 +29,7 @@ int cmd_stage(int argc, char** argv); * @param argv Array of arguments * @return 0 on success, non-zero on failure */ -int cmd_manifest(int argc, char** argv); +int cmd_metadata(int argc, char** argv); /** * @brief Handler for the sign command @@ -84,7 +84,7 @@ int cmd_sign_help(int argc, char** argv); * @param argv Array of arguments * @return 0 on success, non-zero on failure */ -int cmd_manifest_help(int argc, char** argv); +int cmd_metadata_help(int argc, char** argv); /** * @brief Handler for unknown commands diff --git a/modules/build/include/metadata.hpp b/modules/build/include/metadata.hpp index a8c4bc2..d1b31d7 100644 --- a/modules/build/include/metadata.hpp +++ b/modules/build/include/metadata.hpp @@ -18,10 +18,25 @@ #include #include #include +#include #include #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 * @@ -32,7 +47,37 @@ * @param package_dir Root directory of the package stage * @return true if contents manifest generation was successful, false otherwise */ -bool generate_contents_manifest(const std::filesystem::path& package_dir); +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 @@ -52,4 +97,16 @@ bool metadata_generate_new( const std::string& package_name, const std::string& package_version, const std::string& architecture -); \ No newline at end of file +); + +/** + * @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); \ No newline at end of file diff --git a/modules/build/src/checksums.cpp b/modules/build/src/checksums.cpp index 23a4875..d869187 100644 --- a/modules/build/src/checksums.cpp +++ b/modules/build/src/checksums.cpp @@ -169,3 +169,61 @@ std::string generate_file_checksum(const std::filesystem::path& file_path) return ss.str(); } + +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(hash[i]); + } + + return ss.str(); +} \ No newline at end of file diff --git a/modules/build/src/cli_parsers.cpp b/modules/build/src/cli_parsers.cpp index 5f5595e..ad87296 100644 --- a/modules/build/src/cli_parsers.cpp +++ b/modules/build/src/cli_parsers.cpp @@ -237,9 +237,9 @@ Command parse_command(const char* cmd_str) { return CMD_STAGE; } - // Check for manifest command, including when it has additional arguments - if (strncmp(cmd_str, "manifest", 8) == 0) { - return CMD_MANIFEST; + // 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 diff --git a/modules/build/src/commands.cpp b/modules/build/src/commands.cpp index 2454515..6e3413b 100644 --- a/modules/build/src/commands.cpp +++ b/modules/build/src/commands.cpp @@ -1,261 +1,15 @@ #include "commands.hpp" -/** - * @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 - */ -static int refresh_contents_manifest(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 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; -} - -static int generate_contents_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 content 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) { +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 package_dir = ""; + 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++) { @@ -269,29 +23,38 @@ int cmd_manifest(int argc, char** argv) { verbose = true; } else if (arg == "-h" || arg == "--help" || arg == "help") { show_help = true; - } else if ((arg == "-p" || arg == "--package-dir") && i + 1 < argc) { - package_dir = argv[i + 1]; + } 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_manifest_help(argc, argv); + return cmd_metadata_help(argc, argv); } - // 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); + // 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 - package_dir = expand_path(package_dir); + stage_dir = expand_path(stage_dir); - // Check if package directory exists - if (!std::filesystem::exists(package_dir)) { - dpm_log(LOG_ERROR, ("Package directory does not exist: " + package_dir).c_str()); + // 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; } @@ -302,20 +65,44 @@ int cmd_manifest(int argc, char** argv) { // Call the appropriate function based on the refresh flag if (refresh) { - int result = refresh_contents_manifest(package_dir, force); - if (result != 0) { - dpm_log(LOG_ERROR, "Failed to refresh contents manifest."); - return result; - } - dpm_log(LOG_INFO, "Contents manifest refreshed successfully."); - return 0; - } else { - bool success = generate_contents_manifest(std::filesystem::path(package_dir)); + // 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 generate contents manifest."); + dpm_log(LOG_ERROR, "Failed to refresh metadata files."); return 1; } - dpm_log(LOG_INFO, "Contents manifest generated successfully."); + 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; } } @@ -488,7 +275,7 @@ int cmd_help(int argc, char** argv) { dpm_log(LOG_INFO, ""); dpm_log(LOG_INFO, "Available commands:"); dpm_log(LOG_INFO, " stage - Stage a new DPM package directory"); - dpm_log(LOG_INFO, " manifest - Generate or refresh package manifest"); + dpm_log(LOG_INFO, " metadata - Generate or refresh package metadata"); dpm_log(LOG_INFO, " sign - Sign a package or package stage directory"); dpm_log(LOG_INFO, " seal - Seal a package stage directory into final format"); dpm_log(LOG_INFO, " unseal - Unseal a package back to stage format"); @@ -509,16 +296,29 @@ int cmd_unknown(const char* command, int argc, char** argv) { return 1; } -int cmd_manifest_help(int argc, char** argv) { - dpm_log(LOG_INFO, "Usage: dpm build manifest [options]"); +int cmd_metadata_help(int argc, char** argv) { + dpm_log(LOG_INFO, "Usage: dpm build metadata [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, " -r, --refresh Refresh existing manifest (default: generate new)"); - dpm_log(LOG_INFO, " -f, --force Force manifest operation even if warnings occur"); + dpm_log(LOG_INFO, " -s, --stage DIR Package stage directory path (required)"); + dpm_log(LOG_INFO, " -r, --refresh Refresh existing metadata (use for updating)"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "For new metadata generation (when not using --refresh):"); + dpm_log(LOG_INFO, " -n, --name NAME Package name (required for new generation)"); + dpm_log(LOG_INFO, " -V, --version VERSION Package version (required for new generation)"); + dpm_log(LOG_INFO, " -a, --architecture ARCH Package architecture (required for new generation)"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, "Additional options:"); + dpm_log(LOG_INFO, " -f, --force Force 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, ""); + dpm_log(LOG_INFO, "Examples:"); + dpm_log(LOG_INFO, " # Refresh metadata in an existing package stage:"); + dpm_log(LOG_INFO, " dpm build metadata --stage=./my-package-1.0.x86_64 --refresh"); + dpm_log(LOG_INFO, ""); + dpm_log(LOG_INFO, " # Generate new metadata for a package stage:"); + dpm_log(LOG_INFO, " dpm build metadata --stage=./my-package-1.0.x86_64 --name=my-package --version=1.0 --architecture=x86_64"); return 0; } diff --git a/modules/build/src/metadata.cpp b/modules/build/src/metadata.cpp index 7e3dddb..3421510 100644 --- a/modules/build/src/metadata.cpp +++ b/modules/build/src/metadata.cpp @@ -12,44 +12,30 @@ #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 -) { +// 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 { - 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 + // Create empty placeholder files for all metadata std::vector metadata_files = { + "NAME", + "VERSION", + "ARCHITECTURE", "AUTHOR", "MAINTAINER", "DEPENDENCIES", @@ -68,33 +54,94 @@ bool metadata_generate_new( std::ofstream metadata_file(metadata_dir / file_name); metadata_file.close(); } - - dpm_log(LOG_INFO, "Created metadata files"); - - // Update the contents manifest - if (!generate_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; } + + dpm_log(LOG_INFO, "Metadata skeleton generated."); + return true; } -/** - * @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 generate_contents_manifest(const std::filesystem::path& package_dir) +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"; @@ -182,4 +229,424 @@ bool generate_contents_manifest(const std::filesystem::path& package_dir) } } +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 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; +} \ No newline at end of file diff --git a/modules/build/src/staging.cpp b/modules/build/src/staging.cpp index 54c9ff3..e45b4fe 100644 --- a/modules/build/src/staging.cpp +++ b/modules/build/src/staging.cpp @@ -306,12 +306,6 @@ int build_package_stage( return 1; } - // Update the contents manifest - if (!generate_contents_manifest(package_dir)) - { - 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:");