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();