From dfe8ac13056c2d0557a855d0b617afa52cd6cba7 Mon Sep 17 00:00:00 2001 From: Till Wegmueller Date: Sat, 2 Aug 2025 13:17:49 +0200 Subject: [PATCH] Add support for backward compatibility with legacy repository files - Introduced creation of `pub.p5i` files for publishers to maintain compatibility with older repository formats. - Implemented saving repository configuration in legacy INI format (`pkg5.repository`). - Updated tests to validate the generation and structure of legacy files. - Added new dependencies (`rust-ini`) and updated `Cargo.toml` and `Cargo.lock` accordingly. --- Cargo.lock | 71 ++++++++++++++++ libips/Cargo.toml | 1 + libips/src/repository/file_backend.rs | 113 ++++++++++++++++++++++++++ libips/src/repository/tests.rs | 101 +++++++++++++++++++++++ 4 files changed, 286 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index bf1f35a..f26e52a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,26 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -350,6 +370,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -432,6 +458,15 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "either" version = "1.15.0" @@ -627,6 +662,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.4" @@ -981,6 +1022,7 @@ dependencies = [ "pest_derive", "redb", "regex", + "rust-ini", "semver", "serde", "serde_cbor", @@ -1321,6 +1363,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -1644,6 +1696,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-ini" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7295b7ce3bf4806b419dc3420745998b447178b7005e2011947b38fc5aa6791" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.25" @@ -2147,6 +2209,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.1" diff --git a/libips/Cargo.toml b/libips/Cargo.toml index 1b32d1f..821020f 100644 --- a/libips/Cargo.toml +++ b/libips/Cargo.toml @@ -39,6 +39,7 @@ tempfile = "3.20.0" walkdir = "2.4.0" redb = "1.5.0" bincode = "1.3.3" +rust-ini = "0.21.2" [features] default = ["redb-index"] diff --git a/libips/src/repository/file_backend.rs b/libips/src/repository/file_backend.rs index 062062d..433105b 100644 --- a/libips/src/repository/file_backend.rs +++ b/libips/src/repository/file_backend.rs @@ -29,6 +29,7 @@ use super::{ PackageContents, PackageInfo, PublisherInfo, ReadableRepository, RepositoryConfig, RepositoryInfo, RepositoryVersion, WritableRepository, REPOSITORY_CONFIG_FILENAME, }; +use ini::Ini; // Define a struct to hold the content vectors for each package struct PackageContentVectors { @@ -586,6 +587,31 @@ impl Transaction { ); fs::copy(&manifest_path, &pkg_manifest_path)?; + // Check if we need to create a pub.p5i file for the publisher + let config_path = self.repo.join(REPOSITORY_CONFIG_FILENAME); + if config_path.exists() { + let config_content = fs::read_to_string(&config_path)?; + let config: RepositoryConfig = serde_json::from_str(&config_content)?; + + // Check if this publisher was just added in this transaction + let publisher_dir = self.repo.join("publisher").join(&publisher); + let pub_p5i_path = publisher_dir.join("pub.p5i"); + + if !pub_p5i_path.exists() { + debug!("Creating pub.p5i file for publisher: {}", publisher); + + // Create the pub.p5i file + let repo = FileBackend { + path: self.repo.clone(), + config, + catalog_manager: None, + obsoleted_manager: None, + }; + + repo.create_pub_p5i_file(&publisher)?; + } + } + // Clean up the transaction directory fs::remove_dir_all(self.path)?; @@ -1333,9 +1359,14 @@ impl WritableRepository for FileBackend { /// Save the repository configuration fn save_config(&self) -> Result<()> { + // Save the modern JSON format let config_path = self.path.join(REPOSITORY_CONFIG_FILENAME); let config_data = serde_json::to_string_pretty(&self.config)?; fs::write(config_path, config_data)?; + + // Save the legacy INI format for backward compatibility + self.save_legacy_config()?; + Ok(()) } @@ -1348,6 +1379,13 @@ impl WritableRepository for FileBackend { fs::create_dir_all(Self::construct_catalog_path(&self.path, publisher))?; fs::create_dir_all(Self::construct_package_dir(&self.path, publisher, ""))?; + // Create the publisher directory if it doesn't exist + let publisher_dir = self.path.join("publisher").join(publisher); + fs::create_dir_all(&publisher_dir)?; + + // Create the pub.p5i file for backward compatibility + self.create_pub_p5i_file(publisher)?; + // Set as the default publisher if no default publisher is set if self.config.default_publisher.is_none() { self.config.default_publisher = Some(publisher.to_string()); @@ -1513,6 +1551,81 @@ impl WritableRepository for FileBackend { } impl FileBackend { + /// Save the legacy pkg5.repository INI file for backward compatibility + pub fn save_legacy_config(&self) -> Result<()> { + let legacy_config_path = self.path.join("pkg5.repository"); + let mut conf = Ini::new(); + + // Add publisher section with default publisher + if let Some(default_publisher) = &self.config.default_publisher { + conf.with_section(Some("publisher")) + .set("prefix", default_publisher); + } + + // Add repository section with version and default values + conf.with_section(Some("repository")) + .set("version", "4") + .set("trust-anchor-directory", "/etc/certs/CA/") + .set("signature-required-names", "[]") + .set("check-certificate-revocation", "False"); + + // Add CONFIGURATION section with version + conf.with_section(Some("CONFIGURATION")) + .set("version", "4"); + + // Write the INI file + conf.write_to_file(legacy_config_path)?; + + Ok(()) + } + + /// Create a pub.p5i file for a publisher for backward compatibility + /// + /// Format: base_path/publisher/publisher_name/pub.p5i + fn create_pub_p5i_file(&self, publisher: &str) -> Result<()> { + // Define the structure for the pub.p5i file + #[derive(serde::Serialize)] + struct P5iPublisherInfo { + alias: Option, + name: String, + packages: Vec, + repositories: Vec, + } + + #[derive(serde::Serialize)] + struct P5iFile { + packages: Vec, + publishers: Vec, + version: u32, + } + + // Create the publisher info + let publisher_info = P5iPublisherInfo { + alias: None, + name: publisher.to_string(), + packages: Vec::new(), + repositories: Vec::new(), + }; + + // Create the p5i file content + let p5i_content = P5iFile { + packages: Vec::new(), + publishers: vec![publisher_info], + version: 1, + }; + + // Serialize to JSON + let json_content = serde_json::to_string_pretty(&p5i_content)?; + + // Create the path for the pub.p5i file + let pub_p5i_path = self.path.join("publisher").join(publisher).join("pub.p5i"); + + // Write the file + fs::write(pub_p5i_path, json_content)?; + + Ok(()) + } + /// Helper method to construct a catalog path consistently /// /// Format: base_path/publisher/publisher_name/catalog diff --git a/libips/src/repository/tests.rs b/libips/src/repository/tests.rs index bd847fa..d083380 100644 --- a/libips/src/repository/tests.rs +++ b/libips/src/repository/tests.rs @@ -206,6 +206,20 @@ mod tests { assert!(repo.config.publishers.contains(&"example.com".to_string())); assert!(FileBackend::construct_catalog_path(&repo_path, "example.com").exists()); assert!(FileBackend::construct_package_dir(&repo_path, "example.com", "").exists()); + + // Check that the pub.p5i file was created for backward compatibility + let pub_p5i_path = repo_path.join("publisher").join("example.com").join("pub.p5i"); + assert!(pub_p5i_path.exists(), "pub.p5i file should be created for backward compatibility"); + + // Verify the content of the pub.p5i file + let pub_p5i_content = fs::read_to_string(&pub_p5i_path).unwrap(); + let pub_p5i_json: serde_json::Value = serde_json::from_str(&pub_p5i_content).unwrap(); + + // Check the structure of the pub.p5i file + assert_eq!(pub_p5i_json["version"], 1); + assert!(pub_p5i_json["packages"].is_array()); + assert!(pub_p5i_json["publishers"].is_array()); + assert_eq!(pub_p5i_json["publishers"][0]["name"], "example.com"); // Clean up cleanup_test_dir(&test_dir); @@ -432,4 +446,91 @@ mod tests { // Clean up cleanup_test_dir(&test_dir); } + + #[test] + fn test_transaction_pub_p5i_creation() { + // Run the setup script to prepare the test environment + let (prototype_dir, manifest_dir) = run_setup_script(); + + // Create a test directory + let test_dir = create_test_dir("transaction_pub_p5i"); + let repo_path = test_dir.join("repo"); + + // Create a repository + let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap(); + + // Create a new publisher through a transaction + let publisher = "transaction_test"; + + // Start a transaction + let mut transaction = repo.begin_transaction().unwrap(); + + // Set the publisher for the transaction + transaction.set_publisher(publisher); + + // Add a simple manifest to the transaction + let manifest_path = manifest_dir.join("example.p5m"); + let manifest = Manifest::parse_file(&manifest_path).unwrap(); + transaction.update_manifest(manifest); + + // Commit the transaction + transaction.commit().unwrap(); + + // Check that the pub.p5i file was created for the new publisher + let pub_p5i_path = repo_path.join("publisher").join(publisher).join("pub.p5i"); + assert!(pub_p5i_path.exists(), "pub.p5i file should be created for new publisher in transaction"); + + // Verify the content of the pub.p5i file + let pub_p5i_content = fs::read_to_string(&pub_p5i_path).unwrap(); + let pub_p5i_json: serde_json::Value = serde_json::from_str(&pub_p5i_content).unwrap(); + + // Check the structure of the pub.p5i file + assert_eq!(pub_p5i_json["version"], 1); + assert!(pub_p5i_json["packages"].is_array()); + assert!(pub_p5i_json["publishers"].is_array()); + assert_eq!(pub_p5i_json["publishers"][0]["name"], publisher); + + // Clean up + cleanup_test_dir(&test_dir); + } + + #[test] + fn test_legacy_pkg5_repository_creation() { + // Create a test directory + let test_dir = create_test_dir("legacy_pkg5_repository"); + let repo_path = test_dir.join("repo"); + + // Create a repository + let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap(); + + // Add a publisher + let publisher = "openindiana.org"; + repo.add_publisher(publisher).unwrap(); + + // Set as default publisher + repo.set_default_publisher(publisher).unwrap(); + + // Check that the pkg5.repository file was created + let pkg5_repo_path = repo_path.join("pkg5.repository"); + assert!(pkg5_repo_path.exists(), "pkg5.repository file should be created for backward compatibility"); + + // Verify the content of the pkg5.repository file + let pkg5_content = fs::read_to_string(&pkg5_repo_path).unwrap(); + + // Print the content for debugging + println!("pkg5.repository content:\n{}", pkg5_content); + + // Check that the file contains the expected sections and values + assert!(pkg5_content.contains("[publisher]")); + assert!(pkg5_content.contains("prefix=openindiana.org")); + assert!(pkg5_content.contains("[repository]")); + assert!(pkg5_content.contains("version=4")); + assert!(pkg5_content.contains("trust-anchor-directory=/etc/certs/CA/")); + assert!(pkg5_content.contains("signature-required-names=[]")); + assert!(pkg5_content.contains("check-certificate-revocation=False")); + assert!(pkg5_content.contains("[CONFIGURATION]")); + + // Clean up + cleanup_test_dir(&test_dir); + } }