mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 06:45:13 +01:00
Move composefs setup root to bootc initramfs
Move the composefs-setup-root code from composefs-rs to bootc-initramfs crate Signed-off-by: Johan-Liebert1 <pragyanpoudyal41999@gmail.com>
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -191,6 +191,12 @@ name = "bootc-initramfs-setup"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"composefs",
|
||||
"composefs-boot",
|
||||
"rustix 1.0.8",
|
||||
"serde",
|
||||
"toml 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -7,6 +7,17 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true, features = ["std", "help", "usage", "derive"] }
|
||||
rustix.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
composefs.workspace = true
|
||||
composefs-boot.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ['pre-6.15']
|
||||
rhel9 = ['composefs/rhel9']
|
||||
'pre-6.15' = ['composefs/pre-6.15']
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
Description=bootc setup root
|
||||
Documentation=man:bootc(1)
|
||||
DefaultDependencies=no
|
||||
# For now
|
||||
ConditionKernelCommandLine=ostree
|
||||
ConditionKernelCommandLine=composefs
|
||||
ConditionPathExists=/etc/initrd-release
|
||||
After=sysroot.mount
|
||||
After=ostree-prepare-root.service
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
//! Code for bootc that goes into the initramfs.
|
||||
//! At the current time, this is mostly just a no-op.
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
mod mount;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
fn setup_root() -> Result<()> {
|
||||
let _ = std::fs::metadata("/sysroot/usr")?;
|
||||
println!("setup OK");
|
||||
Ok(())
|
||||
}
|
||||
use clap::Parser;
|
||||
use mount::{gpt_workaround, setup_root, Args};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let v = std::env::args().collect::<Vec<_>>();
|
||||
let args = match v.as_slice() {
|
||||
[] => anyhow::bail!("Missing argument".to_string()),
|
||||
[_, rest @ ..] => rest,
|
||||
};
|
||||
match args {
|
||||
[] => anyhow::bail!("Missing argument".to_string()),
|
||||
[s] if s == "setup-root" => setup_root(),
|
||||
[o, ..] => anyhow::bail!(format!("Unknown command {o}")),
|
||||
}
|
||||
let args = Args::parse();
|
||||
gpt_workaround()?;
|
||||
setup_root(args)
|
||||
}
|
||||
|
||||
294
crates/initramfs/src/mount.rs
Normal file
294
crates/initramfs/src/mount.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
//! Mount helpers for bootc-initramfs
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
fmt::Debug,
|
||||
io::ErrorKind,
|
||||
os::fd::{AsFd, OwnedFd},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use rustix::{
|
||||
fs::{major, minor, mkdirat, openat, stat, symlink, Mode, OFlags, CWD},
|
||||
io::Errno,
|
||||
mount::{
|
||||
fsconfig_create, fsconfig_set_string, fsmount, open_tree, unmount, FsMountFlags,
|
||||
MountAttrFlags, OpenTreeFlags, UnmountFlags,
|
||||
},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use composefs::{
|
||||
fsverity::{FsVerityHashValue, Sha256HashValue},
|
||||
mount::{mount_at, FsHandle},
|
||||
mountcompat::{overlayfs_set_fd, overlayfs_set_lower_and_data_fds, prepare_mount},
|
||||
repository::Repository,
|
||||
};
|
||||
use composefs_boot::cmdline::get_cmdline_composefs;
|
||||
|
||||
// Config file
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
enum MountType {
|
||||
None,
|
||||
Bind,
|
||||
Overlay,
|
||||
Transient,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct RootConfig {
|
||||
#[serde(default)]
|
||||
transient: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
struct MountConfig {
|
||||
mount: Option<MountType>,
|
||||
#[serde(default)]
|
||||
transient: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
struct Config {
|
||||
#[serde(default)]
|
||||
etc: MountConfig,
|
||||
#[serde(default)]
|
||||
var: MountConfig,
|
||||
#[serde(default)]
|
||||
root: RootConfig,
|
||||
}
|
||||
|
||||
/// Command-line arguments
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version)]
|
||||
pub struct Args {
|
||||
#[arg(help = "Execute this command (for testing)")]
|
||||
/// Execute this command (for testing)
|
||||
pub cmd: Vec<OsString>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "/sysroot",
|
||||
help = "sysroot directory in initramfs"
|
||||
)]
|
||||
/// sysroot directory in initramfs
|
||||
pub sysroot: PathBuf,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "/usr/lib/composefs/setup-root-conf.toml",
|
||||
help = "Config path (for testing)"
|
||||
)]
|
||||
/// Config path (for testing)
|
||||
pub config: PathBuf,
|
||||
|
||||
// we want to test in a userns, but can't mount erofs there
|
||||
#[arg(long, help = "Bind mount root-fs from (for testing)")]
|
||||
/// Bind mount root-fs from (for testing)
|
||||
pub root_fs: Option<PathBuf>,
|
||||
|
||||
#[arg(long, help = "Kernel commandline args (for testing)")]
|
||||
/// Kernel commandline args (for testing)
|
||||
pub cmdline: Option<String>,
|
||||
|
||||
#[arg(long, help = "Mountpoint (don't replace sysroot, for testing)")]
|
||||
/// Mountpoint (don't replace sysroot, for testing)
|
||||
pub target: Option<PathBuf>,
|
||||
}
|
||||
|
||||
// Helpers
|
||||
fn open_dir(dirfd: impl AsFd, name: impl AsRef<Path> + Debug) -> rustix::io::Result<OwnedFd> {
|
||||
openat(
|
||||
dirfd,
|
||||
name.as_ref(),
|
||||
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
)
|
||||
.inspect_err(|_| {
|
||||
eprintln!("Failed to open dir {name:?}");
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_dir(dirfd: impl AsFd, name: &str) -> rustix::io::Result<OwnedFd> {
|
||||
match mkdirat(dirfd.as_fd(), name, 0o700.into()) {
|
||||
Ok(()) | Err(Errno::EXIST) => {}
|
||||
Err(err) => Err(err)?,
|
||||
}
|
||||
open_dir(dirfd, name)
|
||||
}
|
||||
|
||||
fn bind_mount(fd: impl AsFd, path: &str) -> rustix::io::Result<OwnedFd> {
|
||||
open_tree(
|
||||
fd.as_fd(),
|
||||
path,
|
||||
OpenTreeFlags::OPEN_TREE_CLONE
|
||||
| OpenTreeFlags::OPEN_TREE_CLOEXEC
|
||||
| OpenTreeFlags::AT_EMPTY_PATH,
|
||||
)
|
||||
.inspect_err(|_| {
|
||||
eprintln!("Open tree failed for {path}");
|
||||
})
|
||||
}
|
||||
|
||||
fn mount_tmpfs() -> Result<OwnedFd> {
|
||||
let tmpfs = FsHandle::open("tmpfs")?;
|
||||
fsconfig_create(tmpfs.as_fd())?;
|
||||
Ok(fsmount(
|
||||
tmpfs.as_fd(),
|
||||
FsMountFlags::FSMOUNT_CLOEXEC,
|
||||
MountAttrFlags::empty(),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn overlay_state(base: impl AsFd, state: impl AsFd, source: &str) -> Result<()> {
|
||||
let upper = ensure_dir(state.as_fd(), "upper")?;
|
||||
let work = ensure_dir(state.as_fd(), "work")?;
|
||||
|
||||
let overlayfs = FsHandle::open("overlay")?;
|
||||
fsconfig_set_string(overlayfs.as_fd(), "source", source)?;
|
||||
overlayfs_set_fd(overlayfs.as_fd(), "workdir", work.as_fd())?;
|
||||
overlayfs_set_fd(overlayfs.as_fd(), "upperdir", upper.as_fd())?;
|
||||
overlayfs_set_lower_and_data_fds(&overlayfs, base.as_fd(), None::<OwnedFd>)?;
|
||||
fsconfig_create(overlayfs.as_fd())?;
|
||||
let fs = fsmount(
|
||||
overlayfs.as_fd(),
|
||||
FsMountFlags::FSMOUNT_CLOEXEC,
|
||||
MountAttrFlags::empty(),
|
||||
)?;
|
||||
|
||||
Ok(mount_at(fs, base, ".")?)
|
||||
}
|
||||
|
||||
fn overlay_transient(base: impl AsFd) -> Result<()> {
|
||||
overlay_state(base, prepare_mount(mount_tmpfs()?)?, "transient")
|
||||
}
|
||||
|
||||
fn open_root_fs(path: &Path) -> Result<OwnedFd> {
|
||||
let rootfs = open_tree(
|
||||
CWD,
|
||||
path,
|
||||
OpenTreeFlags::OPEN_TREE_CLONE | OpenTreeFlags::OPEN_TREE_CLOEXEC,
|
||||
)?;
|
||||
|
||||
// https://github.com/bytecodealliance/rustix/issues/975
|
||||
// mount_setattr(rootfs.as_fd()), ..., { ... MountAttrFlags::MOUNT_ATTR_RDONLY ... }, ...)?;
|
||||
|
||||
Ok(rootfs)
|
||||
}
|
||||
|
||||
/// Prepares a floating mount for composefs and returns the fd
|
||||
///
|
||||
/// # Arguments
|
||||
/// * sysroot - fd for /sysroot
|
||||
/// * name - Name of the EROFS image to be mounted
|
||||
/// * insecure - Whether fsverity is optional or not
|
||||
pub fn mount_composefs_image(sysroot: &OwnedFd, name: &str, insecure: bool) -> Result<OwnedFd> {
|
||||
let mut repo = Repository::<Sha256HashValue>::open_path(sysroot, "composefs")?;
|
||||
repo.set_insecure(insecure);
|
||||
repo.mount(name).context("Failed to mount composefs image")
|
||||
}
|
||||
|
||||
fn mount_subdir(
|
||||
new_root: impl AsFd,
|
||||
state: impl AsFd,
|
||||
subdir: &str,
|
||||
config: MountConfig,
|
||||
default: MountType,
|
||||
) -> Result<()> {
|
||||
let mount_type = match config.mount {
|
||||
Some(mt) => mt,
|
||||
None => match config.transient {
|
||||
true => MountType::Transient,
|
||||
false => default,
|
||||
},
|
||||
};
|
||||
|
||||
match mount_type {
|
||||
MountType::None => Ok(()),
|
||||
MountType::Bind => Ok(mount_at(bind_mount(&state, subdir)?, &new_root, subdir)?),
|
||||
MountType::Overlay => overlay_state(
|
||||
open_dir(&new_root, subdir)?,
|
||||
open_dir(&state, subdir)?,
|
||||
"overlay",
|
||||
),
|
||||
MountType::Transient => overlay_transient(open_dir(&new_root, subdir)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gpt_workaround() -> Result<()> {
|
||||
// https://github.com/systemd/systemd/issues/35017
|
||||
let rootdev = stat("/dev/gpt-auto-root");
|
||||
|
||||
let rootdev = match rootdev {
|
||||
Ok(r) => r,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(()),
|
||||
Err(e) => Err(e)?,
|
||||
};
|
||||
|
||||
let target = format!(
|
||||
"/dev/block/{}:{}",
|
||||
major(rootdev.st_rdev),
|
||||
minor(rootdev.st_rdev)
|
||||
);
|
||||
symlink(target, "/run/systemd/volatile-root")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets up /sysroot for switch-root
|
||||
pub fn setup_root(args: Args) -> Result<()> {
|
||||
let config = match std::fs::read_to_string(args.config) {
|
||||
Ok(text) => toml::from_str(&text)?,
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => Config::default(),
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
|
||||
let sysroot = open_dir(CWD, &args.sysroot)
|
||||
.with_context(|| format!("Failed to open sysroot {:?}", args.sysroot))?;
|
||||
|
||||
let cmdline = match &args.cmdline {
|
||||
Some(cmdline) => cmdline,
|
||||
// TODO: Deduplicate this with composefs branch karg parser
|
||||
None => &std::fs::read_to_string("/proc/cmdline")?,
|
||||
};
|
||||
let (image, insecure) = get_cmdline_composefs::<Sha256HashValue>(cmdline)?;
|
||||
|
||||
let new_root = match args.root_fs {
|
||||
Some(path) => open_root_fs(&path).context("Failed to clone specified root fs")?,
|
||||
None => mount_composefs_image(&sysroot, &image.to_hex(), insecure)?,
|
||||
};
|
||||
|
||||
// we need to clone this before the next step to make sure we get the old one
|
||||
let sysroot_clone = bind_mount(&sysroot, "")?;
|
||||
|
||||
// Ideally we build the new root filesystem together before we mount it, but that only works on
|
||||
// 6.15 and later. Before 6.15 we can't mount into a floating tree, so mount it first. This
|
||||
// will leave an abandoned clone of the sysroot mounted under it, but that's OK for now.
|
||||
if cfg!(feature = "pre-6.15") {
|
||||
mount_at(&new_root, CWD, &args.sysroot)?;
|
||||
}
|
||||
|
||||
if config.root.transient {
|
||||
overlay_transient(&new_root)?;
|
||||
}
|
||||
|
||||
match mount_at(&sysroot_clone, &new_root, "sysroot") {
|
||||
Ok(()) | Err(Errno::NOENT) => {}
|
||||
Err(err) => Err(err)?,
|
||||
}
|
||||
|
||||
// etc + var
|
||||
let state = open_dir(open_dir(&sysroot, "state/deploy")?, image.to_hex())?;
|
||||
mount_subdir(&new_root, &state, "etc", config.etc, MountType::Overlay)?;
|
||||
mount_subdir(&new_root, &state, "var", config.var, MountType::Bind)?;
|
||||
|
||||
if cfg!(not(feature = "pre-6.15")) {
|
||||
// Replace the /sysroot with the new composed root filesystem
|
||||
unmount(&args.sysroot, UnmountFlags::DETACH)?;
|
||||
mount_at(&new_root, CWD, &args.sysroot)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user