Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cgwalters committed Dec 2, 2023
1 parent 06f214b commit 1b2b5f7
Showing 1 changed file with 199 additions and 0 deletions.
199 changes: 199 additions & 0 deletions lib/src/podman_ostree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use std::cell::OnceCell;
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
use std::path::{Path, PathBuf};
use std::sync::Arc;

use anyhow::{Context, Result};

use cap_std::fs::Dir;
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::DirBuilder;
use cap_std_ext::cap_std::io_lifetimes::AsFilelike;
use cap_std_ext::cap_tempfile::{TempDir, TempFile};
use cap_std_ext::cmdext::CapStdExtCommandExt;
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use ostree_ext::sysroot::SysrootLock;
use rustix::fd::AsFd;
use std::ffi::OsStr;
use std::os::unix::fs::MetadataExt;

use crate::podman::PodmanInspectGraphDriver;
use crate::utils::sync_cmd_in_root;

const OSTREE_CONTAINER_IMAGE_REF_PREFIX: &str = "ostree-container/image";

fn image_commit_ostree_ref(imageid: &str) -> String {
format!("{OSTREE_CONTAINER_IMAGE_REF_PREFIX}/{imageid}")
}

struct MergeState<'a> {
trash: &'a Dir,
can_clone: bool,
}

fn merge_layer(
layer: &Dir,
pathbuf: &mut std::path::PathBuf,
output: &Dir,
state: &MergeState,
) -> Result<()> {
let mut layer_trash = None;
let mut move_to_trash = |src: &Path, name: &OsStr| -> anyhow::Result<()> {
if layer_trash.is_none() {
layer_trash = Some(TempDir::new_in(state.trash)?);
}
let layer_trash = layer_trash.as_ref().unwrap();
output
.rename(src, layer_trash, name)
.with_context(|| format!("Moving {name:?} to trash"))?;
Ok(())
};
for elt in layer.read_dir(&pathbuf)? {
let elt = elt?;
let name = elt.file_name();
pathbuf.push(&name);
let src_meta = elt.metadata()?;
let src_ftype = src_meta.file_type();
let target_meta = output
.symlink_metadata_optional(&pathbuf)
.context("Querying target")?;
if src_ftype.is_dir() {
let mut needs_create = true;
if let Some(target_meta) = target_meta {
if target_meta.is_dir() {
needs_create = false;
} else {
move_to_trash(&pathbuf, &name)?;
}
}
if needs_create {
let mut db = DirBuilder::new();
db.mode(src_meta.mode());
layer
.create_dir_with(&pathbuf, &db)
.with_context(|| format!("Creating {pathbuf:?}"))?;
}
// Now recurse
merge_layer(layer, pathbuf, output, state)?;
} else if (src_meta.mode() & libc::S_IFMT) == libc::S_IFCHR && src_meta.rdev() == 0 {
// It's a whiteout
if target_meta.is_some() {
move_to_trash(&pathbuf, &name)?;
}
} else {
if target_meta.is_some() {
move_to_trash(&pathbuf, &name)?;
}
if src_meta.is_symlink() {
let target =
cap_primitives::fs::read_link_contents(&layer.as_filelike_view(), &pathbuf)
.with_context(|| format!("Reading link {pathbuf:?}"))?;
cap_primitives::fs::symlink_contents(target, &output.as_filelike_view(), &pathbuf)
.with_context(|| format!("Writing symlink {pathbuf:?}"))?;
} else {
let mut openopts = cap_std::fs::OpenOptions::new();
openopts.create_new(true);
openopts.mode(src_meta.mode());
let src = layer
.open(&pathbuf)
.with_context(|| format!("Opening src {pathbuf:?}"))?;
if state.can_clone {
let dest = output
.open_with(&pathbuf, &openopts)
.with_context(|| format!("Opening dest {pathbuf:?}"))?;
rustix::fs::ioctl_ficlone(src.as_fd(), dest.as_fd()).context("Cloning")?;
} else {
layer
.hard_link(&pathbuf, output, &pathbuf)
.context("Hard linking")?;
}
}
}
assert!(pathbuf.pop());
}
Ok(())
}

#[context("Squashing to tempdir")]
async fn generate_squashed_dir(
rootfs: &Dir,
graph: PodmanInspectGraphDriver,
) -> Result<cap_std_ext::cap_tempfile::TempDir> {
let ostree_tmp = &rootfs.open_dir("ostree/repo/tmp")?;
let td = TempDir::new_in(ostree_tmp)?;
// We put files/directories which should be deleted here; they're processed asynchronously
let trashdir = TempDir::new_in(ostree_tmp)?;
anyhow::ensure!(graph.name == "overlay");
let rootfs = rootfs.try_clone()?;
let td = tokio::task::spawn_blocking(move || {
let can_clone = OnceCell::<bool>::new();
for layer in graph.data.layers() {
// TODO: Does this actually work when operating on a non-default root?
let layer = layer.trim_start_matches('/');
let layer = rootfs
.open_dir(layer)
.with_context(|| format!("Opening {layer}"))?;
if can_clone.get().is_none() {
let src = TempFile::new(&layer)?;
let dest = TempFile::new(&td)?;
let did_clone =
rustix::fs::ioctl_ficlone(src.as_file().as_fd(), dest.as_file().as_fd())
.is_ok();
can_clone.get_or_init(|| did_clone);
}
let mut pathbuf = PathBuf::from(".");
let mergestate = MergeState {
trash: &trashdir,
can_clone: *can_clone.get().unwrap(),
};
merge_layer(&layer, &mut pathbuf, &td, &mergestate)?;
}
anyhow::Ok(td)
})
.await??;
Ok(td)
}

pub(crate) struct CommitResult {
pub(crate) manifest_digest: String,
pub(crate) version: Option<String>,
pub(crate) ostree_ref: String,
}

/// Given an image in containers-storage, generate an ostree commit from it
pub(crate) async fn commit_image_to_ostree(
sysroot: &SysrootLock,
imageid: &str,
) -> Result<CommitResult> {
let rootfs = &Dir::reopen_dir(&crate::utils::sysroot_fd_borrowed(sysroot))?;
let cid = crate::podman::temporary_container_for_image(rootfs, imageid).await?;
let mount_path = &crate::podman::podman_mount(rootfs, &cid).await?;
let ostree_ref = image_commit_ostree_ref(imageid);
let mut inspect = crate::podman::podman_inspect(rootfs, imageid).await?;
let manifest_digest = inspect.digest;
let squashed = generate_squashed_dir(rootfs, inspect.graph_driver).await?;
let repo_fd = Arc::new(sysroot.repo().dfd_borrow().try_clone_to_owned()?);
let mut cmd = sync_cmd_in_root(&squashed, "ostree")?;
cmd.args([
"--repo=/proc/self/fd/3",
"commit",
"--selinux-policy",
mount_path.as_str(),
"--branch",
ostree_ref.as_str(),
"--tree=dir=.",
]);
cmd.take_fd_n(repo_fd, 3);
let mut cmd = tokio::process::Command::from(cmd);
cmd.kill_on_drop(true);
let st = cmd.status().await?;
if !st.success() {
anyhow::bail!("Failed to ostree commit: {st:?}")
}
Ok(CommitResult {
manifest_digest,
version: inspect.config.labels.remove("version"),
ostree_ref,
})
}

0 comments on commit 1b2b5f7

Please sign in to comment.