@@ -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,99 @@ 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+ // Clean up any existing firmware versions for this package to ensure only one version
549+ let pkg_firmware_dir = dest_efidir_base. join ( & pkg_name) ;
550+ if pkg_firmware_dir. exists ( ) {
551+ log:: debug!(
552+ "Removing existing firmware versions for package: {}" ,
553+ pkg_name
554+ ) ;
555+ std:: fs:: remove_dir_all ( & pkg_firmware_dir) . with_context ( || {
556+ format ! (
557+ "Failed to remove existing firmware directory {:?}" ,
558+ pkg_firmware_dir
559+ )
560+ } ) ?;
561+ }
562+
563+ // Use the flattened destination path
564+ let final_dest_path = dest_efidir_base. join ( & pkg_name) . join ( & version_release_str) ;
565+ std:: fs:: create_dir_all ( & final_dest_path) ?;
566+
567+ let efi_dest_path = final_dest_path. join ( "EFI" ) ;
568+ std:: fs:: create_dir_all ( & efi_dest_path) ?;
569+
570+ // Copy the payload files
571+ let src_metadata = std:: fs:: metadata ( src_input_path) ?;
572+ if src_metadata. is_dir ( ) {
573+ Command :: new ( "cp" )
574+ . args ( [
575+ "-rp" ,
576+ & format ! ( "{}/." , src_input) ,
577+ efi_dest_path. to_str ( ) . unwrap ( ) ,
578+ ] )
579+ . run ( )
580+ . with_context ( || {
581+ format ! (
582+ "Failed to copy contents of {:?} to {:?}" ,
583+ src_input, & efi_dest_path
584+ )
585+ } ) ?;
586+ } else {
587+ Command :: new ( "cp" )
588+ . args ( [ "-p" , src_input, efi_dest_path. to_str ( ) . unwrap ( ) ] )
589+ . run ( ) ?;
590+ }
591+
592+ // Create the metadata file for the firmware
593+ let firmware_meta_path = final_dest_path. join ( "EFI.json" ) ;
594+ let meta_file = std:: fs:: File :: create ( firmware_meta_path) ?;
595+ serde_json:: to_writer ( meta_file, & meta_from_src) ?;
596+ log:: debug!( "Wrote firmware metadata for {}" , pkg_name) ;
597+
598+ Ok ( Some ( true ) )
599+ }
600+
509601 fn query_update ( & self , sysroot : & openat:: Dir ) -> Result < Option < ContentMetadata > > {
510602 get_component_update ( sysroot, self )
511603 }
@@ -858,6 +950,171 @@ Boot0003* test";
858950 paths,
859951 [ "usr/lib/efi/FOO/1.1/EFI" , "usr/lib/efi/BAR/1.1/EFI" ]
860952 ) ;
953+ }
954+ #[ test]
955+ fn test_extend_payload ( ) -> Result < ( ) > {
956+ use std:: fs;
957+ use tempfile:: TempDir ;
958+
959+ let temp_sysroot = TempDir :: new ( ) ?;
960+ let temp_src = TempDir :: new ( ) ?;
961+
962+ let sysroot_path = temp_sysroot. path ( ) . to_str ( ) . unwrap ( ) ;
963+ let src_path = temp_src. path ( ) . to_str ( ) . unwrap ( ) ;
964+
965+ // mockup data source: /usr/share/uboot/rpi/
966+ // content: u-boot.bin, overlays/i2c.dtb
967+ // mockup rpm: uboot-images-2023.04-2.fc42.noarch
968+ // mockup rpm_db: /usr/lib/sysimage/rpm/Packages
969+ let src_uboot_dir = temp_src
970+ . path ( )
971+ . join ( "usr" )
972+ . join ( "share" )
973+ . join ( "uboot" )
974+ . join ( "rpi" ) ;
975+ fs:: create_dir_all ( & src_uboot_dir) ?;
976+
977+ let src_overlays_dir = src_uboot_dir. join ( "overlays" ) ;
978+ fs:: create_dir_all ( & src_overlays_dir) ?;
979+
980+ // Create content files
981+ let uboot_bin = src_uboot_dir. join ( "u-boot.bin" ) ;
982+ fs:: write ( & uboot_bin, b"uboot binary content" ) ?;
983+ let overlay_dtb = src_overlays_dir. join ( "i2c.dtb" ) ;
984+ fs:: write ( & overlay_dtb, b"device tree overlay content" ) ?;
985+
986+ // Create a mockup RPM database structure
987+ let rpm_db_dir = temp_sysroot
988+ . path ( )
989+ . join ( "usr" )
990+ . join ( "lib" )
991+ . join ( "sysimage" )
992+ . join ( "rpm" ) ;
993+ fs:: create_dir_all ( & rpm_db_dir) ?;
994+ fs:: write ( rpm_db_dir. join ( "Packages" ) , b"fake rpm database file" ) ?;
995+
996+ // Create a mock rpm script that returns uboot-images package data
997+ let mock_rpm_dir = TempDir :: new ( ) ?;
998+ let mock_rpm_script = mock_rpm_dir. path ( ) . join ( "rpm" ) ;
999+
1000+ let mock_script_content = r#"#!/bin/bash
1001+ # Mock rpm script for testing
1002+ if [[ "$*" == *"-q"* ]] && [[ "$*" == *"-f"* ]]; then
1003+ # Return mock uboot-images package data in the expected format: nevra,buildtime
1004+ echo "uboot-images-2023.04-2.fc42.noarch,1681234567"
1005+ exit 0
1006+ fi
1007+ # For any other rpm command, just fail
1008+ exit 1
1009+ "#
1010+ . to_string ( ) ;
1011+
1012+ fs:: write ( & mock_rpm_script, mock_script_content) ?;
1013+
1014+ #[ cfg( unix) ]
1015+ {
1016+ use std:: os:: unix:: fs:: PermissionsExt ;
1017+ let mut perms = fs:: metadata ( & mock_rpm_script) ?. permissions ( ) ;
1018+ perms. set_mode ( 0o755 ) ;
1019+ fs:: set_permissions ( & mock_rpm_script, perms) ?;
1020+ }
1021+
1022+ let original_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
1023+ let new_path = format ! ( "{}:{}" , mock_rpm_dir. path( ) . display( ) , original_path) ;
1024+ std:: env:: set_var ( "PATH" , & new_path) ;
1025+
1026+ // Test extend_payload
1027+ let efi_component = Efi :: default ( ) ;
1028+
1029+ let result = efi_component
1030+ . extend_payload ( sysroot_path, & format ! ( "{}/usr/share/uboot/rpi" , src_path) ) ;
1031+
1032+ // validation beigins
1033+ std:: env:: set_var ( "PATH" , & original_path) ;
1034+ match result {
1035+ Ok ( Some ( true ) ) => {
1036+ // Verify the files were copied to the right location
1037+ let firmware_base = temp_sysroot
1038+ . path ( )
1039+ . join ( "usr" )
1040+ . join ( "lib" )
1041+ . join ( "efi" )
1042+ . join ( "firmware" ) ;
1043+ assert ! (
1044+ firmware_base. exists( ) ,
1045+ "Firmware base directory should be created"
1046+ ) ;
1047+
1048+ // Look for the uboot package directory (package name is extracted from first part)
1049+ // From "uboot-images-2023.04-2.fc42.noarch" -> package: "uboot", version: "2023.04-2"
1050+ let uboot_dir = firmware_base. join ( "uboot" ) . join ( "2023.04-2" ) ;
1051+ assert ! (
1052+ uboot_dir. exists( ) ,
1053+ "Package directory uboot/2023.04-2 should be created"
1054+ ) ;
1055+
1056+ // Files should be copied to the EFI subdirectory
1057+ let efi_dir = uboot_dir. join ( "EFI" ) ;
1058+ assert ! ( efi_dir. exists( ) , "EFI directory should be created" ) ;
1059+
1060+ // Verify that u-boot.bin was copied to EFI subdirectory
1061+ let copied_uboot_bin = efi_dir. join ( "u-boot.bin" ) ;
1062+ assert ! ( copied_uboot_bin. exists( ) , "u-boot.bin should be copied" ) ;
1063+ let uboot_content = fs:: read_to_string ( & copied_uboot_bin) ?;
1064+ assert_eq ! (
1065+ uboot_content, "uboot binary content" ,
1066+ "u-boot.bin content should be preserved"
1067+ ) ;
1068+
1069+ // Verify that overlays directory and i2c.dtb were copied to EFI subdirectory
1070+ let copied_overlays_dir = efi_dir. join ( "overlays" ) ;
1071+ assert ! (
1072+ copied_overlays_dir. exists( ) ,
1073+ "overlays directory should be copied"
1074+ ) ;
1075+
1076+ let copied_overlay_dtb = copied_overlays_dir. join ( "i2c.dtb" ) ;
1077+ assert ! ( copied_overlay_dtb. exists( ) , "i2c.dtb should be copied" ) ;
1078+ let overlay_content = fs:: read_to_string ( & copied_overlay_dtb) ?;
1079+ assert_eq ! (
1080+ overlay_content, "device tree overlay content" ,
1081+ "i2c.dtb content should be preserved"
1082+ ) ;
1083+
1084+ // Verify the EFI.json metadata
1085+ let metadata_file = uboot_dir. join ( "EFI.json" ) ;
1086+ assert ! (
1087+ metadata_file. exists( ) ,
1088+ "EFI.json metadata file should be created"
1089+ ) ;
1090+ let metadata_content = fs:: read_to_string ( & metadata_file) ?;
1091+ let parsed: ContentMetadata = serde_json:: from_str ( & metadata_content) ?;
1092+ assert ! (
1093+ parsed
1094+ . version
1095+ . contains( "uboot-images-2023.04-2.fc42.noarch" ) ,
1096+ "Metadata should contain uboot package"
1097+ ) ;
1098+
1099+ println ! ( "extend_payload test completed successfully!" ) ;
1100+ println ! ( "✓ Files copied to: {:?}" , efi_dir) ;
1101+ println ! ( "✓ u-boot.bin: {:?}" , copied_uboot_bin) ;
1102+ println ! ( "✓ overlays/i2c.dtb: {:?}" , copied_overlay_dtb) ;
1103+ println ! ( "✓ Metadata created: {:?}" , metadata_file) ;
1104+ println ! ( "✓ Package version: {}" , parsed. version) ;
1105+ }
1106+ Ok ( Some ( false ) ) => {
1107+ panic ! ( "extend_payload returned false - expected success" ) ;
1108+ }
1109+ Ok ( None ) => {
1110+ panic ! ( "extend_payload returned None - expected success" ) ;
1111+ }
1112+ Err ( e) => {
1113+ panic ! ( "extend_payload failed when it should have succeeded: {}" , e) ;
1114+ }
1115+ }
1116+
8611117 Ok ( ( ) )
8621118 }
8631119}
1120+
0 commit comments