From 01e77610d0c69fc68a7defe277dfc38a36210334 Mon Sep 17 00:00:00 2001 From: Justin Schneck Date: Thu, 4 Dec 2025 13:02:48 -0500 Subject: [PATCH] fix multi-input abs output path issue Since adding multi input path searching, absolute paths were being prefixed to the output paths for stone image builders. Fix the issue and test to ensure output paths are always relative to output root --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/commands/stone/provision.rs | 10 ++- tests/commands/stone/provision/mod.rs | 115 ++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f72e84f..cbb37ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ checksum = "9d26fcce2f397e5488affdf681b20c030aa9faa877b92b1825e5d66b08d2fc33" [[package]] name = "stone" -version = "1.8.0" +version = "1.8.1" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 086c5e9..7ddfd79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stone" -version = "1.8.0" +version = "1.8.1" edition = "2024" description = "A CLI for managing Avocado stones." homepage = "https://github.com/avocado-linux/stone" diff --git a/src/commands/stone/provision.rs b/src/commands/stone/provision.rs index 2d018ab..5f75e73 100644 --- a/src/commands/stone/provision.rs +++ b/src/commands/stone/provision.rs @@ -365,8 +365,10 @@ fn create_fat_manifest_with_resolved_paths( for entry in files { let (input_filename, output_name) = match entry { - FileEntry::String(filename) => (filename.as_str(), None), - FileEntry::Object { input, output } => (input.as_str(), Some(output.clone())), + // For String entries, use the original filename as the output path in the FAT image + FileEntry::String(filename) => (filename.as_str(), filename.clone()), + // For Object entries, use the explicit output path + FileEntry::Object { input, output } => (input.as_str(), output.clone()), }; // Resolve the input file across all input directories @@ -375,8 +377,10 @@ fn create_fat_manifest_with_resolved_paths( })?; fat_files.push(fat::FileEntry { + // filename: absolute path where the file is found (for reading) filename: Some(resolved_path.to_string_lossy().to_string()), - output: output_name, + // output: relative path where to place the file in the FAT image + output: Some(output_name), }); } diff --git a/tests/commands/stone/provision/mod.rs b/tests/commands/stone/provision/mod.rs index 49331d5..2fbf8e3 100644 --- a/tests/commands/stone/provision/mod.rs +++ b/tests/commands/stone/provision/mod.rs @@ -1216,6 +1216,121 @@ echo "SD_SPECIFIC=$SD_SPECIFIC" >> provision_sd_output.txt assert!(sd_output.contains("SD_SPECIFIC=sd-value")); } +#[test] +fn test_provision_fat_image_files_at_correct_paths() { + // This test verifies that files in FAT images are placed at the correct + // relative paths, not with absolute path prefixes (regression test for bug + // where files were placed at /opt/_avocado/.../file.txt instead of /file.txt) + let temp_dir = TempDir::new().unwrap(); + let input_path1 = temp_dir.path().join("input1"); + let input_path2 = temp_dir.path().join("input2"); + + fs::create_dir_all(&input_path1).unwrap(); + fs::create_dir_all(&input_path2).unwrap(); + + // Create test files in different input directories + fs::write( + input_path1.join("file_from_input1.txt"), + "Content from input1", + ) + .unwrap(); + fs::write( + input_path2.join("file_from_input2.txt"), + "Content from input2", + ) + .unwrap(); + + // Create os-release in second directory + let os_release_content = r#"NAME="Avocado Linux" +VERSION="1.0.0" +ID=avocado +VERSION_ID="1.0.0" +VERSION_CODENAME=test +PRETTY_NAME="Avocado Linux 1.0.0" +VENDOR_NAME="Avocado Linux""#; + fs::write(input_path2.join("os-release"), os_release_content).unwrap(); + + // Create a manifest with FAT image that references files from both directories + let manifest_content = r#"{ + "runtime": { + "platform": "test-platform", + "architecture": "noarch" + }, + "storage_devices": { + "test_device": { + "out": "test.img", + "devpath": "/dev/test", + "images": { + "fat_image": { + "out": "boot.img", + "size": 16, + "size_unit": "megabytes", + "build_args": { + "type": "fat", + "variant": "FAT32", + "files": [ + "file_from_input1.txt", + "file_from_input2.txt" + ] + } + } + }, + "partitions": [] + } + } + }"#; + + fs::write(input_path2.join("manifest.json"), manifest_content).unwrap(); + + Command::cargo_bin("stone") + .unwrap() + .args([ + "provision", + "--input-dir", + &input_path1.to_string_lossy(), + "--input-dir", + &input_path2.to_string_lossy(), + ]) + .assert() + .success(); + + // Verify the FAT image was created + let fat_image_path = input_path2.join("_build").join("boot.img"); + assert!(fat_image_path.exists(), "FAT image should be created"); + + // List files in the FAT image and verify they are at the correct paths + let files_in_fat = stone::fat::list_fat_files(&fat_image_path) + .expect("Should be able to list FAT image contents"); + + // Files should be at the root of the image, not with absolute path prefixes + assert!( + files_in_fat.contains(&"file_from_input1.txt".to_string()), + "file_from_input1.txt should be at root of FAT image, not with absolute path. Found files: {:?}", + files_in_fat + ); + assert!( + files_in_fat.contains(&"file_from_input2.txt".to_string()), + "file_from_input2.txt should be at root of FAT image, not with absolute path. Found files: {:?}", + files_in_fat + ); + + // Ensure files are at the root (no directory prefix) + // Files should be just the filename, not "/some/path/filename.txt" + for file in &files_in_fat { + assert!( + !file.starts_with('/') || file.matches('/').count() == 1, + "File '{}' should be at root of FAT image, not in a subdirectory", + file + ); + // Make sure there are no deep paths like /opt/_avocado/... + assert!( + file.matches('/').count() <= 1, + "File '{}' has too many path components, should be at root", + file + ); + } +} + #[test] fn test_provision_with_manifest_in_second_directory() { let temp_dir = TempDir::new().unwrap();