1use std::collections::{HashMap, HashSet};
2use std::fmt::Display;
3use std::io::{self, Read};
4use std::path::PathBuf;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use async_trait::async_trait;
8
9use super::cache::*;
10use super::consts::*;
11use super::file::*;
12use super::layer::*;
13
14use flate2::read::GzDecoder;
15use flate2::write::GzEncoder;
16use flate2::Compression;
17use tar::*;
18use tokio::io::AsyncWriteExt;
19
20#[async_trait]
21pub trait Packable {
22 async fn export_layers(
24 &self,
25 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
26 ) -> io::Result<Vec<u8>>;
27
28 async fn import_layers(
34 &self,
35 pack: &[u8],
36 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
37 ) -> io::Result<()>;
38}
39
40#[async_trait]
41impl<T: PersistentLayerStore> Packable for T {
42 async fn export_layers(
43 &self,
44 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
45 ) -> io::Result<Vec<u8>> {
46 let mtime = SystemTime::now()
47 .duration_since(UNIX_EPOCH)
48 .unwrap()
49 .as_secs();
50
51 let mut enc = GzEncoder::new(Vec::new(), Compression::default());
52 {
53 let mut tar = tar::Builder::new(&mut enc);
54 for id in layer_ids {
55 tar_append_layer(&mut tar, self, id, mtime).await?;
56 }
57 tar.finish().unwrap();
58 }
59 Ok(enc.finish().unwrap())
61 }
62
63 async fn import_layers(
64 &self,
65 pack: &[u8],
66 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
67 ) -> io::Result<()> {
68 let mut layer_id_set = HashSet::new();
69 for id in layer_ids {
70 layer_id_set.insert(name_to_string(id));
71 self.create_named_directory(id).await?;
72 }
73
74 let handle = tokio::runtime::Handle::current();
75 tokio::task::block_in_place(|| {
76 let cursor = io::Cursor::new(pack);
77 let tar = GzDecoder::new(cursor);
78 let mut archive = Archive::new(tar);
79
80 for e in archive.entries()? {
85 let mut entry = e?;
86 let path = entry.path()?;
87 let os_file_name = path.file_name().unwrap();
88 let file_name = os_file_name
89 .to_str()
90 .ok_or_else(|| {
91 io::Error::new(
92 io::ErrorKind::InvalidData,
93 "unexpected non-utf8 directory name",
94 )
95 })?
96 .to_owned();
97
98 let layer_id = path.iter().next().and_then(|p| p.to_str()).unwrap_or("");
100
101 if layer_id_set.contains(layer_id) {
102 let layer_id_arr = string_to_name(layer_id).unwrap();
106
107 let header = entry.header();
108 if !header.entry_type().is_file() {
109 continue;
110 }
111
112 let mut content = Vec::with_capacity(header.size()? as usize);
113 entry.read_to_end(&mut content)?;
114
115 handle.block_on(async move {
116 let file = self.get_file(layer_id_arr, &file_name).await?;
117 let mut writer = file.open_write().await?;
118 writer.write_all(&content).await?;
119 writer.flush().await?;
120 writer.sync_all().await?;
121
122 Ok::<_, io::Error>(())
123 })?;
124 }
125 }
126
127 for layer_id in layer_id_set {
128 let layer_id_arr = string_to_name(&layer_id).unwrap();
129 handle.block_on(self.finalize_layer(layer_id_arr))?;
130 }
131
132 Ok(())
133 })
134 }
135}
136
137async fn tar_append_file<S: PersistentLayerStore, W: io::Write>(
138 store: &S,
139 tar: &mut tar::Builder<W>,
140 layer: [u32; 5],
141 layer_path: &PathBuf,
142 file_name: &str,
143 mtime: u64,
144) -> io::Result<()> {
145 if store.file_exists(layer, file_name).await? {
146 let file = store.get_file(layer, file_name).await?;
147 let contents = file.map().await?;
148 let cursor = io::Cursor::new(&contents);
149
150 let path = layer_path.join(file_name);
151
152 let mut header = Header::new_gnu();
153 header.set_mode(0o644);
154 header.set_size(file.size().await? as u64);
155 header.set_mtime(mtime);
156 tokio::task::block_in_place(|| tar.append_data(&mut header, path, cursor).unwrap());
157
158 Ok(())
159 } else {
160 Err(io::Error::new(
161 io::ErrorKind::NotFound,
162 "file does not exist",
163 ))
164 }
165}
166
167async fn tar_append_file_if_exists<S: PersistentLayerStore, W: io::Write>(
168 store: &S,
169 tar: &mut tar::Builder<W>,
170 layer: [u32; 5],
171 layer_path: &PathBuf,
172 file_name: &str,
173 mtime: u64,
174) -> io::Result<()> {
175 if store.file_exists(layer, file_name).await? {
176 let file = store.get_file(layer, file_name).await?;
177 let contents = file.map().await?;
178 let cursor = io::Cursor::new(&contents);
179
180 let path = layer_path.join(file_name);
181
182 let mut header = Header::new_gnu();
183 header.set_mode(0o644);
184 header.set_size(file.size().await? as u64);
185 header.set_mtime(mtime);
186 tokio::task::block_in_place(|| tar.append_data(&mut header, path, cursor).unwrap());
187 }
188
189 Ok(())
190}
191
192async fn tar_append_layer<W: io::Write, S: PersistentLayerStore>(
193 tar: &mut tar::Builder<W>,
194 store: &S,
195 layer: [u32; 5],
196 mtime: u64,
197) -> io::Result<()> {
198 let mut header = Header::new_gnu();
199 header.set_mode(0o755);
200 header.set_entry_type(EntryType::Directory);
201 header.set_mtime(mtime);
202 header.set_size(0);
203 let layer_name = name_to_string(layer);
204 let mut path = PathBuf::new();
205 path.push(layer_name);
206 tokio::task::block_in_place(|| {
207 tar.append_data(&mut header, &path, std::io::empty())
208 .unwrap()
209 });
210
211 for f in &SHARED_REQUIRED_FILES {
212 tar_append_file(store, tar, layer, &path, f, mtime).await?;
213 }
214 for f in &SHARED_OPTIONAL_FILES {
215 if f == &FILENAMES.rollup {
216 continue;
218 }
219 tar_append_file_if_exists(store, tar, layer, &path, f, mtime).await?;
220 }
221 if store.file_exists(layer, FILENAMES.parent).await? {
222 for f in &CHILD_LAYER_REQUIRED_FILES {
224 tar_append_file(store, tar, layer, &path, f, mtime).await?;
225 }
226 for f in &CHILD_LAYER_OPTIONAL_FILES {
227 tar_append_file_if_exists(store, tar, layer, &path, f, mtime).await?;
228 }
229 } else {
230 for f in &BASE_LAYER_REQUIRED_FILES {
232 tar_append_file(store, tar, layer, &path, f, mtime).await?;
233 }
234 for f in &BASE_LAYER_OPTIONAL_FILES {
235 tar_append_file_if_exists(store, tar, layer, &path, f, mtime).await?;
236 }
237 }
238
239 Ok(())
240}
241
242#[derive(Debug)]
243pub enum PackError {
244 LayerNotFound,
245 Io(io::Error),
246 Utf8Error(std::str::Utf8Error),
247}
248
249impl Display for PackError {
250 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
251 write!(formatter, "{:?}", self)
252 }
253}
254
255impl From<io::Error> for PackError {
256 fn from(err: io::Error) -> Self {
257 Self::Io(err)
258 }
259}
260impl From<std::str::Utf8Error> for PackError {
261 fn from(err: std::str::Utf8Error) -> Self {
262 Self::Utf8Error(err)
263 }
264}
265
266pub fn pack_layer_parents<R: io::Read>(
267 readable: R,
268) -> Result<HashMap<[u32; 5], Option<[u32; 5]>>, PackError> {
269 let tar = GzDecoder::new(readable);
270 let mut archive = Archive::new(tar);
271
272 let mut result_map = HashMap::new();
274
275 for e in archive.entries()? {
276 let mut entry = e?;
277 let path = entry.path()?;
278
279 let id = string_to_name(
280 path.iter()
281 .next()
282 .expect("expected path to have at least one component")
283 .to_str()
284 .expect("expected proper unicode path"),
285 )?;
286
287 if path.file_name().expect("expected path to have a filename") == "parent.hex" {
288 let mut parent_id_bytes = [0u8; 40];
291 entry.read_exact(&mut parent_id_bytes)?;
292 let parent_id_str = std::str::from_utf8(&parent_id_bytes)?;
293 let parent_id = string_to_name(parent_id_str)?;
294
295 result_map.insert(id, Some(parent_id));
296 } else {
297 result_map.entry(id).or_insert(None);
301 }
302 }
303
304 Ok(result_map)
305}
306
307#[async_trait]
308impl Packable for CachedLayerStore {
309 async fn export_layers(
310 &self,
311 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
312 ) -> io::Result<Vec<u8>> {
313 self.inner.export_layers(layer_ids).await
314 }
315
316 async fn import_layers(
317 &self,
318 pack: &[u8],
319 layer_ids: Box<dyn Iterator<Item = [u32; 5]> + Send>,
320 ) -> io::Result<()> {
321 self.inner.import_layers(pack, layer_ids).await
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::layer::*;
329 use crate::storage::directory::*;
330 use std::sync::Arc;
331 use tempfile::tempdir;
332
333 #[tokio::test(flavor = "multi_thread")]
334 async fn export_import_layer_with_rollup() {
335 let dir1 = tempdir().unwrap();
336 let store1 = Arc::new(DirectoryLayerStore::new(dir1.path()));
337 let dir2 = tempdir().unwrap();
338 let store2 = Arc::new(DirectoryLayerStore::new(dir2.path()));
339
340 let mut builder = store1.create_base_layer().await.unwrap();
341 let base_name = builder.name();
342
343 builder.add_value_triple(ValueTriple::new_node("cow", "likes", "duck"));
344 builder.add_value_triple(ValueTriple::new_node("duck", "hates", "cow"));
345
346 builder.commit_boxed().await.unwrap();
347
348 let mut builder = store1.create_child_layer(base_name).await.unwrap();
349 let child_name = builder.name();
350
351 builder.remove_value_triple(ValueTriple::new_node("duck", "hates", "cow"));
352 builder.add_value_triple(ValueTriple::new_node("duck", "likes", "cow"));
353
354 builder.commit_boxed().await.unwrap();
355
356 let unrolled_layer = store1.get_layer(child_name).await.unwrap().unwrap();
357
358 store1.clone().rollup(unrolled_layer).await.unwrap();
359
360 let export = store1
361 .export_layers(Box::new(vec![base_name, child_name].into_iter()))
362 .await
363 .unwrap();
364
365 store2
366 .import_layers(&export, Box::new(vec![base_name, child_name].into_iter()))
367 .await
368 .unwrap();
369
370 let imported_layer = store2.get_layer(child_name).await.unwrap().unwrap();
371 let triples: Vec<_> = imported_layer
372 .triples()
373 .map(|t| imported_layer.id_triple_to_string(&t).unwrap())
374 .collect();
375 assert_eq!(
376 vec![
377 ValueTriple::new_node("cow", "likes", "duck"),
378 ValueTriple::new_node("duck", "likes", "cow")
379 ],
380 triples
381 );
382 }
383}