Skip to content

Commit 9809224

Browse files
committed
Introduce bootc-owned container store, use for bound images
WIP for #721 Signed-off-by: Colin Walters <[email protected]>
1 parent 9d149a1 commit 9809224

File tree

4 files changed

+175
-57
lines changed

4 files changed

+175
-57
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ install:
1111
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
1212
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/bound-images.d
1313
install -d -m 0755 $(DESTDIR)$(prefix)/lib/bootc/kargs.d
14+
ln -s /sysroot/ostree/bootc/storage $(DESTDIR)$(prefix)/lib/bootc/storage
1415
install -d -m 0755 $(DESTDIR)$(prefix)/lib/systemd/system-generators/
1516
ln -f $(DESTDIR)$(prefix)/bin/bootc $(DESTDIR)$(prefix)/lib/systemd/system-generators/bootc-systemd-generator
1617
install -d $(DESTDIR)$(prefix)/lib/bootc/install

lib/src/imgstorage.rs

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//! # bootc-managed container storage
2+
//!
3+
//! The default storage for this project uses ostree, canonically storing all of its state in
4+
//! `/sysroot/ostree`.
5+
//!
6+
//! This containers-storage: which canonically lives in `/sysroot/ostree/bootc`.
7+
8+
use std::cell::OnceCell;
9+
use std::sync::Arc;
10+
11+
use anyhow::{Context, Result};
12+
use camino::Utf8Path;
13+
use cap_std_ext::{
14+
cap_std::fs_utf8::Dir, cmdext::CapStdExtCommandExt, dirext::CapStdExtDirExtUtf8,
15+
};
16+
use fn_error_context::context;
17+
use rustix::fd::AsFd;
18+
use std::os::fd::OwnedFd;
19+
20+
use crate::task::Task;
21+
22+
/// The path to the storage, relative to the physical system root.
23+
pub(crate) const SUBPATH: &str = "ostree/bootc/storage";
24+
/// The name of the overlay backend, created under SUBPATH above.
25+
/// Used to signal that the storage is fully initialized.
26+
const OVERLAY: &str = "overlay";
27+
/// The path to the "runroot" with transient runtime state; this is
28+
/// relative to the /run directory
29+
const RUNROOT: &str = "bootc/storage";
30+
31+
pub(crate) struct Storage {
32+
root: Dir,
33+
#[allow(dead_code)]
34+
run: Dir,
35+
// A temporary mount point because skopeo wants absolute paths
36+
temp_mount: OnceCell<Result<tempfile::TempDir>>,
37+
}
38+
39+
impl Storage {
40+
fn podman_task_in(sysroot: OwnedFd, run: OwnedFd) -> Result<crate::task::Task> {
41+
let mut t = Task::new_quiet("podman");
42+
// podman expects absolute paths for these, so use /proc/self/fd
43+
{
44+
let sysroot_fd: Arc<OwnedFd> = Arc::new(sysroot);
45+
t.cmd.take_fd_n(sysroot_fd, 3);
46+
}
47+
{
48+
let run_fd: Arc<OwnedFd> = Arc::new(run);
49+
t.cmd.take_fd_n(run_fd, 4);
50+
}
51+
t = t.args(["--root=/proc/self/fd/3", "--runroot=/proc/self/fd/4"]);
52+
Ok(t)
53+
}
54+
55+
#[allow(dead_code)]
56+
fn podman_task(&self) -> Result<crate::task::Task> {
57+
let sysroot = self.root.as_cap_std().try_clone()?.into_std_file().into();
58+
let run = self.run.as_cap_std().try_clone()?.into_std_file().into();
59+
Self::podman_task_in(sysroot, run)
60+
}
61+
62+
#[context("Creating imgstorage")]
63+
pub(crate) fn create(sysroot: &Dir, run: &Dir) -> Result<Self> {
64+
let subpath = Utf8Path::new(SUBPATH);
65+
// SAFETY: We know there's a parent
66+
let parent = subpath.parent().unwrap();
67+
if !sysroot.try_exists(subpath.join(OVERLAY))? {
68+
let tmp = format!("{SUBPATH}.tmp");
69+
sysroot.remove_all_optional(&tmp)?;
70+
sysroot.create_dir_all(parent)?;
71+
sysroot.create_dir_all(&tmp).context("Creating tmpdir")?;
72+
// There's no explicit API to initialize a containers-storage:
73+
// root, simply passing a path will attempt to auto-create it.
74+
// We run "podman images" in the new root.
75+
Self::podman_task_in(sysroot.open_dir(&tmp)?.into(), run.try_clone()?.into())?
76+
.arg("images")
77+
.run()?;
78+
sysroot
79+
.rename(&tmp, sysroot, subpath)
80+
.context("Renaming tmpdir")?;
81+
sysroot.remove_all_optional(&tmp)?;
82+
}
83+
Self::open(sysroot, run)
84+
}
85+
86+
#[context("Opening imgstorage")]
87+
pub(crate) fn open(sysroot: &Dir, run: &Dir) -> Result<Self> {
88+
let root = sysroot.open_dir(SUBPATH).context(SUBPATH)?;
89+
// Always auto-create this if missing
90+
run.create_dir_all(RUNROOT)?;
91+
let run = run.open_dir(RUNROOT).context(RUNROOT)?;
92+
Ok(Self {
93+
root,
94+
run,
95+
temp_mount: OnceCell::new(),
96+
})
97+
}
98+
99+
/// View this storage as a directory.
100+
#[allow(dead_code)]
101+
pub(crate) fn as_dir(&self) -> &Dir {
102+
&self.root
103+
}
104+
105+
fn get_temp_mount(&self) -> Result<&Utf8Path> {
106+
match self.temp_mount.get_or_init(|| {
107+
let td = tempfile::TempDir::new()?;
108+
Task::new_quiet("mount")
109+
.args(["--bind", "."])
110+
.arg(td.path())
111+
.cwd(self.root.as_cap_std())?
112+
.run()?;
113+
Ok(td)
114+
}) {
115+
Ok(r) => Ok(r.path().try_into()?),
116+
Err(e) => Err(anyhow::Error::msg(e.to_string())),
117+
}
118+
}
119+
120+
pub(crate) fn pull_from_host_storage(&self, image: &str) -> Result<()> {
121+
// The skopeo API expects absolute paths, so we make a temporary bind
122+
let temp_mount = self.get_temp_mount()?;
123+
// And an ephemeral place for the transient state
124+
let tmp_runroot = tempfile::tempdir()?;
125+
let tmp_runroot: &Utf8Path = tmp_runroot.path().try_into()?;
126+
127+
// The destination (target stateroot) + container storage dest
128+
let storage_dest = &format!(
129+
"containers-storage:[overlay@{temp_mount}+{tmp_runroot}]"
130+
);
131+
Task::new(format!("Copying image to target: {}", image), "skopeo")
132+
.arg("copy")
133+
.arg(format!("containers-storage:{image}"))
134+
.arg(format!("{storage_dest}{image}"))
135+
.run()?;
136+
Ok(())
137+
}
138+
}

lib/src/install.rs

+35-57
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub(crate) mod config;
1111
pub(crate) mod osconfig;
1212

1313
use std::io::Write;
14-
use std::os::fd::{AsFd, OwnedFd};
14+
use std::os::fd::AsFd;
1515
use std::os::unix::process::CommandExt;
1616
use std::path::Path;
1717
use std::process::Command;
@@ -24,9 +24,9 @@ use anyhow::{anyhow, Context, Result};
2424
use camino::Utf8Path;
2525
use camino::Utf8PathBuf;
2626
use cap_std::fs::{Dir, MetadataExt};
27+
use cap_std::fs_utf8::Dir as DirUtf8;
2728
use cap_std_ext::cap_std;
2829
use cap_std_ext::cap_std::fs_utf8::DirEntry as DirEntryUtf8;
29-
use cap_std_ext::cmdext::CapStdExtCommandExt;
3030
use cap_std_ext::prelude::CapStdExtDirExt;
3131
use chrono::prelude::*;
3232
use clap::ValueEnum;
@@ -42,6 +42,7 @@ use serde::{Deserialize, Serialize};
4242

4343
use self::baseline::InstallBlockDeviceOpts;
4444
use crate::containerenv::ContainerExecutionInfo;
45+
use crate::imgstorage::Storage;
4546
use crate::mount::Filesystem;
4647
use crate::spec::ImageReference;
4748
use crate::task::Task;
@@ -548,8 +549,11 @@ pub(crate) fn print_configuration() -> Result<()> {
548549
serde_json::to_writer(stdout, &install_config).map_err(Into::into)
549550
}
550551

551-
#[context("Creating ostree deployment")]
552-
async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result<ostree::Sysroot> {
552+
#[context("Creating system root")]
553+
async fn initialize_ostree_root(
554+
state: &State,
555+
root_setup: &RootSetup,
556+
) -> Result<(ostree::Sysroot, crate::imgstorage::Storage)> {
553557
let sepolicy = state.load_policy()?;
554558
let sepolicy = sepolicy.as_ref();
555559
// Load a fd for the mounted target physical root
@@ -594,6 +598,16 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
594598
.cwd(rootfs_dir)?
595599
.run()?;
596600

601+
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs)));
602+
sysroot.load(cancellable)?;
603+
let sysroot_dir = DirUtf8::reopen_dir(&crate::utils::sysroot_fd(&sysroot))?;
604+
605+
let tmp_run = cap_std_ext::cap_tempfile::utf8::TempDir::new(cap_std::ambient_authority())?;
606+
sysroot_dir
607+
.create_dir(Utf8Path::new(crate::imgstorage::SUBPATH).parent().unwrap())
608+
.context("creating bootc dir")?;
609+
let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &*tmp_run)?;
610+
597611
// Bootstrap the initial labeling of the /ostree directory as usr_t
598612
if let Some(policy) = sepolicy {
599613
let ostree_dir = rootfs_dir.open_dir("ostree")?;
@@ -606,9 +620,7 @@ async fn initialize_ostree_root(state: &State, root_setup: &RootSetup) -> Result
606620
)?;
607621
}
608622

609-
let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs)));
610-
sysroot.load(cancellable)?;
611-
Ok(sysroot)
623+
Ok((sysroot, imgstore))
612624
}
613625

614626
#[context("Creating ostree deployment")]
@@ -1271,14 +1283,14 @@ async fn install_with_sysroot(
12711283
state: &State,
12721284
rootfs: &RootSetup,
12731285
sysroot: &ostree::Sysroot,
1286+
imgstore: &Storage,
12741287
boot_uuid: &str,
12751288
bound_images: &[crate::boundimage::ResolvedBoundImage],
12761289
) -> Result<()> {
12771290
let sysroot = SysrootLock::new_from_sysroot(&sysroot).await?;
12781291
// And actually set up the container in that root, returning a deployment and
12791292
// the aleph state (see below).
1280-
let (deployment, aleph) = install_container(state, rootfs, &sysroot).await?;
1281-
let stateroot = deployment.osname();
1293+
let (_deployment, aleph) = install_container(state, rootfs, &sysroot).await?;
12821294
// Write the aleph data that captures the system state at the time of provisioning for aid in future debugging.
12831295
rootfs
12841296
.rootfs_fd
@@ -1301,53 +1313,11 @@ async fn install_with_sysroot(
13011313
tracing::debug!("Installed bootloader");
13021314

13031315
tracing::debug!("Perfoming post-deployment operations");
1304-
if !bound_images.is_empty() {
1305-
// TODO: We shouldn't hardcode the overlay driver for source or
1306-
// target, but we currently need to in order to reference the location.
1307-
// For this one, containers-storage: is actually the *host*'s /var/lib/containers
1308-
// which we are accessing directly.
1309-
let storage_src = "containers-storage:";
1310-
// TODO: We only do this dance to initialize `/var` at install time if
1311-
// there are bound images today; it minimizes side effects.
1312-
// However going forward we really do need to handle a separate /var partition...
1313-
// and to do that we may in the general case need to run the `var.mount`
1314-
// target from the new root.
1315-
// Probably the best fix is for us to switch bound images to use the bootc storage.
1316-
let varpath = format!("ostree/deploy/{stateroot}/var");
1317-
let var = rootfs
1318-
.rootfs_fd
1319-
.open_dir(&varpath)
1320-
.with_context(|| format!("Opening {varpath}"))?;
1321-
1322-
// The skopeo API expects absolute paths, so we make a temporary bind
1323-
let tmp_dest_var_abs = tempfile::tempdir()?;
1324-
let tmp_dest_var_abs: &Utf8Path = tmp_dest_var_abs.path().try_into()?;
1325-
let mut t = Task::new("Mounting deployment /var", "mount")
1326-
.args(["--bind", "/proc/self/fd/3"])
1327-
.arg(tmp_dest_var_abs);
1328-
t.cmd.take_fd_n(Arc::new(OwnedFd::from(var)), 3);
1329-
t.run()?;
1330-
1331-
// And an ephemeral place for the transient state
1332-
let tmp_runroot = tempfile::tempdir()?;
1333-
let tmp_runroot: &Utf8Path = tmp_runroot.path().try_into()?;
1334-
1335-
// The destination (target stateroot) + container storage dest
1336-
let storage_dest = &format!(
1337-
"containers-storage:[overlay@{tmp_dest_var_abs}/lib/containers/storage+{tmp_runroot}]"
1338-
);
1339-
1340-
// Now copy each bound image from the host's container storage into the target.
1341-
for image in bound_images {
1342-
let image = image.image.as_str();
1343-
Task::new(format!("Copying image to target: {}", image), "skopeo")
1344-
.arg("copy")
1345-
.arg(format!("{storage_src}{image}"))
1346-
.arg(format!("{storage_dest}{image}"))
1347-
.run()?;
1348-
}
1316+
// Now copy each bound image from the host's container storage into the target.
1317+
for image in bound_images {
1318+
let image = image.image.as_str();
1319+
imgstore.pull_from_host_storage(image)?;
13491320
}
1350-
13511321
Ok(())
13521322
}
13531323

@@ -1397,8 +1367,16 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
13971367

13981368
// Initialize the ostree sysroot (repo, stateroot, etc.)
13991369
{
1400-
let sysroot = initialize_ostree_root(state, rootfs).await?;
1401-
install_with_sysroot(state, rootfs, &sysroot, &boot_uuid, &bound_images).await?;
1370+
let (sysroot, imgstore) = initialize_ostree_root(state, rootfs).await?;
1371+
install_with_sysroot(
1372+
state,
1373+
rootfs,
1374+
&sysroot,
1375+
&imgstore,
1376+
&boot_uuid,
1377+
&bound_images,
1378+
)
1379+
.await?;
14021380
// We must drop the sysroot here in order to close any open file
14031381
// descriptors.
14041382
}

lib/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ pub mod spec;
4545

4646
#[cfg(feature = "docgen")]
4747
mod docgen;
48+
mod imgstorage;

0 commit comments

Comments
 (0)