diff --git a/modules/build/include/commands.hpp b/modules/build/include/commands.hpp index 717fd46..4cc2bb6 100644 --- a/modules/build/include/commands.hpp +++ b/modules/build/include/commands.hpp @@ -4,6 +4,8 @@ #include #include #include "package_staging.hpp" +#include +#include /** * @brief Handler for the stage command diff --git a/modules/build/include/metadata.hpp b/modules/build/include/metadata.hpp index bdf20bd..a8c4bc2 100644 --- a/modules/build/include/metadata.hpp +++ b/modules/build/include/metadata.hpp @@ -32,7 +32,7 @@ * @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); +bool generate_contents_manifest(const std::filesystem::path& package_dir); /** * @brief Generates basic metadata files for a package stage diff --git a/modules/build/src/commands.cpp b/modules/build/src/commands.cpp index 0a4c5fa..6c1615e 100644 --- a/modules/build/src/commands.cpp +++ b/modules/build/src/commands.cpp @@ -1,18 +1,243 @@ #include "commands.hpp" -static int refresh_package_manifest(const std::string& stage_dir, bool force) { +/** + * @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_package_manifest( +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 package manifest for: " + package_dir).c_str()); + 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)) { @@ -27,13 +252,10 @@ static int generate_package_manifest( int cmd_manifest(int argc, char** argv) { // Parse command line options bool force = false; - bool replace = false; + bool refresh = false; bool verbose = false; bool show_help = false; 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++) { @@ -41,8 +263,8 @@ int cmd_manifest(int argc, char** argv) { if (arg == "-f" || arg == "--force") { force = true; - } else if (arg == "-r" || arg == "--replace") { - replace = 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") { @@ -50,15 +272,6 @@ int cmd_manifest(int argc, char** argv) { } 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 } } @@ -87,17 +300,23 @@ int cmd_manifest(int argc, char** argv) { dpm_set_logging_level(LOG_DEBUG); } - // Log the operation being performed - 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); + // 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; } - - return generate_package_manifest(package_dir, package_name, package_version, architecture, force); + dpm_log(LOG_INFO, "Contents manifest refreshed successfully."); + return 0; } else { - return refresh_package_manifest(package_dir, force); + bool success = generate_contents_manifest(std::filesystem::path(package_dir)); + if (!success) { + dpm_log(LOG_ERROR, "Failed to generate contents manifest."); + return 1; + } + dpm_log(LOG_INFO, "Contents manifest generated successfully."); + return 0; } } @@ -199,16 +418,12 @@ 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]"); 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, " -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, " -v, --verbose Enable verbose output"); dpm_log(LOG_INFO, " -h, --help Display this help message"); @@ -216,7 +431,6 @@ int cmd_manifest_help(int argc, char** argv) { return 0; } - int cmd_stage_help(int argc, char** argv) { dpm_log(LOG_INFO, "Usage: dpm build stage [options]"); dpm_log(LOG_INFO, ""); diff --git a/modules/build/src/metadata.cpp b/modules/build/src/metadata.cpp index cfff889..7e3dddb 100644 --- a/modules/build/src/metadata.cpp +++ b/modules/build/src/metadata.cpp @@ -72,7 +72,7 @@ bool metadata_generate_new( dpm_log(LOG_INFO, "Created metadata files"); // Update the contents manifest - if (!update_contents_manifest(package_dir)) { + if (!generate_contents_manifest(package_dir)) { dpm_log(LOG_ERROR, "Failed to update contents manifest"); return false; } @@ -94,7 +94,7 @@ bool metadata_generate_new( * @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) +bool generate_contents_manifest(const std::filesystem::path& package_dir) { try { std::filesystem::path contents_dir = package_dir / "contents"; @@ -174,11 +174,12 @@ bool update_contents_manifest(const std::filesystem::path& package_dir) } 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 baa4b34..e3193f4 100644 --- a/modules/build/src/package_staging.cpp +++ b/modules/build/src/package_staging.cpp @@ -307,7 +307,7 @@ int build_package_stage( } // Update the contents manifest - if (!update_contents_manifest(package_dir)) + if (!generate_contents_manifest(package_dir)) { return 1; }