@@ -9,7 +9,7 @@ use std::os::unix::io::AsRawFd;
99use std:: path:: { Path , PathBuf } ;
1010use std:: process:: Command ;
1111
12- use anyhow:: { bail, Context , Result } ;
12+ use anyhow:: { anyhow , bail, Context , Result } ;
1313use bootc_utils:: CommandRunExt ;
1414use cap_std:: fs:: Dir ;
1515use cap_std_ext:: cap_std;
@@ -462,6 +462,84 @@ impl Component for Efi {
462462 Ok ( meta)
463463 }
464464
465+ fn extend_payload ( & self , sysroot_path : & str , src_input : & str ) -> Result < Option < bool > > {
466+ let dest_efidir_base = Path :: new ( sysroot_path) . join ( "usr/lib/efi" ) . join ( "firmware" ) ;
467+
468+ let src_input_path = Path :: new ( src_input) ;
469+ let path_to_query = if src_input_path. is_dir ( ) {
470+ WalkDir :: new ( src_input_path)
471+ . into_iter ( )
472+ . filter_map ( |e| e. ok ( ) )
473+ . find ( |e| e. file_type ( ) . is_file ( ) )
474+ . map ( |e| e. path ( ) . to_path_buf ( ) )
475+ . ok_or_else ( || anyhow ! ( "No file found in directory {}" , src_input) ) ?
476+ } else {
477+ src_input_path. to_path_buf ( )
478+ } ;
479+
480+ let meta_from_src = packagesystem:: query_files ( sysroot_path, [ path_to_query] )
481+ . context ( format ! ( "Querying RPM metadata for {:?}" , src_input_path) ) ?;
482+
483+ let version_string_part =
484+ meta_from_src. version . split ( ',' ) . next ( ) . ok_or_else ( || {
485+ anyhow ! ( "RPM query returned an empty or malformed version string" )
486+ } ) ?;
487+
488+ let parts: Vec < & str > = version_string_part. split ( '-' ) . collect ( ) ;
489+ let ( pkg_name, version_release_str) = if parts. len ( ) >= 3 {
490+ (
491+ parts[ 0 ] . to_string ( ) ,
492+ format ! (
493+ "{}-{}" ,
494+ parts[ parts. len( ) - 2 ] ,
495+ parts[ parts. len( ) - 1 ]
496+ . split( '.' )
497+ . next( )
498+ . unwrap_or( parts[ parts. len( ) - 1 ] )
499+ ) ,
500+ )
501+ } else {
502+ anyhow:: bail!( "Unexpected RPM version string format" ) ;
503+ } ;
504+
505+ // Use the flattened destination path
506+ let final_dest_path = dest_efidir_base. join ( & pkg_name) . join ( & version_release_str) ;
507+ std:: fs:: create_dir_all ( & final_dest_path) ?;
508+
509+ let efi_dest_path = final_dest_path. join ( "EFI" ) ;
510+ std:: fs:: create_dir_all ( & efi_dest_path) ?;
511+
512+ // Copy the payload files
513+ let src_metadata = std:: fs:: metadata ( src_input_path) ?;
514+ if src_metadata. is_dir ( ) {
515+ Command :: new ( "cp" )
516+ . args ( [
517+ "-rp" ,
518+ & format ! ( "{}/." , src_input) ,
519+ efi_dest_path. to_str ( ) . unwrap ( ) ,
520+ ] )
521+ . run ( )
522+ . with_context ( || {
523+ format ! (
524+ "Failed to copy contents of {:?} to {:?}" ,
525+ src_input, & efi_dest_path
526+ )
527+ } ) ?;
528+ } else {
529+ Command :: new ( "cp" )
530+ . args ( [ "-p" , src_input, efi_dest_path. to_str ( ) . unwrap ( ) ] )
531+ . run ( ) ?;
532+ }
533+
534+ // Create the metadata file for the firmware
535+ let firmware_meta_path = final_dest_path. join ( "EFI.json" ) ;
536+ let meta_file = std:: fs:: File :: create ( firmware_meta_path) ?;
537+ serde_json:: to_writer ( meta_file, & meta_from_src) ?;
538+ log:: debug!( "Wrote firmware metadata for {}" , pkg_name) ;
539+
540+ Ok ( Some ( true ) )
541+ }
542+
465543 fn query_update ( & self , sysroot : & openat:: Dir ) -> Result < Option < ContentMetadata > > {
466544 get_component_update ( sysroot, self )
467545 }
@@ -774,4 +852,170 @@ Boot0003* test";
774852 }
775853 Ok ( ( ) )
776854 }
855+ #[ test]
856+ fn test_extend_payload ( ) -> Result < ( ) > {
857+ use std:: fs;
858+ use tempfile:: TempDir ;
859+
860+ let temp_sysroot = TempDir :: new ( ) ?;
861+ let temp_src = TempDir :: new ( ) ?;
862+
863+ let sysroot_path = temp_sysroot. path ( ) . to_str ( ) . unwrap ( ) ;
864+ let src_path = temp_src. path ( ) . to_str ( ) . unwrap ( ) ;
865+
866+ // mockup data source: /usr/share/uboot/rpi/
867+ // content: u-boot.bin, overlays/i2c.dtb
868+ // mockup rpm: uboot-images-2023.04-2.fc42.noarch
869+ // mockup rpm_db: /usr/lib/sysimage/rpm/Packages
870+ let src_uboot_dir = temp_src
871+ . path ( )
872+ . join ( "usr" )
873+ . join ( "share" )
874+ . join ( "uboot" )
875+ . join ( "rpi" ) ;
876+ fs:: create_dir_all ( & src_uboot_dir) ?;
877+
878+ let src_overlays_dir = src_uboot_dir. join ( "overlays" ) ;
879+ fs:: create_dir_all ( & src_overlays_dir) ?;
880+
881+ // Create content files
882+ let uboot_bin = src_uboot_dir. join ( "u-boot.bin" ) ;
883+ fs:: write ( & uboot_bin, b"uboot binary content" ) ?;
884+ let overlay_dtb = src_overlays_dir. join ( "i2c.dtb" ) ;
885+ fs:: write ( & overlay_dtb, b"device tree overlay content" ) ?;
886+
887+ // Create a mockup RPM database structure
888+ let rpm_db_dir = temp_sysroot
889+ . path ( )
890+ . join ( "usr" )
891+ . join ( "lib" )
892+ . join ( "sysimage" )
893+ . join ( "rpm" ) ;
894+ fs:: create_dir_all ( & rpm_db_dir) ?;
895+ fs:: write ( rpm_db_dir. join ( "Packages" ) , b"fake rpm database file" ) ?;
896+
897+ // Create a mock rpm script that returns uboot-images package data
898+ let mock_rpm_dir = TempDir :: new ( ) ?;
899+ let mock_rpm_script = mock_rpm_dir. path ( ) . join ( "rpm" ) ;
900+
901+ let mock_script_content = format ! (
902+ r#"#!/bin/bash
903+ # Mock rpm script for testing
904+ if [[ "$*" == *"-q"* ]] && [[ "$*" == *"-f"* ]]; then
905+ # Return mock uboot-images package data in the expected format: nevra,buildtime
906+ echo "uboot-images-2023.04-2.fc42.noarch,1681234567"
907+ exit 0
908+ fi
909+ # For any other rpm command, just fail
910+ exit 1
911+ "#
912+ ) ;
913+
914+ fs:: write ( & mock_rpm_script, mock_script_content) ?;
915+
916+ #[ cfg( unix) ]
917+ {
918+ use std:: os:: unix:: fs:: PermissionsExt ;
919+ let mut perms = fs:: metadata ( & mock_rpm_script) ?. permissions ( ) ;
920+ perms. set_mode ( 0o755 ) ;
921+ fs:: set_permissions ( & mock_rpm_script, perms) ?;
922+ }
923+
924+ let original_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
925+ let new_path = format ! ( "{}:{}" , mock_rpm_dir. path( ) . display( ) , original_path) ;
926+ std:: env:: set_var ( "PATH" , & new_path) ;
927+
928+ // Test extend_payload
929+ let efi_component = Efi :: default ( ) ;
930+
931+ let result = efi_component
932+ . extend_payload ( sysroot_path, & format ! ( "{}/usr/share/uboot/rpi" , src_path) ) ;
933+
934+ // validation beigins
935+ std:: env:: set_var ( "PATH" , & original_path) ;
936+ match result {
937+ Ok ( Some ( true ) ) => {
938+ // Verify the files were copied to the right location
939+ let firmware_base = temp_sysroot
940+ . path ( )
941+ . join ( "usr" )
942+ . join ( "lib" )
943+ . join ( "efi" )
944+ . join ( "firmware" ) ;
945+ assert ! (
946+ firmware_base. exists( ) ,
947+ "Firmware base directory should be created"
948+ ) ;
949+
950+ // Look for the uboot package directory (package name is extracted from first part)
951+ // From "uboot-images-2023.04-2.fc42.noarch" -> package: "uboot", version: "2023.04-2"
952+ let uboot_dir = firmware_base. join ( "uboot" ) . join ( "2023.04-2" ) ;
953+ assert ! (
954+ uboot_dir. exists( ) ,
955+ "Package directory uboot/2023.04-2 should be created"
956+ ) ;
957+
958+ // Files should be copied to the EFI subdirectory
959+ let efi_dir = uboot_dir. join ( "EFI" ) ;
960+ assert ! ( efi_dir. exists( ) , "EFI directory should be created" ) ;
961+
962+ // Verify that u-boot.bin was copied to EFI subdirectory
963+ let copied_uboot_bin = efi_dir. join ( "u-boot.bin" ) ;
964+ assert ! ( copied_uboot_bin. exists( ) , "u-boot.bin should be copied" ) ;
965+ let uboot_content = fs:: read_to_string ( & copied_uboot_bin) ?;
966+ assert_eq ! (
967+ uboot_content, "uboot binary content" ,
968+ "u-boot.bin content should be preserved"
969+ ) ;
970+
971+ // Verify that overlays directory and i2c.dtb were copied to EFI subdirectory
972+ let copied_overlays_dir = efi_dir. join ( "overlays" ) ;
973+ assert ! (
974+ copied_overlays_dir. exists( ) ,
975+ "overlays directory should be copied"
976+ ) ;
977+
978+ let copied_overlay_dtb = copied_overlays_dir. join ( "i2c.dtb" ) ;
979+ assert ! ( copied_overlay_dtb. exists( ) , "i2c.dtb should be copied" ) ;
980+ let overlay_content = fs:: read_to_string ( & copied_overlay_dtb) ?;
981+ assert_eq ! (
982+ overlay_content, "device tree overlay content" ,
983+ "i2c.dtb content should be preserved"
984+ ) ;
985+
986+ // Verify the EFI.json metadata
987+ let metadata_file = uboot_dir. join ( "EFI.json" ) ;
988+ assert ! (
989+ metadata_file. exists( ) ,
990+ "EFI.json metadata file should be created"
991+ ) ;
992+ let metadata_content = fs:: read_to_string ( & metadata_file) ?;
993+ let parsed: ContentMetadata = serde_json:: from_str ( & metadata_content) ?;
994+ assert ! (
995+ parsed
996+ . version
997+ . contains( "uboot-images-2023.04-2.fc42.noarch" ) ,
998+ "Metadata should contain uboot package"
999+ ) ;
1000+
1001+ println ! ( "extend_payload test completed successfully!" ) ;
1002+ println ! ( "✓ Files copied to: {:?}" , efi_dir) ;
1003+ println ! ( "✓ u-boot.bin: {:?}" , copied_uboot_bin) ;
1004+ println ! ( "✓ overlays/i2c.dtb: {:?}" , copied_overlay_dtb) ;
1005+ println ! ( "✓ Metadata created: {:?}" , metadata_file) ;
1006+ println ! ( "✓ Package version: {}" , parsed. version) ;
1007+ }
1008+ Ok ( Some ( false ) ) => {
1009+ panic ! ( "extend_payload returned false - expected success" ) ;
1010+ }
1011+ Ok ( None ) => {
1012+ panic ! ( "extend_payload returned None - expected success" ) ;
1013+ }
1014+ Err ( e) => {
1015+ panic ! ( "extend_payload failed when it should have succeeded: {}" , e) ;
1016+ }
1017+ }
1018+
1019+ Ok ( ( ) )
1020+ }
7771021}
0 commit comments