Skip to content

Commit a448355

Browse files
committed
ostree-ext: Add tar_create_parent_dirs to container::ExportOpts
Part of coreos/rpm-ostree#5416 Default to previous behavior and do *not* create parent directories, however for reproducible builds we need to ensure we create them with consistent metadata. See also containers/composefs-rs#132 Signed-off-by: John Eckersberg <[email protected]>
1 parent 1f0d118 commit a448355

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

ostree-ext/src/container/encapsulate.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,14 @@ fn export_chunks(
9393
.enumerate()
9494
.map(|(i, chunk)| -> Result<_> {
9595
let mut w = ociw.create_layer(Some(opts.compression()))?;
96-
ostree_tar::export_chunk(repo, commit, chunk.content, &mut w)
97-
.with_context(|| format!("Exporting chunk {i}"))?;
96+
ostree_tar::export_chunk(
97+
repo,
98+
commit,
99+
chunk.content,
100+
&mut w,
101+
opts.tar_create_parent_dirs,
102+
)
103+
.with_context(|| format!("Exporting chunk {i}"))?;
98104
let w = w.into_inner()?;
99105
Ok((w.complete()?, chunk.name, chunk.packages))
100106
})
@@ -120,7 +126,13 @@ pub(crate) fn export_chunked(
120126

121127
// In V1, the ostree layer comes first
122128
let mut w = ociw.create_layer(compression)?;
123-
ostree_tar::export_final_chunk(repo, commit, chunking.remainder, &mut w)?;
129+
ostree_tar::export_final_chunk(
130+
repo,
131+
commit,
132+
chunking.remainder,
133+
&mut w,
134+
opts.tar_create_parent_dirs,
135+
)?;
124136
let w = w.into_inner()?;
125137
let ostree_layer = w.complete()?;
126138

@@ -418,6 +430,8 @@ pub struct ExportOpts<'m, 'o> {
418430
pub contentmeta: Option<&'o ObjectMetaSized>,
419431
/// Sets the created tag in the image manifest.
420432
pub created: Option<String>,
433+
/// Whether to explicitly create all parent directories in the tar layers.
434+
pub tar_create_parent_dirs: bool,
421435
}
422436

423437
impl ExportOpts<'_, '_> {

ostree-ext/src/tar/export.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,52 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
636636
.append_data(&mut header, "var/tmp", std::io::empty())?;
637637
Ok(())
638638
}
639+
640+
fn write_parents_of(
641+
&mut self,
642+
path: &Utf8Path,
643+
cache: &mut HashSet<Utf8PathBuf>,
644+
) -> Result<()> {
645+
let Some(parent) = path.parent() else {
646+
return Ok(());
647+
};
648+
649+
if parent.components().count() == 0 {
650+
return Ok(());
651+
}
652+
653+
if cache.contains(parent) {
654+
return Ok(());
655+
}
656+
657+
self.write_parents_of(parent, cache)?;
658+
659+
let inserted = cache.insert(parent.to_owned());
660+
debug_assert!(inserted);
661+
662+
let root = self
663+
.repo
664+
.read_commit(&self.commit_checksum, gio::Cancellable::NONE)?
665+
.0;
666+
let parent_file = root.resolve_relative_path(unmap_path(parent).as_ref());
667+
let queryattrs = "unix::*";
668+
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
669+
let stat = parent_file.query_info(&queryattrs, queryflags, gio::Cancellable::NONE)?;
670+
let uid = stat.attribute_uint32(gio::FILE_ATTRIBUTE_UNIX_UID);
671+
let gid = stat.attribute_uint32(gio::FILE_ATTRIBUTE_UNIX_GID);
672+
let orig_mode = stat.attribute_uint32(gio::FILE_ATTRIBUTE_UNIX_MODE);
673+
let mode = self.filter_mode(orig_mode);
674+
675+
let mut header = tar::Header::new_gnu();
676+
header.set_entry_type(tar::EntryType::Directory);
677+
header.set_size(0);
678+
header.set_uid(uid as u64);
679+
header.set_gid(gid as u64);
680+
header.set_mode(mode);
681+
self.out
682+
.append_data(&mut header, parent, std::io::empty())?;
683+
Ok(())
684+
}
639685
}
640686

641687
/// Recursively walk an OSTree commit and generate data into a `[tar::Builder]`
@@ -684,12 +730,17 @@ fn path_for_tar_v1(p: &Utf8Path) -> &Utf8Path {
684730
fn write_chunk<W: std::io::Write>(
685731
writer: &mut OstreeTarWriter<W>,
686732
chunk: chunking::ChunkMapping,
733+
create_parent_dirs: bool,
687734
) -> Result<()> {
735+
let mut cache = std::collections::HashSet::new();
688736
for (checksum, (_size, paths)) in chunk.into_iter() {
689737
let (objpath, h) = writer.append_content(checksum.borrow())?;
690738
for path in paths.iter() {
691739
let path = path_for_tar_v1(path);
692740
let h = h.clone();
741+
if create_parent_dirs {
742+
writer.write_parents_of(&path, &mut cache)?;
743+
}
693744
writer.append_content_hardlink(&objpath, h, path)?;
694745
}
695746
}
@@ -702,13 +753,14 @@ pub(crate) fn export_chunk<W: std::io::Write>(
702753
commit: &str,
703754
chunk: chunking::ChunkMapping,
704755
out: &mut tar::Builder<W>,
756+
create_parent_dirs: bool,
705757
) -> Result<()> {
706758
// For chunking, we default to format version 1
707759
#[allow(clippy::needless_update)]
708760
let opts = ExportOptions;
709761
let writer = &mut OstreeTarWriter::new(repo, commit, out, opts)?;
710762
writer.write_repo_structure()?;
711-
write_chunk(writer, chunk)
763+
write_chunk(writer, chunk, create_parent_dirs)
712764
}
713765

714766
/// Output the last chunk in a chunking.
@@ -718,6 +770,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
718770
commit_checksum: &str,
719771
remainder: chunking::Chunk,
720772
out: &mut tar::Builder<W>,
773+
create_parent_dirs: bool,
721774
) -> Result<()> {
722775
let options = ExportOptions;
723776
let writer = &mut OstreeTarWriter::new(repo, commit_checksum, out, options)?;
@@ -726,7 +779,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
726779
writer.structure_only = true;
727780
writer.write_commit()?;
728781
writer.structure_only = false;
729-
write_chunk(writer, remainder.content)
782+
write_chunk(writer, remainder.content, create_parent_dirs)
730783
}
731784

732785
/// Process an exported tar stream, and update the detached metadata.

0 commit comments

Comments
 (0)