@@ -9,9 +9,8 @@ use std::os::unix::io::AsRawFd;
99use std:: path:: { Path , PathBuf } ;
1010use std:: process:: Command ;
1111
12- use anyhow:: { bail, Context , Result } ;
13- use bootc_internal_utils:: CommandRunExt ;
14- use camino:: { Utf8Path , Utf8PathBuf } ;
12+ use anyhow:: { anyhow, bail, Context , Result } ;
13+ use bootc_utils:: CommandRunExt ;
1514use cap_std:: fs:: Dir ;
1615use cap_std_ext:: cap_std;
1716use chrono:: prelude:: * ;
@@ -506,6 +505,84 @@ impl Component for Efi {
506505 Ok ( meta)
507506 }
508507
508+ fn extend_payload ( & self , sysroot_path : & str , src_input : & str ) -> Result < Option < bool > > {
509+ let dest_efidir_base = Path :: new ( sysroot_path) . join ( "usr/lib/efi" ) . join ( "firmware" ) ;
510+
511+ let src_input_path = Path :: new ( src_input) ;
512+ let path_to_query = if src_input_path. is_dir ( ) {
513+ WalkDir :: new ( src_input_path)
514+ . into_iter ( )
515+ . filter_map ( |e| e. ok ( ) )
516+ . find ( |e| e. file_type ( ) . is_file ( ) )
517+ . map ( |e| e. path ( ) . to_path_buf ( ) )
518+ . ok_or_else ( || anyhow ! ( "No file found in directory {}" , src_input) ) ?
519+ } else {
520+ src_input_path. to_path_buf ( )
521+ } ;
522+
523+ let meta_from_src = packagesystem:: query_files ( sysroot_path, [ path_to_query] )
524+ . context ( format ! ( "Querying RPM metadata for {:?}" , src_input_path) ) ?;
525+
526+ let version_string_part =
527+ meta_from_src. version . split ( ',' ) . next ( ) . ok_or_else ( || {
528+ anyhow ! ( "RPM query returned an empty or malformed version string" )
529+ } ) ?;
530+
531+ let parts: Vec < & str > = version_string_part. split ( '-' ) . collect ( ) ;
532+ let ( pkg_name, version_release_str) = if parts. len ( ) >= 3 {
533+ (
534+ parts[ 0 ] . to_string ( ) ,
535+ format ! (
536+ "{}-{}" ,
537+ parts[ parts. len( ) - 2 ] ,
538+ parts[ parts. len( ) - 1 ]
539+ . split( '.' )
540+ . next( )
541+ . unwrap_or( parts[ parts. len( ) - 1 ] )
542+ ) ,
543+ )
544+ } else {
545+ anyhow:: bail!( "Unexpected RPM version string format" ) ;
546+ } ;
547+
548+ // Use the flattened destination path
549+ let final_dest_path = dest_efidir_base. join ( & pkg_name) . join ( & version_release_str) ;
550+ std:: fs:: create_dir_all ( & final_dest_path) ?;
551+
552+ let efi_dest_path = final_dest_path. join ( "EFI" ) ;
553+ std:: fs:: create_dir_all ( & efi_dest_path) ?;
554+
555+ // Copy the payload files
556+ let src_metadata = std:: fs:: metadata ( src_input_path) ?;
557+ if src_metadata. is_dir ( ) {
558+ Command :: new ( "cp" )
559+ . args ( [
560+ "-rp" ,
561+ & format ! ( "{}/." , src_input) ,
562+ efi_dest_path. to_str ( ) . unwrap ( ) ,
563+ ] )
564+ . run ( )
565+ . with_context ( || {
566+ format ! (
567+ "Failed to copy contents of {:?} to {:?}" ,
568+ src_input, & efi_dest_path
569+ )
570+ } ) ?;
571+ } else {
572+ Command :: new ( "cp" )
573+ . args ( [ "-p" , src_input, efi_dest_path. to_str ( ) . unwrap ( ) ] )
574+ . run ( ) ?;
575+ }
576+
577+ // Create the metadata file for the firmware
578+ let firmware_meta_path = final_dest_path. join ( "EFI.json" ) ;
579+ let meta_file = std:: fs:: File :: create ( firmware_meta_path) ?;
580+ serde_json:: to_writer ( meta_file, & meta_from_src) ?;
581+ log:: debug!( "Wrote firmware metadata for {}" , pkg_name) ;
582+
583+ Ok ( Some ( true ) )
584+ }
585+
509586 fn query_update ( & self , sysroot : & openat:: Dir ) -> Result < Option < ContentMetadata > > {
510587 get_component_update ( sysroot, self )
511588 }
@@ -858,6 +935,171 @@ Boot0003* test";
858935 paths,
859936 [ "usr/lib/efi/FOO/1.1/EFI" , "usr/lib/efi/BAR/1.1/EFI" ]
860937 ) ;
938+ }
939+ #[ test]
940+ fn test_extend_payload ( ) -> Result < ( ) > {
941+ use std:: fs;
942+ use tempfile:: TempDir ;
943+
944+ let temp_sysroot = TempDir :: new ( ) ?;
945+ let temp_src = TempDir :: new ( ) ?;
946+
947+ let sysroot_path = temp_sysroot. path ( ) . to_str ( ) . unwrap ( ) ;
948+ let src_path = temp_src. path ( ) . to_str ( ) . unwrap ( ) ;
949+
950+ // mockup data source: /usr/share/uboot/rpi/
951+ // content: u-boot.bin, overlays/i2c.dtb
952+ // mockup rpm: uboot-images-2023.04-2.fc42.noarch
953+ // mockup rpm_db: /usr/lib/sysimage/rpm/Packages
954+ let src_uboot_dir = temp_src
955+ . path ( )
956+ . join ( "usr" )
957+ . join ( "share" )
958+ . join ( "uboot" )
959+ . join ( "rpi" ) ;
960+ fs:: create_dir_all ( & src_uboot_dir) ?;
961+
962+ let src_overlays_dir = src_uboot_dir. join ( "overlays" ) ;
963+ fs:: create_dir_all ( & src_overlays_dir) ?;
964+
965+ // Create content files
966+ let uboot_bin = src_uboot_dir. join ( "u-boot.bin" ) ;
967+ fs:: write ( & uboot_bin, b"uboot binary content" ) ?;
968+ let overlay_dtb = src_overlays_dir. join ( "i2c.dtb" ) ;
969+ fs:: write ( & overlay_dtb, b"device tree overlay content" ) ?;
970+
971+ // Create a mockup RPM database structure
972+ let rpm_db_dir = temp_sysroot
973+ . path ( )
974+ . join ( "usr" )
975+ . join ( "lib" )
976+ . join ( "sysimage" )
977+ . join ( "rpm" ) ;
978+ fs:: create_dir_all ( & rpm_db_dir) ?;
979+ fs:: write ( rpm_db_dir. join ( "Packages" ) , b"fake rpm database file" ) ?;
980+
981+ // Create a mock rpm script that returns uboot-images package data
982+ let mock_rpm_dir = TempDir :: new ( ) ?;
983+ let mock_rpm_script = mock_rpm_dir. path ( ) . join ( "rpm" ) ;
984+
985+ let mock_script_content = r#"#!/bin/bash
986+ # Mock rpm script for testing
987+ if [[ "$*" == *"-q"* ]] && [[ "$*" == *"-f"* ]]; then
988+ # Return mock uboot-images package data in the expected format: nevra,buildtime
989+ echo "uboot-images-2023.04-2.fc42.noarch,1681234567"
990+ exit 0
991+ fi
992+ # For any other rpm command, just fail
993+ exit 1
994+ "#
995+ . to_string ( ) ;
996+
997+ fs:: write ( & mock_rpm_script, mock_script_content) ?;
998+
999+ #[ cfg( unix) ]
1000+ {
1001+ use std:: os:: unix:: fs:: PermissionsExt ;
1002+ let mut perms = fs:: metadata ( & mock_rpm_script) ?. permissions ( ) ;
1003+ perms. set_mode ( 0o755 ) ;
1004+ fs:: set_permissions ( & mock_rpm_script, perms) ?;
1005+ }
1006+
1007+ let original_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
1008+ let new_path = format ! ( "{}:{}" , mock_rpm_dir. path( ) . display( ) , original_path) ;
1009+ std:: env:: set_var ( "PATH" , & new_path) ;
1010+
1011+ // Test extend_payload
1012+ let efi_component = Efi :: default ( ) ;
1013+
1014+ let result = efi_component
1015+ . extend_payload ( sysroot_path, & format ! ( "{}/usr/share/uboot/rpi" , src_path) ) ;
1016+
1017+ // validation beigins
1018+ std:: env:: set_var ( "PATH" , & original_path) ;
1019+ match result {
1020+ Ok ( Some ( true ) ) => {
1021+ // Verify the files were copied to the right location
1022+ let firmware_base = temp_sysroot
1023+ . path ( )
1024+ . join ( "usr" )
1025+ . join ( "lib" )
1026+ . join ( "efi" )
1027+ . join ( "firmware" ) ;
1028+ assert ! (
1029+ firmware_base. exists( ) ,
1030+ "Firmware base directory should be created"
1031+ ) ;
1032+
1033+ // Look for the uboot package directory (package name is extracted from first part)
1034+ // From "uboot-images-2023.04-2.fc42.noarch" -> package: "uboot", version: "2023.04-2"
1035+ let uboot_dir = firmware_base. join ( "uboot" ) . join ( "2023.04-2" ) ;
1036+ assert ! (
1037+ uboot_dir. exists( ) ,
1038+ "Package directory uboot/2023.04-2 should be created"
1039+ ) ;
1040+
1041+ // Files should be copied to the EFI subdirectory
1042+ let efi_dir = uboot_dir. join ( "EFI" ) ;
1043+ assert ! ( efi_dir. exists( ) , "EFI directory should be created" ) ;
1044+
1045+ // Verify that u-boot.bin was copied to EFI subdirectory
1046+ let copied_uboot_bin = efi_dir. join ( "u-boot.bin" ) ;
1047+ assert ! ( copied_uboot_bin. exists( ) , "u-boot.bin should be copied" ) ;
1048+ let uboot_content = fs:: read_to_string ( & copied_uboot_bin) ?;
1049+ assert_eq ! (
1050+ uboot_content, "uboot binary content" ,
1051+ "u-boot.bin content should be preserved"
1052+ ) ;
1053+
1054+ // Verify that overlays directory and i2c.dtb were copied to EFI subdirectory
1055+ let copied_overlays_dir = efi_dir. join ( "overlays" ) ;
1056+ assert ! (
1057+ copied_overlays_dir. exists( ) ,
1058+ "overlays directory should be copied"
1059+ ) ;
1060+
1061+ let copied_overlay_dtb = copied_overlays_dir. join ( "i2c.dtb" ) ;
1062+ assert ! ( copied_overlay_dtb. exists( ) , "i2c.dtb should be copied" ) ;
1063+ let overlay_content = fs:: read_to_string ( & copied_overlay_dtb) ?;
1064+ assert_eq ! (
1065+ overlay_content, "device tree overlay content" ,
1066+ "i2c.dtb content should be preserved"
1067+ ) ;
1068+
1069+ // Verify the EFI.json metadata
1070+ let metadata_file = uboot_dir. join ( "EFI.json" ) ;
1071+ assert ! (
1072+ metadata_file. exists( ) ,
1073+ "EFI.json metadata file should be created"
1074+ ) ;
1075+ let metadata_content = fs:: read_to_string ( & metadata_file) ?;
1076+ let parsed: ContentMetadata = serde_json:: from_str ( & metadata_content) ?;
1077+ assert ! (
1078+ parsed
1079+ . version
1080+ . contains( "uboot-images-2023.04-2.fc42.noarch" ) ,
1081+ "Metadata should contain uboot package"
1082+ ) ;
1083+
1084+ println ! ( "extend_payload test completed successfully!" ) ;
1085+ println ! ( "✓ Files copied to: {:?}" , efi_dir) ;
1086+ println ! ( "✓ u-boot.bin: {:?}" , copied_uboot_bin) ;
1087+ println ! ( "✓ overlays/i2c.dtb: {:?}" , copied_overlay_dtb) ;
1088+ println ! ( "✓ Metadata created: {:?}" , metadata_file) ;
1089+ println ! ( "✓ Package version: {}" , parsed. version) ;
1090+ }
1091+ Ok ( Some ( false ) ) => {
1092+ panic ! ( "extend_payload returned false - expected success" ) ;
1093+ }
1094+ Ok ( None ) => {
1095+ panic ! ( "extend_payload returned None - expected success" ) ;
1096+ }
1097+ Err ( e) => {
1098+ panic ! ( "extend_payload failed when it should have succeeded: {}" , e) ;
1099+ }
1100+ }
1101+
8611102 Ok ( ( ) )
8621103 }
8631104}
1105+
0 commit comments