@@ -16,10 +16,13 @@ use cap_std::fs::Dir;
1616use cap_std_ext:: cap_std;
1717use chrono:: prelude:: * ;
1818use fn_error_context:: context;
19+ use log:: { info, warn} ;
1920use openat_ext:: OpenatDirExt ;
2021use os_release:: OsRelease ;
22+ use std:: io:: Write ;
2123use rustix:: fd:: BorrowedFd ;
2224use walkdir:: WalkDir ;
25+ use widestring:: U16CString ;
2326
2427use crate :: bootupd:: RootContext ;
2528use crate :: freezethaw:: fsfreeze_thaw_cycle;
@@ -46,10 +49,6 @@ pub(crate) const SHIM: &str = "shimx64.efi";
4649#[ cfg( target_arch = "riscv64" ) ]
4750pub ( crate ) const SHIM : & str = "shimriscv64.efi" ;
4851
49- // /// Systemd boot loader info EFI variable names
50- // const LOADER_INFO_VAR_STR: &str = "LoaderInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f";
51- // const STUB_INFO_VAR_STR: &str = "StubInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f";
52-
5352/// Return `true` if the system is booted via EFI
5453pub ( crate ) fn is_efi_booted ( ) -> Result < bool > {
5554 Path :: new ( "/sys/firmware/efi" )
@@ -152,6 +151,35 @@ impl Efi {
152151 clear_efi_target ( & product_name) ?;
153152 create_efi_boot_entry ( device, espdir, vendordir, & product_name)
154153 }
154+
155+ /// Find a kernel and optional initramfs/uki file in the update directory for systemd-boot
156+ fn find_kernel_and_initrd ( dir : & openat:: Dir ) -> Result < ( String , Option < String > ) > {
157+ let mut kernel: Option < String > = None ;
158+ let mut initrd: Option < String > = None ;
159+ log:: warn!( "Searching for kernel and initrd in update dir: {:?}" , dir. recover_path( ) ) ;
160+ for entry in dir. list_dir ( "." ) ? {
161+ log:: warn!( "Found entry: {:?}" , entry) ;
162+ let entry = entry?;
163+ let fname = entry. file_name ( ) . to_string_lossy ( ) ;
164+ if fname. starts_with ( "vmlinuz" ) || fname. ends_with ( ".efi" ) || fname. ends_with ( ".uki" ) {
165+ log:: warn!( "Found kernel/UKI file: {}" , fname) ;
166+ if kernel. is_some ( ) {
167+ log:: warn!( "Multiple kernel/UKI files found in update dir" ) ;
168+ bail ! ( "Multiple kernel/UKI files found in update dir" ) ;
169+ }
170+ kernel = Some ( fname. to_string ( ) ) ;
171+ } else if fname. starts_with ( "initrd" ) || fname. ends_with ( ".img" ) || fname. ends_with ( ".cpio.gz" ) {
172+ log:: warn!( "Found initramfs file: {}" , fname) ;
173+ if initrd. is_some ( ) {
174+ log:: warn!( "Multiple initramfs files found in update dir" ) ;
175+ bail ! ( "Multiple initramfs files found in update dir" ) ;
176+ }
177+ initrd = Some ( fname. to_string ( ) ) ;
178+ }
179+ }
180+ let kernel = kernel. ok_or_else ( || anyhow:: anyhow!( "No kernel or UKI file found in update dir" ) ) ?;
181+ Ok ( ( kernel, initrd) )
182+ }
155183}
156184
157185#[ context( "Get product name" ) ]
@@ -168,70 +196,6 @@ fn get_product_name(sysroot: &Dir) -> Result<String> {
168196 Ok ( release. name )
169197}
170198
171- // /// Convert a nul-terminated UTF-16 byte array to a String.
172- // fn string_from_utf16_bytes(slice: &[u8]) -> String {
173- // // For some reason, systemd appends 3 nul bytes after the string.
174- // // Drop the last byte if there's an odd number.
175- // let size = slice.len() / 2;
176- // let v: Vec<u16> = (0..size)
177- // .map(|i| u16::from_ne_bytes([slice[2 * i], slice[2 * i + 1]]))
178- // .collect();
179- // U16CString::from_vec(v).unwrap().to_string_lossy()
180- // }
181-
182- // /// Read a nul-terminated UTF-16 string from an EFI variable.
183- // fn read_efi_var_utf16_string(name: &str) -> Option<String> {
184- // let efivars = Path::new("/sys/firmware/efi/efivars");
185- // if !efivars.exists() {
186- // log::trace!("No efivars mount at {:?}", efivars);
187- // return None;
188- // }
189- // let path = efivars.join(name);
190- // if !path.exists() {
191- // log::trace!("No EFI variable {name}");
192- // return None;
193- // }
194- // match std::fs::read(&path) {
195- // Ok(buf) => {
196- // // Skip the first 4 bytes, those are the EFI variable attributes.
197- // if buf.len() < 4 {
198- // log::warn!("Read less than 4 bytes from {:?}", path);
199- // return None;
200- // }
201- // Some(string_from_utf16_bytes(&buf[4..]))
202- // }
203- // Err(reason) => {
204- // log::warn!("Failed reading {:?}: {reason}", path);
205- // None
206- // }
207- // }
208- // }
209-
210- // /// Read the LoaderInfo EFI variable if it exists.
211- // fn get_loader_info() -> Option<String> {
212- // read_efi_var_utf16_string(LOADER_INFO_VAR_STR)
213- // }
214-
215- // /// Read the StubInfo EFI variable if it exists.
216- // fn get_stub_info() -> Option<String> {
217- // read_efi_var_utf16_string(STUB_INFO_VAR_STR)
218- // }
219-
220- // /// Whether to skip adoption if a systemd bootloader is found.
221- // fn skip_systemd_bootloaders() -> bool {
222- // if let Some(loader_info) = get_loader_info() {
223- // if loader_info.starts_with("systemd") {
224- // log::trace!("Skipping adoption for {:?}", loader_info);
225- // return true;
226- // }
227- // }
228- // if let Some(stub_info) = get_stub_info() {
229- // log::trace!("Skipping adoption for {:?}", stub_info);
230- // return true;
231- // }
232- // false
233- // }
234-
235199impl Component for Efi {
236200 fn name ( & self ) -> & ' static str {
237201 "EFI"
@@ -342,10 +306,11 @@ impl Component for Efi {
342306 device : & str ,
343307 update_firmware : bool ,
344308 ) -> Result < InstalledContent > {
309+ log:: warn!( "Installing component: {}" , self . name( ) ) ;
345310 let Some ( meta) = get_component_update ( src_root, self ) ? else {
346311 anyhow:: bail!( "No update metadata for component {} found" , self . name( ) ) ;
347312 } ;
348- log:: debug !( "Found metadata {}" , meta. version) ;
313+ log:: warn !( "Found metadata {}" , meta. version) ;
349314 let srcdir_name = component_updatedirname ( self ) ;
350315 let ft = crate :: filetree:: FileTree :: new_from_dir ( & src_root. sub_dir ( & srcdir_name) ?) ?;
351316
@@ -364,23 +329,94 @@ impl Component for Efi {
364329 self . mount_esp_device ( Path :: new ( dest_root) , Path :: new ( & esp_device) ) ?
365330 } ;
366331
332+ let destpath_clone = destpath. clone ( ) ;
333+
367334 let destd = & openat:: Dir :: open ( & destpath)
368335 . with_context ( || format ! ( "opening dest dir {}" , destpath. display( ) ) ) ?;
369336 validate_esp_fstype ( destd) ?;
370337
338+ // let using_systemd_boot = skip_systemd_bootloaders();
339+ let using_systemd_boot = true ;
340+
371341 // TODO - add some sort of API that allows directly setting the working
372342 // directory to a file descriptor.
373343 std:: process:: Command :: new ( "cp" )
374344 . args ( [ "-rp" , "--reflink=auto" ] )
375345 . arg ( & srcdir_name)
376- . arg ( destpath)
346+ . arg ( & destpath)
377347 . current_dir ( format ! ( "/proc/self/fd/{}" , src_root. as_raw_fd( ) ) )
378348 . run ( ) ?;
379- if update_firmware {
349+
350+ // Configure systemd-boot loader config/entry
351+ if using_systemd_boot {
352+ let loader_dir = destpath. join ( "loader" ) ;
353+ if !loader_dir. exists ( ) {
354+ log:: warn!( "Creating systemd-boot loader directory at {}" , loader_dir. display( ) ) ;
355+ std:: fs:: create_dir_all ( & loader_dir)
356+ . with_context ( || format ! ( "creating systemd-boot loader directory {}" , loader_dir. display( ) ) ) ?;
357+ }
358+ let entries_dir = loader_dir. join ( "entries" ) ;
359+ log:: warn!( "Using systemd-boot, creating entries dir at {}" , entries_dir. display( ) ) ;
360+ std:: fs:: create_dir_all ( & entries_dir)
361+ . with_context ( || format ! ( "creating entries dir {}" , entries_dir. display( ) ) ) ?;
362+ log:: warn!( "Installing systemd-boot entry in {}/bootupd.conf" , entries_dir. display( ) ) ;
363+ // Write loader.conf if it doesn't exist
364+ let loader_conf_path = loader_dir. join ( "loader.conf" ) ;
365+ if !loader_conf_path. exists ( ) {
366+ let mut loader_conf = std:: fs:: File :: create ( & loader_conf_path)
367+ . with_context ( || format ! ( "creating loader.conf at {}" , loader_conf_path. display( ) ) ) ?;
368+ writeln ! ( loader_conf, "default auto" ) ?;
369+ writeln ! ( loader_conf, "timeout 20" ) ?;
370+ writeln ! ( loader_conf, "editor no" ) ?;
371+ }
372+ log:: warn!( "Installed: {}/bootupd.conf" , entries_dir. display( ) ) ;
373+
374+ // let sysroot = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
375+ // let product_name = get_product_name(&sysroot).unwrap_or("Unknown Product".to_string());
376+ // log::warn!("Get product name: '{product_name}'");
377+
378+ // Find the kernel/UKI and optional initramfs in the update dir
379+ log:: warn!(
380+ "Searching for kernel and initrd in update dir: {:?}" ,
381+ crate :: model:: BOOTUPD_UPDATES_DIR
382+ ) ;
383+ let update_dir = src_root. sub_dir ( & srcdir_name)
384+ . context ( "opening update dir" ) ?;
385+ log:: debug!( "Searching for kernel and initrd in update dir: {:?}" , update_dir. recover_path( ) ) ;
386+ let ( kernel_file, initrd_file) = Self :: find_kernel_and_initrd ( & update_dir) ?;
387+
388+ log:: warn!(
389+ "Installing systemd-boot entry for kernel: {}, initrd: {:?}" ,
390+ kernel_file, initrd_file
391+ ) ;
392+ crate :: systemd_boot_configs:: install (
393+ & destd,
394+ true ,
395+ "Unknown Product" ,
396+ & kernel_file,
397+ initrd_file. as_deref ( ) ,
398+ ) . context ( "installing systemd-boot entry" ) ?;
399+ }
400+
401+ // If using systemd-boot, run `bootctl install` to set up the bootloader.
402+ if using_systemd_boot {
403+ log:: warn!( "Using systemd-boot, running bootctl install" ) ;
404+ let status = std:: process:: Command :: new ( "bootctl" )
405+ . args ( [ "install" , "--esp-path" , destpath_clone. to_str ( ) . unwrap ( ) ] )
406+ . status ( )
407+ . context ( "running bootctl install" ) ?;
408+ log:: warn!( "bootctl install status: {}" , status) ;
409+ if !status. success ( ) {
410+ bail ! ( "bootctl install failed with status: {}" , status) ;
411+ }
412+ }
413+
414+ if update_firmware && !using_systemd_boot {
380415 if let Some ( vendordir) = self . get_efi_vendor ( & src_root) ? {
381416 self . update_firmware ( device, destd, & vendordir) ?
382417 }
383418 }
419+
384420 Ok ( InstalledContent {
385421 meta,
386422 filetree : Some ( ft) ,
@@ -714,9 +750,12 @@ fn get_efi_component_from_usr<'a>(
714750 . filter_map ( |entry| {
715751 let entry = entry. ok ( ) ?;
716752 if !entry. file_type ( ) . is_dir ( ) || entry. file_name ( ) != "EFI" {
753+ info ! ( "Skipping non-directory or non-EFI entry: {}" , entry. path( ) . display( ) ) ;
717754 return None ;
718755 }
719756
757+ info ! ( "Found EFI component at {}" , entry. path( ) . display( ) ) ;
758+
720759 let abs_path = entry. path ( ) ;
721760 let rel_path = abs_path. strip_prefix ( sysroot) . ok ( ) ?;
722761 let utf8_rel_path = Utf8PathBuf :: from_path_buf ( rel_path. to_path_buf ( ) ) . ok ( ) ?;
0 commit comments