Skip to content

Commit

Permalink
container/store: Process whiteouts
Browse files Browse the repository at this point in the history
A long time ago, in a galaxy far far away, a few of us in the
ostree space did some initial work on Docker-related things; specifically
support for whiteouts landed in
ostreedev/ostree@baaf745

I wish at that time we'd realized how we could more natively
support fetching containers; but, it never occurred to me to fork
off skopeo to do all the heavy lifting for the *fetch* side, which
would have been a lot of work to reimplement particularly in C.
Oh well, better late than never!

Anyways, that whiteout processing was only designed to happen at
checkout time - i.e. when materializing the final filesystem tree.  I
think this was actually a misdesign and we should add
`ostree_mutable_tree_write_with_whiteouts` so that the whiteouts
are processed in-memory.

However for now, there's a relatively low cost to temporarily
materializing the merged tree via hardlinks and handle whiteouts
via the existing code, so let's do that.

Closes: ostreedev#273
  • Loading branch information
cgwalters committed Aug 26, 2022
1 parent 75fe6d3 commit c4b2f1b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 24 deletions.
81 changes: 61 additions & 20 deletions lib/src/container/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,34 +713,75 @@ impl ImageImporter {
let imgref = self.target_imgref.unwrap_or(self.imgref);
let state = crate::tokio_util::spawn_blocking_cancellable_flatten(
move |cancellable| -> Result<Box<LayeredImageState>> {
use cap_std_ext::rustix::fd::AsRawFd;

let cancellable = Some(cancellable);
let repo = &repo;
let txn = repo.auto_transaction(cancellable)?;
let (base_commit_tree, _) = repo.read_commit(&base_commit, cancellable)?;
let base_commit_tree = base_commit_tree.downcast::<ostree::RepoFile>().unwrap();
let base_contents_obj = base_commit_tree.tree_get_contents_checksum().unwrap();
let base_metadata_obj = base_commit_tree.tree_get_metadata_checksum().unwrap();
let mt = ostree::MutableTree::from_checksum(
repo,
&base_contents_obj,
&base_metadata_obj,
);

let devino = ostree::RepoDevInoCache::new();
let repodir = repo.dfd_as_dir()?;
let repo_tmp = repodir.open_dir("tmp")?;
let td = cap_tempfile::TempDir::new_in(&repo_tmp)?;

let rootpath = "root";
let checkout_mode = if repo.mode() == ostree::RepoMode::Bare {
ostree::RepoCheckoutMode::None
} else {
ostree::RepoCheckoutMode::User
};
let mut checkout_opts = ostree::RepoCheckoutAtOptions {
mode: checkout_mode,
overwrite_mode: ostree::RepoCheckoutOverwriteMode::UnionFiles,
devino_to_csum_cache: Some(devino.clone()),
no_copy_fallback: true,
force_copy_zerosized: true,
process_whiteouts: false,
..Default::default()
};
repo.checkout_at(
Some(&checkout_opts),
(*td).as_raw_fd(),
rootpath,
&base_commit,
cancellable,
)
.context("Checking out base commit")?;

// Layer all subsequent commits
checkout_opts.process_whiteouts = true;
for commit in layer_commits {
let (layer_tree, _) = repo.read_commit(&commit, cancellable)?;
repo.write_directory_to_mtree(&layer_tree, &mt, None, cancellable)?;
repo.checkout_at(
Some(&checkout_opts),
(*td).as_raw_fd(),
rootpath,
&commit,
cancellable,
)
.with_context(|| format!("Checking out layer {commit}"))?;
}

let merged_root = repo.write_mtree(&mt, cancellable)?;
let merged_root = merged_root.downcast::<ostree::RepoFile>().unwrap();
let merged_commit = repo.write_commit(
None,
None,
None,
Some(&metadata),
&merged_root,
let modifier =
ostree::RepoCommitModifier::new(ostree::RepoCommitModifierFlags::CONSUME, None);
modifier.set_devino_cache(&devino);

let mt = ostree::MutableTree::new();
repo.write_dfd_to_mtree(
(*td).as_raw_fd(),
rootpath,
&mt,
Some(&modifier),
cancellable,
)?;
)
.context("Writing merged filesystem to mtree")?;

let merged_root = repo
.write_mtree(&mt, cancellable)
.context("Writing mtree")?;
let merged_root = merged_root.downcast::<ostree::RepoFile>().unwrap();
let merged_commit = repo
.write_commit(None, None, None, Some(&metadata), &merged_root, cancellable)
.context("Writing commit")?;
repo.transaction_set_ref(None, &ostree_ref, Some(merged_commit.as_str()));
txn.commit(cancellable)?;
// Here we re-query state just to run through the same code path,
Expand Down
7 changes: 3 additions & 4 deletions lib/src/ostree_manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ use ostree;
use ostree::prelude::{Cast, InputStreamExtManual};
use ostree::{gio, glib};

/// Equivalent of `g_file_read()` for ostree::RepoFile to work around https://github.com/ostreedev/ostree/issues/2703
#[allow(unsafe_code)]

/// Equivalent of `g_file_read()` for ostree::RepoFile to work around an ostree bug.
pub fn repo_file_read(f: &ostree::RepoFile) -> Result<gio::InputStream, glib::Error> {
use glib::translate::*;
let stream = unsafe {
Expand All @@ -19,8 +18,8 @@ pub fn repo_file_read(f: &ostree::RepoFile) -> Result<gio::InputStream, glib::Er
if !error.is_null() {
return Err(from_glib_full(error));
}
let stream = stream as *mut gio::ffi::GInputStream;
from_glib_full(stream)
// Upcast to GInputStream here
from_glib_full(stream as *mut gio::ffi::GInputStream)
};

Ok(stream)
Expand Down
12 changes: 12 additions & 0 deletions lib/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,15 @@ async fn test_container_write_derive() -> Result<()> {
temproot.join("usr/bin/newderivedfile3"),
"newderivedfile3 v0",
)?;
// Remove the kernel directory and make a new one
let moddir = temproot.join("usr/lib/modules");
let oldkernel = "5.10.18-200.x86_64";
std::fs::create_dir_all(&moddir)?;
let oldkernel_wh = &format!(".wh.{oldkernel}");
std::fs::write(moddir.join(oldkernel_wh), "")?;
let newkdir = moddir.join("5.12.7-42.x86_64");
std::fs::create_dir_all(&newkdir)?;
std::fs::write(newkdir.join("vmlinuz"), "a new kernel")?;
ostree_ext::integrationtest::generate_derived_oci(derived_path, temproot)?;
// And v2
let derived2_path = &fixture.path.join("derived2.oci");
Expand Down Expand Up @@ -1010,6 +1019,9 @@ async fn test_container_write_derive() -> Result<()> {
let found_newderived_contents =
ostree_ext::ostree_manual::repo_file_read_to_string(derived)?;
assert_eq!(found_newderived_contents, newderivedfile_contents);

let old_kernel_dir = root.resolve_relative_path(format!("usr/lib/modules/{oldkernel}"));
assert!(!old_kernel_dir.query_exists(cancellable));
}

// Import again, but there should be no changes.
Expand Down

0 comments on commit c4b2f1b

Please sign in to comment.