@@ -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,172 @@ 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 = format ! (
986+ r#"#!/bin/bash
987+ # Mock rpm script for testing
988+ if [[ "$*" == *"-q"* ]] && [[ "$*" == *"-f"* ]]; then
989+ # Return mock uboot-images package data in the expected format: nevra,buildtime
990+ echo "uboot-images-2023.04-2.fc42.noarch,1681234567"
991+ exit 0
992+ fi
993+ # For any other rpm command, just fail
994+ exit 1
995+ "#
996+ ) ;
997+
998+ fs:: write ( & mock_rpm_script, mock_script_content) ?;
999+
1000+ #[ cfg( unix) ]
1001+ {
1002+ use std:: os:: unix:: fs:: PermissionsExt ;
1003+ let mut perms = fs:: metadata ( & mock_rpm_script) ?. permissions ( ) ;
1004+ perms. set_mode ( 0o755 ) ;
1005+ fs:: set_permissions ( & mock_rpm_script, perms) ?;
1006+ }
1007+
1008+ let original_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
1009+ let new_path = format ! ( "{}:{}" , mock_rpm_dir. path( ) . display( ) , original_path) ;
1010+ std:: env:: set_var ( "PATH" , & new_path) ;
1011+
1012+ // Test extend_payload
1013+ let efi_component = Efi :: default ( ) ;
1014+
1015+ let result = efi_component
1016+ . extend_payload ( sysroot_path, & format ! ( "{}/usr/share/uboot/rpi" , src_path) ) ;
1017+
1018+ // validation beigins
1019+ std:: env:: set_var ( "PATH" , & original_path) ;
1020+ match result {
1021+ Ok ( Some ( true ) ) => {
1022+ // Verify the files were copied to the right location
1023+ let firmware_base = temp_sysroot
1024+ . path ( )
1025+ . join ( "usr" )
1026+ . join ( "lib" )
1027+ . join ( "efi" )
1028+ . join ( "firmware" ) ;
1029+ assert ! (
1030+ firmware_base. exists( ) ,
1031+ "Firmware base directory should be created"
1032+ ) ;
1033+
1034+ // Look for the uboot package directory (package name is extracted from first part)
1035+ // From "uboot-images-2023.04-2.fc42.noarch" -> package: "uboot", version: "2023.04-2"
1036+ let uboot_dir = firmware_base. join ( "uboot" ) . join ( "2023.04-2" ) ;
1037+ assert ! (
1038+ uboot_dir. exists( ) ,
1039+ "Package directory uboot/2023.04-2 should be created"
1040+ ) ;
1041+
1042+ // Files should be copied to the EFI subdirectory
1043+ let efi_dir = uboot_dir. join ( "EFI" ) ;
1044+ assert ! ( efi_dir. exists( ) , "EFI directory should be created" ) ;
1045+
1046+ // Verify that u-boot.bin was copied to EFI subdirectory
1047+ let copied_uboot_bin = efi_dir. join ( "u-boot.bin" ) ;
1048+ assert ! ( copied_uboot_bin. exists( ) , "u-boot.bin should be copied" ) ;
1049+ let uboot_content = fs:: read_to_string ( & copied_uboot_bin) ?;
1050+ assert_eq ! (
1051+ uboot_content, "uboot binary content" ,
1052+ "u-boot.bin content should be preserved"
1053+ ) ;
1054+
1055+ // Verify that overlays directory and i2c.dtb were copied to EFI subdirectory
1056+ let copied_overlays_dir = efi_dir. join ( "overlays" ) ;
1057+ assert ! (
1058+ copied_overlays_dir. exists( ) ,
1059+ "overlays directory should be copied"
1060+ ) ;
1061+
1062+ let copied_overlay_dtb = copied_overlays_dir. join ( "i2c.dtb" ) ;
1063+ assert ! ( copied_overlay_dtb. exists( ) , "i2c.dtb should be copied" ) ;
1064+ let overlay_content = fs:: read_to_string ( & copied_overlay_dtb) ?;
1065+ assert_eq ! (
1066+ overlay_content, "device tree overlay content" ,
1067+ "i2c.dtb content should be preserved"
1068+ ) ;
1069+
1070+ // Verify the EFI.json metadata
1071+ let metadata_file = uboot_dir. join ( "EFI.json" ) ;
1072+ assert ! (
1073+ metadata_file. exists( ) ,
1074+ "EFI.json metadata file should be created"
1075+ ) ;
1076+ let metadata_content = fs:: read_to_string ( & metadata_file) ?;
1077+ let parsed: ContentMetadata = serde_json:: from_str ( & metadata_content) ?;
1078+ assert ! (
1079+ parsed
1080+ . version
1081+ . contains( "uboot-images-2023.04-2.fc42.noarch" ) ,
1082+ "Metadata should contain uboot package"
1083+ ) ;
1084+
1085+ println ! ( "extend_payload test completed successfully!" ) ;
1086+ println ! ( "✓ Files copied to: {:?}" , efi_dir) ;
1087+ println ! ( "✓ u-boot.bin: {:?}" , copied_uboot_bin) ;
1088+ println ! ( "✓ overlays/i2c.dtb: {:?}" , copied_overlay_dtb) ;
1089+ println ! ( "✓ Metadata created: {:?}" , metadata_file) ;
1090+ println ! ( "✓ Package version: {}" , parsed. version) ;
1091+ }
1092+ Ok ( Some ( false ) ) => {
1093+ panic ! ( "extend_payload returned false - expected success" ) ;
1094+ }
1095+ Ok ( None ) => {
1096+ panic ! ( "extend_payload returned None - expected success" ) ;
1097+ }
1098+ Err ( e) => {
1099+ panic ! ( "extend_payload failed when it should have succeeded: {}" , e) ;
1100+ }
1101+ }
1102+
8611103 Ok ( ( ) )
8621104 }
8631105}
1106+
0 commit comments