@@ -23,7 +23,13 @@ const BOOTUPD_UPDATES: &str = "usr/lib/bootupd/updates";
2323// from: https://github.com/systemd/systemd/blob/26b2085d54ebbfca8637362eafcb4a8e3faf832f/man/systemd-boot.xml#L392
2424const SYSTEMD_KEY_DIR : & str = "loader/keys" ;
2525
26- /// Mount ESP part at /boot/efi
26+ /// Mount the first ESP found among backing devices at /boot/efi.
27+ ///
28+ /// This is used by the install-alongside path to clean stale bootloader
29+ /// files before reinstallation. On multi-device setups only the first
30+ /// ESP is mounted and cleaned; stale files on additional ESPs are left
31+ /// in place (bootupd will overwrite them during installation).
32+ // TODO: clean all ESPs on multi-device setups
2733pub ( crate ) fn mount_esp_part ( root : & Dir , root_path : & Utf8Path , is_ostree : bool ) -> Result < ( ) > {
2834 let efi_path = Utf8Path :: new ( "boot" ) . join ( crate :: bootloader:: EFI_DIR ) ;
2935 let Some ( esp_fd) = root
@@ -45,11 +51,14 @@ pub(crate) fn mount_esp_part(root: &Dir, root_path: &Utf8Path, is_ostree: bool)
4551 root
4652 } ;
4753
48- let dev = bootc_blockdev:: list_dev_by_dir ( physical_root) ?. require_single_root ( ) ?;
49- if let Some ( esp_dev) = dev. find_partition_of_type ( bootc_blockdev:: ESP ) {
50- let esp_path = esp_dev. path ( ) ;
51- bootc_mount:: mount ( & esp_path, & root_path. join ( & efi_path) ) ?;
52- tracing:: debug!( "Mounted {esp_path} at /boot/efi" ) ;
54+ let roots = bootc_blockdev:: list_dev_by_dir ( physical_root) ?. find_all_roots ( ) ?;
55+ for dev in & roots {
56+ if let Some ( esp_dev) = dev. find_partition_of_type ( bootc_blockdev:: ESP ) {
57+ let esp_path = esp_dev. path ( ) ;
58+ bootc_mount:: mount ( & esp_path, & root_path. join ( & efi_path) ) ?;
59+ tracing:: debug!( "Mounted {esp_path} at /boot/efi" ) ;
60+ return Ok ( ( ) ) ;
61+ }
5362 }
5463 Ok ( ( ) )
5564}
@@ -67,6 +76,48 @@ pub(crate) fn supports_bootupd(root: &Dir) -> Result<bool> {
6776 Ok ( r)
6877}
6978
79+ /// Check whether the target bootupd supports `--filesystem`.
80+ ///
81+ /// Runs `bootupctl backend install --help` and looks for `--filesystem` in the
82+ /// output. When `deployment_path` is set the command runs inside a bwrap
83+ /// container so we probe the binary from the target image.
84+ fn bootupd_supports_filesystem ( rootfs : & Utf8Path , deployment_path : Option < & str > ) -> Result < bool > {
85+ let help_args = [ "bootupctl" , "backend" , "install" , "--help" ] ;
86+ let output = if let Some ( deploy) = deployment_path {
87+ let target_root = rootfs. join ( deploy) ;
88+ BwrapCmd :: new ( & target_root)
89+ . setenv (
90+ "PATH" ,
91+ "/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" ,
92+ )
93+ . run_get_string ( help_args) ?
94+ } else {
95+ Command :: new ( "bootupctl" )
96+ . args ( & help_args[ 1 ..] )
97+ . log_debug ( )
98+ . run_get_string ( ) ?
99+ } ;
100+
101+ let use_filesystem = output. contains ( "--filesystem" ) ;
102+
103+ if use_filesystem {
104+ tracing:: debug!( "bootupd supports --filesystem" ) ;
105+ } else {
106+ tracing:: debug!( "bootupd does not support --filesystem, falling back to --device" ) ;
107+ }
108+
109+ Ok ( use_filesystem)
110+ }
111+
112+ /// Install the bootloader via bootupd.
113+ ///
114+ /// When the target bootupd supports `--filesystem` we pass it pointing at a
115+ /// block-backed mount so that bootupd can resolve the backing device(s) itself
116+ /// via `lsblk`. In the bwrap path we bind-mount the physical root at
117+ /// `/sysroot` to give `lsblk` a real block-backed path.
118+ ///
119+ /// For older bootupd versions that lack `--filesystem` we fall back to the
120+ /// legacy `--device <device_path> <rootfs>` invocation.
70121#[ context( "Installing bootloader" ) ]
71122pub ( crate ) fn install_via_bootupd (
72123 device : & bootc_blockdev:: Device ,
@@ -91,8 +142,6 @@ pub(crate) fn install_via_bootupd(
91142
92143 println ! ( "Installing bootloader via bootupd" ) ;
93144
94- let device_path = device. path ( ) ;
95-
96145 // Build the bootupctl arguments
97146 let mut bootupd_args: Vec < & str > = vec ! [ "backend" , "install" ] ;
98147 if configopts. bootupd_skip_boot_uuid {
@@ -107,26 +156,55 @@ pub(crate) fn install_via_bootupd(
107156 if let Some ( ref opts) = bootupd_opts {
108157 bootupd_args. extend ( opts. iter ( ) . copied ( ) ) ;
109158 }
110- bootupd_args. extend ( [ "--device" , & device_path, rootfs_mount] ) ;
159+
160+ // When the target bootupd lacks --filesystem support, fall back to the
161+ // legacy --device flag. For --device we need the whole-disk device path
162+ // (e.g. /dev/vda), not a partition (e.g. /dev/vda3), so resolve the
163+ // parent via require_single_root(). (Older bootupd doesn't support
164+ // multiple backing devices anyway.)
165+ // Computed before building bootupd_args so the String lives long enough.
166+ let root_device_path = if bootupd_supports_filesystem ( rootfs, deployment_path)
167+ . context ( "Probing bootupd --filesystem support" ) ?
168+ {
169+ None
170+ } else {
171+ Some ( device. require_single_root ( ) ?. path ( ) )
172+ } ;
173+ if let Some ( ref dev) = root_device_path {
174+ tracing:: debug!( "bootupd does not support --filesystem, falling back to --device {dev}" ) ;
175+ bootupd_args. extend ( [ "--device" , dev] ) ;
176+ bootupd_args. push ( rootfs_mount) ;
177+ } else {
178+ tracing:: debug!( "bootupd supports --filesystem" ) ;
179+ bootupd_args. extend ( [ "--filesystem" , rootfs_mount] ) ;
180+ bootupd_args. push ( rootfs_mount) ;
181+ }
111182
112183 // Run inside a bwrap container. It takes care of mounting and creating
113184 // the necessary API filesystems in the target deployment and acts as
114185 // a nicer `chroot`.
115186 if let Some ( deploy) = deployment_path {
116187 let target_root = rootfs. join ( deploy) ;
117188 let boot_path = rootfs. join ( "boot" ) ;
189+ let rootfs_path = rootfs. to_path_buf ( ) ;
118190
119191 tracing:: debug!( "Running bootupctl via bwrap in {}" , target_root) ;
120192
121193 // Prepend "bootupctl" to the args for bwrap
122194 let mut bwrap_args = vec ! [ "bootupctl" ] ;
123195 bwrap_args. extend ( bootupd_args) ;
124196
125- let cmd = BwrapCmd :: new ( & target_root)
197+ let mut cmd = BwrapCmd :: new ( & target_root)
126198 // Bind mount /boot from the physical target root so bootupctl can find
127199 // the boot partition and install the bootloader there
128200 . bind ( & boot_path, & "/boot" ) ;
129201
202+ // Only bind mount the physical root at /sysroot when using --filesystem;
203+ // bootupd needs it to resolve backing block devices via lsblk.
204+ if root_device_path. is_none ( ) {
205+ cmd = cmd. bind ( & rootfs_path, & "/sysroot" ) ;
206+ }
207+
130208 // The $PATH in the bwrap env is not complete enough for some images
131209 // so we inject a reasonnable default.
132210 // This is causing bootupctl and/or sfdisk binaries
0 commit comments