-
Notifications
You must be signed in to change notification settings - Fork 166
install/bootupd: chroot to deployment #1816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a8288be
09299f8
3a1f2d9
0bc7df5
93aaa72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ use fn_error_context::context; | |
|
|
||
| use bootc_blockdev::{Partition, PartitionTable}; | ||
| use bootc_mount as mount; | ||
| use rustix::mount::UnmountFlags; | ||
|
|
||
| use crate::bootc_composefs::boot::{get_sysroot_parent_dev, mount_esp, SecurebootKeys}; | ||
| use crate::{discoverable_partition_specification, utils}; | ||
|
|
@@ -91,22 +92,101 @@ pub(crate) fn install_via_bootupd( | |
| // bootc defaults to only targeting the platform boot method. | ||
| let bootupd_opts = (!configopts.generic_image).then_some(["--update-firmware", "--auto"]); | ||
|
|
||
| let abs_deployment_path = deployment_path.map(|v| rootfs.join(v)); | ||
| let src_root_arg = if let Some(p) = abs_deployment_path.as_deref() { | ||
| vec!["--src-root", p.as_str()] | ||
| let abs_deployment_path = deployment_path.map(|deploy| rootfs.join(deploy)); | ||
| // When not running inside the target container (through `--src-imgref`) we chroot | ||
| // into the deployment before running bootupd. This makes sure we use binaries | ||
| // from the target image rather than the buildroot | ||
| // In some cases (e.g. --write-uuid), bootupd needs to find the underlying device | ||
| // for /boot. But since we don't control where the destination rootfs is mounted | ||
| // let's bind mount it to a temp mountpoint under /run | ||
| // so it gets carried over in the chroot. | ||
|
|
||
| // let rootfs_mountpoint: TempDir; | ||
| let rootfs_mount = if rootfs.starts_with("/run") { | ||
| rootfs.as_str() | ||
| } else { | ||
| "/" | ||
| }; | ||
|
|
||
| // We mount the linux API file systems into the target deployment before chrooting | ||
| // so bootupd can find the proper backing device. | ||
| // xref https://systemd.io/API_FILE_SYSTEMS/ | ||
| let bind_mount_dirs = ["/dev", "/run", "/proc", "/sys"]; | ||
jbtrystram marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let chroot_args = if let Some(target_root) = abs_deployment_path.as_deref() { | ||
| tracing::debug!("Setting up bind-mounts before chrooting to the target deployment"); | ||
| for src in bind_mount_dirs { | ||
| let dest = target_root | ||
| // joining an absolute path | ||
| // makes it replace self, so we strip the prefix | ||
| .join_os(src.strip_prefix("/").unwrap()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
| tracing::debug!("bind mounting {}", dest.display()); | ||
| rustix::mount::mount_bind_recursive(src, dest)?; | ||
| } | ||
|
Comment on lines
+117
to
+124
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If To fix this, you should ensure that cleanup is always performed. A common pattern in Rust is to use a guard struct with a An alternative is to restructure the code to ensure the cleanup block is always reached. For example, you could wrap the mounting and command execution in a closure that returns a Example structure: let mut mounted_dirs = Vec::new();
let result = (|| {
// Mount loop, add to mounted_dirs on success
for src in bind_mount_dirs {
// ... mount logic ...
mounted_dirs.push(src);
}
// Execute command
// ...
Ok(())
})();
// Cleanup loop over mounted_dirs
// ...
// Return combined resultThis is a significant refactoring, but it's crucial for resource safety. |
||
| // WIP : let's try to bind-mount /target/boot into the deployment as well rather than bind-mounting the whole thing?? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if !rootfs.starts_with("/run") { | ||
| tracing::debug!( | ||
| "We need to access the target /boot filesystem so let's also bind-mount it" | ||
| ); | ||
| let trgt_boot = rootfs.as_std_path().join("boot"); | ||
| let chrooted_boot = target_root.join_os("boot"); | ||
| tracing::debug!( | ||
| "bind-mounting {} in {}", | ||
| &trgt_boot.display(), | ||
| &chrooted_boot.display() | ||
| ); | ||
| rustix::mount::mount_bind_recursive(trgt_boot, chrooted_boot)?; | ||
| } | ||
|
|
||
| // Append the `bootupctl` command, it will be passed as | ||
| // an argument to chroot | ||
| vec![target_root.as_str(), "bootupctl"] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we just pass
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we would then need to have another bind mount
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok so i ended up doing just that and now more the tests are passing. |
||
| } else { | ||
| vec![] | ||
| }; | ||
|
|
||
| let devpath = device.path(); | ||
| println!("Installing bootloader via bootupd"); | ||
| Command::new("bootupctl") | ||
| let mut bootupctl = if abs_deployment_path.is_some() { | ||
| Command::new("chroot") | ||
| } else { | ||
| Command::new("bootupctl") | ||
| }; | ||
| let install_result = bootupctl | ||
| .args(chroot_args) | ||
| // Inject a reasonnable PATH here so we find the required tools | ||
| // when running chrooted in the deployment. Testing show that | ||
| // the default $PATH value in the chroot is insufficient. | ||
| .env( | ||
| "PATH", | ||
| "/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin", | ||
| ) | ||
| .args(["backend", "install", "--write-uuid"]) | ||
| .args(verbose) | ||
| .args(bootupd_opts.iter().copied().flatten()) | ||
| .args(src_root_arg) | ||
| .args(["--device", devpath.as_str(), rootfs.as_str()]) | ||
| .args(["--device", devpath.as_str(), rootfs_mount]) | ||
| .log_debug() | ||
| .run_inherited_with_cmd_context() | ||
| .run_inherited_with_cmd_context(); | ||
|
|
||
| // Clean up the mounts after ourselves | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could entirely avoid the need to clean up by using the new mount API to get file descriptors instead, and then use https://docs.rs/cap-std-ext/latest/cap_std_ext/cmdext/trait.CapStdExtCommandExt.html#tymethod.cwd_dir with |
||
| if let Some(target_root) = abs_deployment_path { | ||
| if let Err(e) = rustix::mount::unmount( | ||
| target_root.join("boot").into_std_path_buf(), | ||
| UnmountFlags::DETACH, | ||
| ) { | ||
| tracing::warn!("Error unmounting target/boot: {e}"); | ||
| } | ||
| for dir in bind_mount_dirs { | ||
| let mount = target_root | ||
| .join(dir.strip_prefix("/").unwrap()) | ||
| .into_std_path_buf(); | ||
| if let Err(e) = rustix::mount::unmount(&mount, UnmountFlags::DETACH) { | ||
| // let's not propagate the error up because in some cases we can't unmount | ||
| // e.g. when running `to-existing-root` | ||
| tracing::warn!("Error unmounting {}: {e}", mount.display()); | ||
| } | ||
| } | ||
| } | ||
| install_result | ||
| } | ||
|
|
||
| #[context("Installing bootloader")] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's other threads were we talked about offering a
bootc install mountas a general ability to mount a deployment outside of booting it; were we to do that it would make a lot of sense for this code to use it.In ostree we resisted doing that for a long time but eventually did just internally for selinux, see https://github.com/ostreedev/ostree/blob/c6f0b5b2bc26b22fbceee0dc28a0f31349c28d41/src/libostree/ostree-sysroot-deploy.c#L3308
On that topic, it'd be a lot cleaner even here to use a more proper containerization than just setting up the mounts. It's a bit tricky though because we actually do need to e.g. pass through all of
/devand/syshere (i.e.--privilegedin docker/podman terms) in order to update the ESP if desired.I haven't looked at which of bwrap/{runc,crun}/nspawn/podman would make the most sense for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure of what you mean with this comment. Do you want to block this change until there are more proper containerization helpers in bootc, or are you just making a note that this should be revisited later on ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a live chat about this and agreed to merge as is and file a tracker followup issue for improving the mount setup.