-
Notifications
You must be signed in to change notification settings - Fork 166
install: add target_root_path for RootSetup
#1752
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
Changes from all commits
10b5c32
083b03d
d0952c3
18c44da
d00f8d1
bab8f98
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 |
|---|---|---|
|
|
@@ -779,6 +779,17 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result | |
| // Another implementation: https://github.com/coreos/coreos-assembler/blob/3cd3307904593b3a131b81567b13a4d0b6fe7c90/src/create_disk.sh#L295 | ||
| crate::lsm::ensure_dir_labeled(rootfs_dir, "", Some("/".into()), 0o755.into(), sepolicy)?; | ||
|
|
||
| // If we're installing alongside existing ostree and there's a separate boot partition, | ||
| // we need to mount it to the sysroot's /boot so ostree can write bootloader entries there | ||
|
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. One thing I don't quite understand is why other cases don't hit this. I feel like there might be a different bug somewhere.
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. On ostree env, the bootloader entries are written under |
||
| if has_ostree && root_setup.boot.is_some() { | ||
| if let Some(boot) = &root_setup.boot { | ||
| let source_boot = &boot.source; | ||
| let target_boot = root_setup.physical_root_path.join(BOOT); | ||
| tracing::debug!("Mount {source_boot} to {target_boot} on ostree"); | ||
| bootc_mount::mount(source_boot, &target_boot)?; | ||
| } | ||
| } | ||
|
|
||
| // And also label /boot AKA xbootldr, if it exists | ||
| if rootfs_dir.try_exists("boot")? { | ||
| crate::lsm::ensure_dir_labeled(rootfs_dir, "boot", None, 0o755.into(), sepolicy)?; | ||
|
|
@@ -1122,6 +1133,8 @@ pub(crate) struct RootSetup { | |
| pub(crate) physical_root_path: Utf8PathBuf, | ||
| /// Directory file descriptor for the above physical root. | ||
| pub(crate) physical_root: Dir, | ||
| /// Target root path /target. | ||
| pub(crate) target_root_path: Option<Utf8PathBuf>, | ||
|
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. In general, I would like if we could go to having most things be file descriptor relative; it's unfortunate to need to add a new absolute path here. But I understand that the bootupd install is using it today. What I would find helpful is to understand a bit more strictly the meaing of "target" here. I think we're effectively using it to be "what the user provided as an install target" right? And for ostree/bootc systems, that's always the mounted deployment root conventionally today when they say But that's just conventional - what would happen if they passed The more I think about this the more I feel what we should do is detect when the passed root filesystem refers to either a mounted deployment root or the physical root, and automatically canonicalize internally to "physical root" and "deployment root" (which for non-ostree systems are the same thing), and then discard the user provided input path.
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.
This sounds better and more sane, will try this later.
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 still need |
||
| pub(crate) rootfs_uuid: Option<String>, | ||
| /// True if we should skip finalizing | ||
| skip_finalize: bool, | ||
|
|
@@ -1575,7 +1588,10 @@ async fn install_with_sysroot( | |
| Bootloader::Grub => { | ||
| crate::bootloader::install_via_bootupd( | ||
| &rootfs.device_info, | ||
| &rootfs.physical_root_path, | ||
| &rootfs | ||
| .target_root_path | ||
| .clone() | ||
| .unwrap_or(rootfs.physical_root_path.clone()), | ||
| &state.config_opts, | ||
| Some(&deployment_path.as_str()), | ||
| )?; | ||
|
|
@@ -1922,30 +1938,66 @@ fn remove_all_in_dir_no_xdev(d: &Dir, mount_err: bool) -> Result<()> { | |
| anyhow::Ok(()) | ||
| } | ||
|
|
||
| #[context("Removing boot directory content except loader dir on ostree")] | ||
| fn remove_all_except_loader_dirs(bootdir: &Dir, is_ostree: bool) -> Result<()> { | ||
| let entries = bootdir | ||
| .entries() | ||
| .context("Reading boot directory entries")?; | ||
|
|
||
| for entry in entries { | ||
| let entry = entry.context("Reading directory entry")?; | ||
| let file_name = entry.file_name(); | ||
| let file_name = if let Some(n) = file_name.to_str() { | ||
| n | ||
| } else { | ||
| anyhow::bail!("Invalid non-UTF8 filename: {file_name:?} in /boot"); | ||
| }; | ||
|
|
||
| // TODO: Preserve basically everything (including the bootloader entries | ||
| // on non-ostree) by default until the very end of the install. And ideally | ||
| // make the "commit" phase an optional step after. | ||
| if is_ostree && file_name.starts_with("loader") { | ||
|
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. This also relates to an issue (TODO link) where we should support preserving basically everything (including the bootloader entries on non-ostree) by default until the very end of the install. And ideally make the "commit" phase an optional step after. This one also textually conflicts with #1727
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.
I think you meant preserve everything under /boot & /boot/efi, then clean them before install bootloader, is this right?
A little confused, this requires empty rootfs when |
||
| continue; | ||
| } | ||
|
|
||
| let etype = entry.file_type()?; | ||
| if etype == FileType::dir() { | ||
| // Open the directory and remove its contents | ||
| if let Some(subdir) = bootdir.open_dir_noxdev(&file_name)? { | ||
| remove_all_in_dir_no_xdev(&subdir, false) | ||
| .with_context(|| format!("Removing directory contents: {}", file_name))?; | ||
| bootdir.remove_dir(&file_name)?; | ||
| } | ||
| } else { | ||
| bootdir | ||
| .remove_file_optional(&file_name) | ||
| .with_context(|| format!("Removing file: {}", file_name))?; | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[context("Removing boot directory content")] | ||
| fn clean_boot_directories(rootfs: &Dir, is_ostree: bool) -> Result<()> { | ||
| fn clean_boot_directories(rootfs: &Dir, rootfs_path: &Utf8Path, is_ostree: bool) -> Result<()> { | ||
| let bootdir = | ||
| crate::utils::open_dir_remount_rw(rootfs, BOOT.into()).context("Opening /boot")?; | ||
|
|
||
| if is_ostree { | ||
| // On ostree systems, the boot directory already has our desired format, we should only | ||
| // remove the bootupd-state.json file to avoid bootupctl complaining it already exists. | ||
| bootdir | ||
| .remove_file_optional("bootupd-state.json") | ||
| .context("removing bootupd-state.json")?; | ||
| } else { | ||
| // This should not remove /boot/efi note. | ||
| remove_all_in_dir_no_xdev(&bootdir, false).context("Emptying /boot")?; | ||
| // TODO: Discover the ESP the same way bootupd does it; we should also | ||
| // support not wiping the ESP. | ||
| if ARCH_USES_EFI { | ||
| if let Some(efidir) = bootdir | ||
| .open_dir_optional(crate::bootloader::EFI_DIR) | ||
| .context("Opening /boot/efi")? | ||
| { | ||
| remove_all_in_dir_no_xdev(&efidir, false) | ||
| .context("Emptying EFI system partition")?; | ||
| } | ||
| if ARCH_USES_EFI { | ||
| // On booted FCOS, esp is not mounted by default | ||
| // Mount ESP part at /boot/efi before clean | ||
| crate::bootloader::mount_esp_part(&rootfs, &rootfs_path, is_ostree)?; | ||
| } | ||
|
|
||
| // This should not remove /boot/efi note. | ||
| remove_all_except_loader_dirs(&bootdir, is_ostree).context("Emptying /boot")?; | ||
|
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. Hmm looks like this is going to conflict with #1727 |
||
|
|
||
| // TODO: we should also support not wiping the ESP. | ||
| if ARCH_USES_EFI { | ||
| if let Some(efidir) = bootdir | ||
| .open_dir_optional(crate::bootloader::EFI_DIR) | ||
| .context("Opening /boot/efi")? | ||
| { | ||
| remove_all_in_dir_no_xdev(&efidir, false).context("Emptying EFI system partition")?; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -2082,6 +2134,18 @@ pub(crate) async fn install_to_filesystem( | |
| .context("Mounting host / to {ALONGSIDE_ROOT_MOUNT}")?; | ||
| } | ||
|
|
||
| let target_root_path = fsopts.root_path.clone(); | ||
| // Get a file descriptor for the root path /target | ||
| let target_rootfs_fd = | ||
| Dir::open_ambient_dir(&target_root_path, cap_std::ambient_authority()) | ||
| .with_context(|| format!("Opening target root directory {target_root_path}"))?; | ||
|
|
||
| tracing::debug!("Target root filesystem: {target_root_path}"); | ||
|
|
||
| if let Some(false) = target_rootfs_fd.is_mountpoint(".")? { | ||
| anyhow::bail!("Not a mountpoint: {target_root_path}"); | ||
| } | ||
|
|
||
| // Check that the target is a directory | ||
| { | ||
| let root_path = &fsopts.root_path; | ||
|
|
@@ -2095,10 +2159,7 @@ pub(crate) async fn install_to_filesystem( | |
|
|
||
| // Check to see if this happens to be the real host root | ||
| if !fsopts.acknowledge_destructive { | ||
| let root_path = &fsopts.root_path; | ||
| let rootfs_fd = Dir::open_ambient_dir(root_path, cap_std::ambient_authority()) | ||
| .with_context(|| format!("Opening target root directory {root_path}"))?; | ||
| warn_on_host_root(&rootfs_fd)?; | ||
| warn_on_host_root(&target_rootfs_fd)?; | ||
| } | ||
|
|
||
| // If we're installing to an ostree root, then find the physical root from | ||
|
|
@@ -2114,7 +2175,8 @@ pub(crate) async fn install_to_filesystem( | |
| }; | ||
|
|
||
| // Get a file descriptor for the root path | ||
| let rootfs_fd = { | ||
| // It will be /target/sysroot on ostree OS, or will be /target | ||
| let rootfs_fd = if is_already_ostree { | ||
|
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. While I know you're just changing existing code, I think it'd be cleaner to fold this conditional with the one above. I had to read this whole logic a few times to notice that a key bit here is we're mutating the user input |
||
| let root_path = &fsopts.root_path; | ||
| let rootfs_fd = Dir::open_ambient_dir(&fsopts.root_path, cap_std::ambient_authority()) | ||
| .with_context(|| format!("Opening target root directory {root_path}"))?; | ||
|
|
@@ -2125,6 +2187,8 @@ pub(crate) async fn install_to_filesystem( | |
| anyhow::bail!("Not a mountpoint: {root_path}"); | ||
| } | ||
| rootfs_fd | ||
| } else { | ||
| target_rootfs_fd.try_clone()? | ||
| }; | ||
|
|
||
| match fsopts.replace { | ||
|
|
@@ -2134,7 +2198,9 @@ pub(crate) async fn install_to_filesystem( | |
| tokio::task::spawn_blocking(move || remove_all_in_dir_no_xdev(&rootfs_fd, true)) | ||
| .await??; | ||
| } | ||
| Some(ReplaceMode::Alongside) => clean_boot_directories(&rootfs_fd, is_already_ostree)?, | ||
| Some(ReplaceMode::Alongside) => { | ||
| clean_boot_directories(&target_rootfs_fd, &target_root_path, is_already_ostree)? | ||
| } | ||
| None => require_empty_rootdir(&rootfs_fd)?, | ||
| } | ||
|
|
||
|
|
@@ -2179,7 +2245,7 @@ pub(crate) async fn install_to_filesystem( | |
|
|
||
| let boot_is_mount = { | ||
| let root_dev = rootfs_fd.dir_metadata()?.dev(); | ||
| let boot_dev = rootfs_fd | ||
| let boot_dev = target_rootfs_fd | ||
| .symlink_metadata_optional(BOOT)? | ||
| .ok_or_else(|| { | ||
| anyhow!("No /{BOOT} directory found in root; this is is currently required") | ||
|
|
@@ -2190,9 +2256,10 @@ pub(crate) async fn install_to_filesystem( | |
| }; | ||
| // Find the UUID of /boot because we need it for GRUB. | ||
| let boot_uuid = if boot_is_mount { | ||
| let boot_path = fsopts.root_path.join(BOOT); | ||
| let boot_path = target_root_path.join(BOOT); | ||
|
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. This seems to be a key change here. I guess we needed this because in the ostree case we'd mutated But this relates to what I mean above - we could instead just know to look at the deployment's
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.
I am a little confused, do you mean "physical root" is From following log (copy from bug), we passed And I do not understand
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. For non-bootc/ostree setups, "physical root == root", so the user always passes For bootc/ostree setups, they are not the same. What I am saying though is we should support being passed either thing (
Typically we only mount the separate boot partition in the target root, not the physical root.
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. Understood, thank you for the detail info.
Try to catch your meaning, for bootc/ostree setups, if passed
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. Will try this later. |
||
| tracing::debug!("boot_path={boot_path}"); | ||
| let u = bootc_mount::inspect_filesystem(&boot_path) | ||
| .context("Inspecting /{BOOT}")? | ||
| .with_context(|| format!("Inspecting /{BOOT}"))? | ||
| .uuid | ||
| .ok_or_else(|| anyhow!("No UUID found for /{BOOT}"))?; | ||
| Some(u) | ||
|
|
@@ -2279,6 +2346,7 @@ pub(crate) async fn install_to_filesystem( | |
| device_info, | ||
| physical_root_path: fsopts.root_path, | ||
| physical_root: rootfs_fd, | ||
| target_root_path: Some(target_root_path.clone()), | ||
| rootfs_uuid: inspect.uuid.clone(), | ||
| boot, | ||
| kargs, | ||
|
|
||
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.
Ah it looks like this is going to conflict with #1864
But it's ok if this goes in first I can rebase