Skip to content
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

WIP: Add support for --replace-mode=alongside for ostree target #137

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ exclude-crate-paths = [ { name = "libz-sys", exclude = "src/zlib" },
{ name = "k8s-openapi", exclude = "src/v1_25" },
{ name = "k8s-openapi", exclude = "src/v1_27" },
]

[patch.crates-io]
ostree-ext = { path = "../../ostreedev/ostree-rs-ext/lib" }
45 changes: 35 additions & 10 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,17 @@ async fn initialize_ostree_root_from_self(

// TODO: make configurable?
let stateroot = STATEROOT_DEFAULT;
Task::new_and_run(
"Initializing ostree layout",
"ostree",
["admin", "init-fs", "--modern", rootfs.as_str()],
)?;
let has_ostree = rootfs_dir.try_exists("ostree/repo")?;
if !has_ostree {
Task::new_and_run(
"Initializing ostree layout",
"ostree",
["admin", "init-fs", "--modern", rootfs.as_str()],
)?;
} else {
println!("Reusing extant ostree layout");
let _ = crate::utils::open_dir_remount_rw(rootfs_dir, "sysroot".into())?;
}

// Default to avoiding grub2-mkconfig etc., but we need to use zipl on s390x.
// TODO: Lower this logic into ostree proper.
Expand All @@ -482,10 +488,15 @@ async fn initialize_ostree_root_from_self(
.quiet()
.run()?;
}
Task::new("Initializing sysroot", "ostree")
.args(["admin", "os-init", stateroot, "--sysroot", "."])
.cwd(rootfs_dir)?
.run()?;
let stateroot_exists = rootfs_dir.try_exists(format!("ostree/deploy/{stateroot}"))?;
if stateroot_exists {
anyhow::bail!("Cannot redeploy over extant stateroot {stateroot}");
} else {
Task::new("Initializing sysroot", "ostree")
.args(["admin", "os-init", stateroot, "--sysroot", "."])
.cwd(rootfs_dir)?
.run()?;
}

// Ensure everything in the ostree repo is labeled
state.lsm_label(&rootfs.join("ostree"), "/usr".into(), true)?;
Expand Down Expand Up @@ -532,6 +543,7 @@ async fn initialize_ostree_root_from_self(
options.kargs = Some(kargs.as_slice());
options.target_imgref = Some(&target_imgref);
options.proxy_cfg = Some(proxy_cfg);
options.no_clean = has_ostree;
println!("Creating initial deployment");
let state =
ostree_container::deploy::deploy(&sysroot, stateroot, &src_imageref, Some(options)).await?;
Expand Down Expand Up @@ -845,6 +857,18 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
}

let boot_uuid = rootfs.get_boot_uuid()?;
// If we're doing an alongside install, then the /dev bootupd sees needs to be the host's.
// What we probably really want to do here is tunnel in the host's /dev properly, but for now
// just copy /dev/disk
if rootfs.skip_finalize {
if !Utf8Path::new("/dev/disk").try_exists()? {
Task::new_and_run(
"Copying host /dev/disk",
"cp",
["-a", "/proc/1/root/dev/disk", "/dev/disk"],
)?;
}
}
crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, boot_uuid)?;
tracing::debug!("Installed bootloader");

Expand Down Expand Up @@ -958,7 +982,8 @@ fn remove_all_in_dir_no_xdev(d: &Dir) -> Result<()> {

#[context("Removing boot directory content")]
fn clean_boot_directories(rootfs: &Dir) -> Result<()> {
let bootdir = rootfs.open_dir(BOOT).context("Opening /boot")?;
let bootdir =
crate::utils::open_dir_remount_rw(rootfs, BOOT.into()).context("Opening /boot")?;
// This should not remove /boot/efi note.
remove_all_in_dir_no_xdev(&bootdir)?;
if ARCH_USES_EFI {
Expand Down
42 changes: 42 additions & 0 deletions lib/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ use std::os::unix::prelude::OsStringExt;
use std::process::Command;

use anyhow::{Context, Result};
use camino::Utf8Path;
use cap_std_ext::{cap_std::fs::Dir, prelude::CapStdExtCommandExt};
use fn_error_context::context;
use ostree::glib;
use ostree_ext::ostree;
use std::os::fd::AsFd;

/// Try to look for keys injected by e.g. rpm-ostree requesting machine-local
/// changes; if any are present, return `true`.
Expand Down Expand Up @@ -32,6 +36,44 @@ pub(crate) fn find_mount_option<'a>(
.next()
}

/// Try to (heuristically) determine if the provided path is a mount root.
pub(crate) fn is_mountpoint(root: &Dir, path: &Utf8Path) -> Result<Option<bool>> {
// https://github.com/systemd/systemd/blob/8fbf0a214e2fe474655b17a4b663122943b55db0/src/basic/mountpoint-util.c#L176
use rustix::fs::{AtFlags, StatxFlags};

// SAFETY(unwrap): We can infallibly convert an i32 into a u64.
let mountroot_flag: u64 = libc::STATX_ATTR_MOUNT_ROOT.try_into().unwrap();
match rustix::fs::statx(
root.as_fd(),
path.as_std_path(),
AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW,
StatxFlags::empty(),
) {
Ok(r) => {
let present = (r.stx_attributes_mask & mountroot_flag) > 0;
Ok(present.then(|| r.stx_attributes & mountroot_flag > 0))
}
Err(e) if e == rustix::io::Errno::NOSYS => Ok(None),
Err(e) => Err(e.into()),
}
}

/// Given a target directory, if it's a read-only mount, then remount it writable
#[context("Opening {target} with writable mount")]
pub(crate) fn open_dir_remount_rw(root: &Dir, target: &Utf8Path) -> Result<Dir> {
if is_mountpoint(root, target)?.unwrap_or_default() {
tracing::debug!("Target {target} is a mountpoint, remounting rw");
let st = Command::new("mount")
.args(["-o", "remount,rw", target.as_str()])
.cwd_dir(root.try_clone()?)
.status()?;
if !st.success() {
anyhow::bail!("Failed to remount: {st:?}");
}
}
root.open_dir(target).map_err(anyhow::Error::new)
}

/// Run a command in the host mount namespace
#[allow(dead_code)]
pub(crate) fn run_in_host_mountns(cmd: &str) -> Command {
Expand Down
Loading