@@ -21,6 +21,11 @@ use openat_ext::OpenatDirExt;
2121use os_release:: OsRelease ;
2222use rustix:: { fd:: AsFd , fd:: BorrowedFd , fs:: StatVfsMountFlags } ;
2323use walkdir:: WalkDir ;
24+ #[ cfg( target_os = "linux" ) ]
25+ use rustix:: mount:: {
26+ fsconfig_create, fsconfig_set_path, fsmount, fsopen, move_mount, FsMountFlags , FsOpenFlags ,
27+ MoveMountFlags , MountAttrFlags , UnmountFlags ,
28+ } ;
2429use widestring:: U16CString ;
2530
2631use crate :: bootupd:: RootContext ;
@@ -65,11 +70,36 @@ pub(crate) fn is_efi_booted() -> Result<bool> {
6570 . map_err ( Into :: into)
6671}
6772
73+ #[ cfg( target_os = "linux" ) ]
74+ fn mount_esp ( esp_device : & Path , target : & Path ) -> Result < ( ) > {
75+ use rustix:: fs:: CWD ;
76+
77+ let fs_fd = fsopen ( "vfat" , FsOpenFlags :: empty ( ) ) . context ( "fsopen vfat" ) ?;
78+ fsconfig_set_path ( fs_fd. as_fd ( ) , "source" , esp_device, CWD ) . context ( "fsconfig_set_path source" ) ?;
79+ fsconfig_create ( fs_fd. as_fd ( ) ) . context ( "fsconfig_create" ) ?;
80+ let mount_fd =
81+ fsmount ( fs_fd. as_fd ( ) , FsMountFlags :: empty ( ) , MountAttrFlags :: empty ( ) ) . context ( "fsmount" ) ?;
82+ let target_dir = std:: fs:: File :: open ( target) . context ( "open target dir for move_mount" ) ?;
83+ let target_fd = unsafe { BorrowedFd :: borrow_raw ( target_dir. as_raw_fd ( ) ) } ;
84+ move_mount (
85+ mount_fd. as_fd ( ) ,
86+ "" ,
87+ target_fd,
88+ "." ,
89+ MoveMountFlags :: MOVE_MOUNT_F_EMPTY_PATH ,
90+ )
91+ . context ( "move_mount" ) ?;
92+ Ok ( ( ) )
93+ }
94+
95+ struct Mount {
96+ path : PathBuf ,
97+ owned : bool ,
98+ }
99+
68100#[ derive( Default ) ]
69101pub ( crate ) struct Efi {
70- mountpoint : RefCell < Option < PathBuf > > ,
71- /// Track whether we mounted the ESP ourselves (true) or found it pre-mounted (false)
72- did_mount : RefCell < bool > ,
102+ mountpoint : RefCell < Option < Mount > > ,
73103}
74104
75105impl Efi {
@@ -90,19 +120,18 @@ impl Efi {
90120 break ;
91121 }
92122 }
93-
94- // Only borrow mutably if we found a mount point
95123 if let Some ( mnt) = found_mount {
96124 log:: debug!( "Reusing existing mount point {mnt:?}" ) ;
97- * self . mountpoint . borrow_mut ( ) = Some ( mnt. clone ( ) ) ;
98- * self . did_mount . borrow_mut ( ) = false ; // We didn't mount it
125+ * self . mountpoint . borrow_mut ( ) = Some ( Mount {
126+ path : mnt. clone ( ) ,
127+ owned : false ,
128+ } ) ;
99129 Ok ( Some ( mnt) )
100130 } else {
101131 Ok ( None )
102132 }
103133 }
104134
105- // Mount the passed esp_device, return mount point
106135 pub ( crate ) fn mount_esp_device ( & self , root : & Path , esp_device : & Path ) -> Result < PathBuf > {
107136 let mut mountpoint = None ;
108137
@@ -111,8 +140,16 @@ impl Efi {
111140 if !mnt. exists ( ) {
112141 continue ;
113142 }
143+ #[ cfg( target_os = "linux" ) ]
144+ if mount_esp ( esp_device, & mnt) . is_ok ( ) {
145+ log:: debug!( "Mounted at {mnt:?}" ) ;
146+ mountpoint = Some ( mnt) ;
147+ break ;
148+ }
149+ #[ cfg( target_os = "linux" ) ]
150+ log:: trace!( "Mount failed, falling back to mount(8)" ) ;
114151 std:: process:: Command :: new ( "mount" )
115- . arg ( & esp_device)
152+ . arg ( esp_device)
116153 . arg ( & mnt)
117154 . run_inherited ( )
118155 . with_context ( || format ! ( "Failed to mount {:?}" , esp_device) ) ?;
@@ -121,17 +158,19 @@ impl Efi {
121158 break ;
122159 }
123160 let mnt = mountpoint. ok_or_else ( || anyhow:: anyhow!( "No mount point found" ) ) ?;
124- * self . mountpoint . borrow_mut ( ) = Some ( mnt. clone ( ) ) ;
125- * self . did_mount . borrow_mut ( ) = true ; // We mounted it ourselves
161+ * self . mountpoint . borrow_mut ( ) = Some ( Mount {
162+ path : mnt. clone ( ) ,
163+ owned : true ,
164+ } ) ;
126165 Ok ( mnt)
127166 }
128167
129168 // Firstly check if esp is already mounted, then mount the passed esp device
130169 pub ( crate ) fn ensure_mounted_esp ( & self , root : & Path , esp_device : & Path ) -> Result < PathBuf > {
131- if let Some ( mountpoint ) = self . mountpoint . borrow ( ) . as_deref ( ) {
132- return Ok ( mountpoint . to_owned ( ) ) ;
170+ if let Some ( mount ) = self . mountpoint . borrow ( ) . as_ref ( ) {
171+ return Ok ( mount . path . clone ( ) ) ;
133172 }
134- let destdir = if let Some ( destdir) = self . get_mounted_esp ( Path :: new ( root) ) ? {
173+ let destdir = if let Some ( destdir) = self . get_mounted_esp ( root) ? {
135174 destdir
136175 } else {
137176 self . mount_esp_device ( root, esp_device) ?
@@ -141,14 +180,32 @@ impl Efi {
141180
142181 fn unmount ( & self ) -> Result < ( ) > {
143182 // Only unmount if we mounted it ourselves
144- if * self . did_mount . borrow ( ) {
183+ let should_unmount = self
184+ . mountpoint
185+ . borrow ( )
186+ . as_ref ( )
187+ . map ( |m| m. owned )
188+ . unwrap_or ( false ) ;
189+ if should_unmount {
145190 if let Some ( mount) = self . mountpoint . borrow_mut ( ) . take ( ) {
146- Command :: new ( "umount" )
147- . arg ( & mount)
148- . run_inherited ( )
149- . with_context ( || format ! ( "Failed to unmount {mount:?}" ) ) ?;
150- log:: trace!( "Unmounted" ) ;
151- * self . did_mount . borrow_mut ( ) = false ;
191+ #[ cfg( target_os = "linux" ) ]
192+ if rustix:: mount:: unmount ( & mount. path , UnmountFlags :: empty ( ) ) . is_ok ( ) {
193+ log:: trace!( "Unmounted (new mount API)" ) ;
194+ } else {
195+ Command :: new ( "umount" )
196+ . arg ( & mount. path )
197+ . run_inherited ( )
198+ . with_context ( || format ! ( "Failed to unmount {:?}" , mount. path) ) ?;
199+ log:: trace!( "Unmounted" ) ;
200+ }
201+ #[ cfg( not( target_os = "linux" ) ) ]
202+ {
203+ Command :: new ( "umount" )
204+ . arg ( & mount. path )
205+ . run_inherited ( )
206+ . with_context ( || format ! ( "Failed to unmount {:?}" , mount. path) ) ?;
207+ log:: trace!( "Unmounted" ) ;
208+ }
152209 }
153210 }
154211 Ok ( ( ) )
@@ -191,19 +248,17 @@ impl Efi {
191248 clear_efi_target ( & product_name) ?;
192249 create_efi_boot_entry ( device, esp_part_num. trim ( ) , & loader, & product_name)
193250 }
194-
195- /// Shared helper to copy EFI components to a single ESP
196251 fn copy_efi_components_to_esp (
197252 & self ,
198253 sysroot_dir : & openat:: Dir ,
254+ esp_dir : & openat:: Dir ,
199255 esp_path : & Path ,
200256 efi_components : & [ EFIComponent ] ,
201257 ) -> Result < ( ) > {
202258 let dest_str = esp_path
203259 . to_str ( )
204- . context ( "ESP path contains invalid UTF-8" ) ?;
260+ . with_context ( || format ! ( "Invalid UTF-8: {}" , esp_path . display ( ) ) ) ?;
205261
206- // Copy each component
207262 for efi_comp in efi_components {
208263 log:: info!(
209264 "Copying EFI component {} version {} to ESP at {}" ,
@@ -221,51 +276,41 @@ impl Efi {
221276 } ) ?;
222277 }
223278
224- // Sync filesystem
225- let efidir =
226- openat:: Dir :: open ( & esp_path. join ( "EFI" ) ) . context ( "Opening EFI directory for sync" ) ?;
227- fsfreeze_thaw_cycle ( efidir. open_file ( "." ) ?) ?;
279+ // Sync the whole ESP filesystem
280+ fsfreeze_thaw_cycle ( esp_dir. open_file ( "." ) ?) ?;
228281
229282 Ok ( ( ) )
230283 }
231284
232- /// Copy from /usr/lib/efi to boot/ESP.
233- fn package_mode_copy_to_boot_impl ( & self ) -> Result < ( ) > {
234- let sysroot = Path :: new ( "/" ) ;
235- let sysroot_path =
236- Utf8Path :: from_path ( sysroot) . context ( "Sysroot path is not valid UTF-8" ) ?;
285+ /// Copy from /usr/lib/efi to boot/ESP. Caller provides sysroot (e.g. for recovery or tests).
286+ fn package_mode_copy_to_boot_impl ( & self , sysroot : & Path ) -> Result < ( ) > {
287+ let sysroot_path = Utf8Path :: from_path ( sysroot)
288+ . with_context ( || format ! ( "Invalid UTF-8: {}" , sysroot. display( ) ) ) ?;
237289
238290 let efi_comps = match get_efi_component_from_usr ( sysroot_path, EFILIB ) ? {
239291 Some ( comps) if !comps. is_empty ( ) => comps,
240- _ => {
241- log:: debug!( "No EFI components found in /usr/lib/efi" ) ;
242- return Ok ( ( ) ) ;
243- }
292+ _ => anyhow:: bail!( "No EFI components found in /usr/lib/efi" ) ,
244293 } ;
245294
246- let sysroot_dir = openat:: Dir :: open ( sysroot) . context ( "Opening sysroot for reading" ) ?;
247-
248295 // First try to use an already mounted ESP
249296 let esp_path = if let Some ( mounted_esp) = self . get_mounted_esp ( sysroot) ? {
250297 mounted_esp
251298 } else {
252- // If not mounted, find ESP from devices
253299 let devices = blockdev:: get_devices ( sysroot) ?;
254300 let Some ( esp_devices) = blockdev:: find_colocated_esps ( & devices) ? else {
255301 anyhow:: bail!( "No ESP found" ) ;
256302 } ;
257-
258- let esp_device = esp_devices
259- . first ( )
260- . ok_or_else ( || anyhow:: anyhow!( "No ESP device found" ) ) ?;
303+ // find_colocated_esps returns Some only with a non-empty vec
304+ let esp_device = esp_devices. first ( ) . unwrap ( ) ;
261305 self . ensure_mounted_esp ( sysroot, Path :: new ( esp_device) ) ?
262306 } ;
263307
264308 let esp_dir = openat:: Dir :: open ( & esp_path)
265309 . with_context ( || format ! ( "Opening ESP at {}" , esp_path. display( ) ) ) ?;
266310 validate_esp_fstype ( & esp_dir) ?;
267311
268- self . copy_efi_components_to_esp ( & sysroot_dir, & esp_path, & efi_comps) ?;
312+ let sysroot_dir = openat:: Dir :: open ( sysroot) . context ( "Opening sysroot for reading" ) ?;
313+ self . copy_efi_components_to_esp ( & sysroot_dir, & esp_dir, & esp_path, & efi_comps) ?;
269314
270315 log:: info!(
271316 "Successfully copied {} EFI component(s) to ESP at {}" ,
@@ -508,19 +553,16 @@ impl Component for Efi {
508553
509554 let efi_path = if let Some ( efi_components) = efi_comps {
510555 // Use shared helper to copy components from /usr/lib/efi
511- self . copy_efi_components_to_esp ( & src_dir, & destpath, & efi_components) ?;
556+ self . copy_efi_components_to_esp ( & src_dir, destd , & destpath, & efi_components) ?;
512557 EFILIB
513558 } else {
514559 let updates = component_updatedirname ( self ) ;
515560 let src = updates
516561 . to_str ( )
517- . context ( "Include invalid UTF-8 characters in path" ) ?;
518- let dest = destpath. to_str ( ) . with_context ( || {
519- format ! (
520- "Include invalid UTF-8 characters in dest {}" ,
521- destpath. display( )
522- )
523- } ) ?;
562+ . with_context ( || format ! ( "Invalid UTF-8: {}" , updates. display( ) ) ) ?;
563+ let dest = destpath
564+ . to_str ( )
565+ . with_context ( || format ! ( "Invalid UTF-8: {}" , destpath. display( ) ) ) ?;
524566 filetree:: copy_dir_with_args ( & src_dir, src, dest, OPTIONS ) ?;
525567 & src. to_owned ( )
526568 } ;
@@ -715,8 +757,8 @@ impl Component for Efi {
715757 }
716758
717759 /// Package mode copy: Simple copy from /usr/lib/efi to boot/ESP.
718- fn package_mode_copy_to_boot ( & self ) -> Result < ( ) > {
719- self . package_mode_copy_to_boot_impl ( )
760+ fn package_mode_copy_to_boot ( & self , root : & Path ) -> Result < ( ) > {
761+ self . package_mode_copy_to_boot_impl ( root )
720762 }
721763}
722764
@@ -1177,8 +1219,9 @@ Boot0003* test";
11771219 assert_eq ! ( efi_comps[ 0 ] . version, "15.8-3" ) ;
11781220
11791221 // Create Efi instance and copy components to ESP
1222+ let esp_dir = openat:: Dir :: open ( & esp_path) . context ( "Opening ESP dir for test" ) ?;
11801223 let efi = Efi :: default ( ) ;
1181- efi. copy_efi_components_to_esp ( & sysroot_dir, & esp_path, & efi_comps) ?;
1224+ efi. copy_efi_components_to_esp ( & sysroot_dir, & esp_dir , & esp_path, & efi_comps) ?;
11821225
11831226 // Expected path: /boot/efi/EFI/fedora/shimx64.efi (or shimaa64.efi, etc.)
11841227 let copied_shim_path = esp_path. join ( "EFI/fedora" ) . join ( SHIM ) ;
0 commit comments