containers_api/
tarball.rs1use flate2::{write::GzEncoder, Compression};
4use std::{
5 fs::{self, File},
6 io::{self, Write},
7 path::{Path, MAIN_SEPARATOR},
8};
9use tar::Builder;
10
11#[cfg(feature = "par-compress")]
12use gzp::{
13 deflate::Gzip,
14 par::compress::{ParCompress, ParCompressBuilder},
15};
16
17pub fn dir<W, P>(buf: W, path: P) -> io::Result<()>
19where
20 W: Write,
21 P: AsRef<Path>,
22{
23 let encoder = GzEncoder::new(buf, Compression::best());
24 let path = path.as_ref();
25 ArchiveBuilder::build(encoder, path)?;
26
27 Ok(())
28}
29
30#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
31#[cfg(feature = "par-compress")]
32pub fn dir_par<P>(path: P) -> io::Result<Vec<u8>>
35where
36 P: AsRef<Path>,
37{
38 use memfile::MemFile;
39 use std::io::{Read, Seek};
40
41 let tx = MemFile::create_default(&path.as_ref().to_string_lossy())?;
42 let mut rx = tx.try_clone()?;
43 let encoder: ParCompress<Gzip> = ParCompressBuilder::new().from_writer(tx);
44
45 let path = path.as_ref();
46 ArchiveBuilder::build(encoder, path)?;
47
48 rx.rewind()?;
49 let mut data = vec![];
50 rx.read_to_end(&mut data)?;
51 Ok(data)
52}
53
54#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd")))]
55#[cfg(feature = "par-compress")]
56pub fn dir_par<P>(path: P) -> io::Result<Vec<u8>>
59where
60 P: AsRef<Path>,
61{
62 use std::io::{Read, Seek};
63
64 let tmp_dir = tempfile::tempdir()?;
65 let tmp_file_path = tmp_dir.path().join("data");
66 let tx = std::fs::File::create(&tmp_file_path)?;
67
68 let encoder: ParCompress<Gzip> = ParCompressBuilder::new().from_writer(tx);
69
70 let path = path.as_ref();
71 ArchiveBuilder::build(encoder, path)?;
72
73 let mut rx = std::fs::File::open(&tmp_file_path)?;
74 rx.rewind()?;
75 let mut data = vec![];
76 rx.read_to_end(&mut data)?;
77 Ok(data)
78}
79
80fn resolve_base_path(canonical_path: &Path) -> io::Result<String> {
81 let mut base_path_str = canonical_path
82 .to_str()
83 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid base path"))?
84 .to_owned();
85 if let Some(last) = base_path_str.chars().last() {
86 if last != MAIN_SEPARATOR {
87 base_path_str.push(MAIN_SEPARATOR)
88 }
89 }
90 Ok(base_path_str)
91}
92
93struct ArchiveBuilder<W: Write> {
94 archive: Builder<W>,
95 base_path: String,
96}
97
98impl<W: Write> ArchiveBuilder<W> {
99 fn build(buf: W, path: &Path) -> io::Result<()> {
100 let canonical = path.canonicalize()?;
101 let mut builder = Self::new(buf, &canonical)?;
102 builder.bundle(&canonical, false)?;
103 builder.archive.finish()?;
104 builder.archive.into_inner()?.flush()
105 }
106
107 fn new(buf: W, canonical: &Path) -> io::Result<Self> {
108 let base_path = resolve_base_path(canonical)?;
109
110 Ok(Self {
111 archive: Builder::new(buf),
112 base_path,
113 })
114 }
115
116 fn bundle(&mut self, dir: &Path, bundle_dir: bool) -> io::Result<()> {
118 if fs::metadata(dir)?.is_dir() {
119 if bundle_dir {
120 self.append_entry(dir)?;
121 }
122 for entry in fs::read_dir(dir)? {
123 let entry = entry?;
124 if fs::metadata(entry.path())?.is_dir() {
125 self.bundle(&entry.path(), true)?;
126 } else {
127 self.append_entry(entry.path().as_path())?
128 }
129 }
130 }
131 Ok(())
132 }
133
134 fn append_entry(&mut self, path: &Path) -> io::Result<()> {
135 let canonical = path.canonicalize()?;
136 let relativized = canonical
137 .to_str()
138 .ok_or_else(|| {
139 io::Error::new(io::ErrorKind::InvalidInput, "invalid canonicalized path")
140 })?
141 .trim_start_matches(&self.base_path[..]);
142 if path.is_dir() {
143 self.archive
144 .append_dir(Path::new(relativized), &canonical)?
145 } else {
146 self.archive
147 .append_file(Path::new(relativized), &mut File::open(&canonical)?)?
148 }
149 Ok(())
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use flate2::read::GzDecoder;
157 use tar::Archive;
158 const N_DIRS: usize = 3;
159 const N_ENTRIES: usize = 10;
160
161 fn _prepare_dirs(tmp: &std::path::Path) {
162 for i in 1..=N_DIRS {
163 let d_path = tmp.join(&format!("d{i}"));
164 std::fs::create_dir(&d_path).unwrap();
165 for j in 1..=N_ENTRIES {
166 let f_path = d_path.join(&format!("f{}", i * j));
167 let mut f = std::fs::File::create(&f_path).unwrap();
168 let _ = f.write(&[j as u8]).unwrap();
169 f.flush().unwrap();
170 }
171 }
172 }
173
174 fn _verify_archive(buf: &[u8]) {
175 let decoder = GzDecoder::new(buf);
176 let mut archive = Archive::new(decoder);
177
178 let tmp = tempfile::tempdir().unwrap();
179 archive.unpack(tmp.path()).unwrap();
180
181 for i in 1..=N_DIRS {
182 let d_path = tmp.path().join(&format!("d{i}"));
183 assert!(d_path.exists());
184 for j in 1..=N_ENTRIES {
185 let f_path = d_path.join(&format!("f{}", i * j));
186 assert!(f_path.exists());
187 }
188 }
189 }
190
191 #[test]
192 fn creates_gzipped_dir() {
193 let tmp = tempfile::tempdir().unwrap();
194 _prepare_dirs(tmp.path());
195 let mut buf = vec![];
196 dir(&mut buf, tmp.path()).unwrap();
197 _verify_archive(&buf[..]);
198 }
199
200 #[test]
201 #[cfg(feature = "par-compress")]
202 fn creates_gzipped_dir_par() {
203 let tmp = tempfile::tempdir().unwrap();
204 _prepare_dirs(tmp.path());
205 let buf = dir_par(tmp.path()).unwrap();
206 _verify_archive(&buf[..]);
207 }
208}