Skip to content

Commit a9f01fe

Browse files
authored
Merge pull request #1364 from jeckersb/mtime
ostree-ext: Add tar_create_parent_dirs to container::ExportOpts
2 parents 19616ef + a448355 commit a9f01fe

File tree

2 files changed

+138
-16
lines changed

2 files changed

+138
-16
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: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,35 @@ mode=bare-split-xattrs
5656
/// System calls are expensive.
5757
const BUF_CAPACITY: usize = 131072;
5858

59-
/// Convert /usr/etc back to /etc
60-
fn map_path(p: &Utf8Path) -> std::borrow::Cow<Utf8Path> {
61-
match p.strip_prefix("./usr/etc") {
62-
Ok(r) => Cow::Owned(Utf8Path::new("./etc").join(r)),
59+
/// Convert `from` to `to`
60+
fn map_path_inner<'p>(
61+
p: &'p Utf8Path,
62+
from: &'_ str,
63+
to: &'_ str,
64+
) -> std::borrow::Cow<'p, Utf8Path> {
65+
match p.strip_prefix(from) {
66+
Ok(r) => {
67+
if r.components().count() > 0 {
68+
Cow::Owned(Utf8Path::new(to).join(r))
69+
} else {
70+
Cow::Owned(Utf8PathBuf::from(to))
71+
}
72+
}
6373
_ => Cow::Borrowed(p),
6474
}
6575
}
6676

77+
/// Convert /usr/etc back to /etc
78+
fn map_path(p: &Utf8Path) -> std::borrow::Cow<Utf8Path> {
79+
map_path_inner(p, "./usr/etc", "./etc")
80+
}
81+
82+
/// Convert etc to usr/etc
83+
/// Note: no leading '/' or './'
84+
fn unmap_path(p: &Utf8Path) -> std::borrow::Cow<Utf8Path> {
85+
map_path_inner(p, "etc", "usr/etc")
86+
}
87+
6788
/// Convert usr/etc back to etc for the tar stream.
6889
fn map_path_v1(p: &Utf8Path) -> &Utf8Path {
6990
debug_assert!(!p.starts_with("/") && !p.starts_with("."));
@@ -615,6 +636,52 @@ impl<'a, W: std::io::Write> OstreeTarWriter<'a, W> {
615636
.append_data(&mut header, "var/tmp", std::io::empty())?;
616637
Ok(())
617638
}
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+
}
618685
}
619686

620687
/// Recursively walk an OSTree commit and generate data into a `[tar::Builder]`
@@ -663,12 +730,17 @@ fn path_for_tar_v1(p: &Utf8Path) -> &Utf8Path {
663730
fn write_chunk<W: std::io::Write>(
664731
writer: &mut OstreeTarWriter<W>,
665732
chunk: chunking::ChunkMapping,
733+
create_parent_dirs: bool,
666734
) -> Result<()> {
735+
let mut cache = std::collections::HashSet::new();
667736
for (checksum, (_size, paths)) in chunk.into_iter() {
668737
let (objpath, h) = writer.append_content(checksum.borrow())?;
669738
for path in paths.iter() {
670739
let path = path_for_tar_v1(path);
671740
let h = h.clone();
741+
if create_parent_dirs {
742+
writer.write_parents_of(&path, &mut cache)?;
743+
}
672744
writer.append_content_hardlink(&objpath, h, path)?;
673745
}
674746
}
@@ -681,13 +753,14 @@ pub(crate) fn export_chunk<W: std::io::Write>(
681753
commit: &str,
682754
chunk: chunking::ChunkMapping,
683755
out: &mut tar::Builder<W>,
756+
create_parent_dirs: bool,
684757
) -> Result<()> {
685758
// For chunking, we default to format version 1
686759
#[allow(clippy::needless_update)]
687760
let opts = ExportOptions;
688761
let writer = &mut OstreeTarWriter::new(repo, commit, out, opts)?;
689762
writer.write_repo_structure()?;
690-
write_chunk(writer, chunk)
763+
write_chunk(writer, chunk, create_parent_dirs)
691764
}
692765

693766
/// Output the last chunk in a chunking.
@@ -697,6 +770,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
697770
commit_checksum: &str,
698771
remainder: chunking::Chunk,
699772
out: &mut tar::Builder<W>,
773+
create_parent_dirs: bool,
700774
) -> Result<()> {
701775
let options = ExportOptions;
702776
let writer = &mut OstreeTarWriter::new(repo, commit_checksum, out, options)?;
@@ -705,7 +779,7 @@ pub(crate) fn export_final_chunk<W: std::io::Write>(
705779
writer.structure_only = true;
706780
writer.write_commit()?;
707781
writer.structure_only = false;
708-
write_chunk(writer, remainder.content)
782+
write_chunk(writer, remainder.content, create_parent_dirs)
709783
}
710784

711785
/// Process an exported tar stream, and update the detached metadata.
@@ -799,19 +873,53 @@ mod tests {
799873

800874
#[test]
801875
fn test_map_path() {
802-
assert_eq!(map_path("/".into()), Utf8Path::new("/"));
803876
assert_eq!(
804-
map_path("./usr/etc/blah".into()),
805-
Utf8Path::new("./etc/blah")
877+
map_path("/".into()).as_os_str(),
878+
Utf8Path::new("/").as_os_str()
879+
);
880+
assert_eq!(
881+
map_path("./usr/etc/blah".into()).as_os_str(),
882+
Utf8Path::new("./etc/blah").as_os_str()
806883
);
807884
for unchanged in ["boot", "usr/bin", "usr/lib/foo"].iter().map(Utf8Path::new) {
808-
assert_eq!(unchanged, map_path_v1(unchanged));
885+
assert_eq!(unchanged.as_os_str(), map_path_v1(unchanged).as_os_str());
809886
}
810887

811-
assert_eq!(Utf8Path::new("etc"), map_path_v1(Utf8Path::new("usr/etc")));
812888
assert_eq!(
813-
Utf8Path::new("etc/foo"),
814-
map_path_v1(Utf8Path::new("usr/etc/foo"))
889+
Utf8Path::new("etc").as_os_str(),
890+
map_path_v1(Utf8Path::new("usr/etc")).as_os_str()
891+
);
892+
assert_eq!(
893+
Utf8Path::new("etc/foo").as_os_str(),
894+
map_path_v1(Utf8Path::new("usr/etc/foo")).as_os_str()
895+
);
896+
}
897+
898+
#[test]
899+
fn test_unmap_path() {
900+
assert_eq!(
901+
unmap_path("/".into()).as_os_str(),
902+
Utf8Path::new("/").as_os_str()
903+
);
904+
assert_eq!(
905+
unmap_path("/etc".into()).as_os_str(),
906+
Utf8Path::new("/etc").as_os_str()
907+
);
908+
assert_eq!(
909+
unmap_path("/usr/etc".into()).as_os_str(),
910+
Utf8Path::new("/usr/etc").as_os_str()
911+
);
912+
assert_eq!(
913+
unmap_path("usr/etc".into()).as_os_str(),
914+
Utf8Path::new("usr/etc").as_os_str()
915+
);
916+
assert_eq!(
917+
unmap_path("etc".into()).as_os_str(),
918+
Utf8Path::new("usr/etc").as_os_str()
919+
);
920+
assert_eq!(
921+
unmap_path("etc/blah".into()).as_os_str(),
922+
Utf8Path::new("usr/etc/blah").as_os_str()
815923
);
816924
}
817925

0 commit comments

Comments
 (0)