1use std::ffi::{OsStr, OsString};
19use std::fs::{File, OpenOptions};
20use std::io::{BufReader, Read, Seek, SeekFrom};
21use std::os::unix::ffi::OsStrExt;
22use std::path::{Path, PathBuf};
23use std::sync::Mutex;
24
25use anyhow::{anyhow, bail, Context, Result};
26use tar::{Archive, Entry, EntryType, Header};
27
28use nydus_api::enosys;
29use nydus_rafs::metadata::inode::{InodeWrapper, RafsInodeFlags, RafsV6Inode};
30use nydus_rafs::metadata::layout::v5::RafsV5Inode;
31use nydus_rafs::metadata::layout::RafsXAttrs;
32use nydus_rafs::metadata::RafsVersion;
33use nydus_storage::device::BlobFeatures;
34use nydus_storage::meta::ZranContextGenerator;
35use nydus_storage::RAFS_MAX_CHUNKS_PER_BLOB;
36use nydus_utils::compact::makedev;
37use nydus_utils::compress::zlib_random::{ZranReader, ZRAN_READER_BUF_SIZE};
38use nydus_utils::compress::ZlibDecoder;
39use nydus_utils::digest::RafsDigest;
40use nydus_utils::{div_round_up, lazy_drop, root_tracer, timing_tracer, BufReaderInfo, ByteSize};
41
42use crate::core::context::{Artifact, NoopArtifactWriter};
43
44use super::core::blob::Blob;
45use super::core::context::{
46 ArtifactWriter, BlobManager, BootstrapManager, BuildContext, BuildOutput, ConversionType,
47};
48use super::core::node::{Node, NodeInfo};
49use super::core::tree::Tree;
50use super::{build_bootstrap, dump_bootstrap, finalize_blob, Builder, TarBuilder};
51
52enum CompressionType {
53 None,
54 Gzip,
55}
56
57enum TarReader {
58 File(File),
59 BufReader(BufReader<File>),
60 BufReaderInfo(BufReaderInfo<File>),
61 BufReaderInfoSeekable(BufReaderInfo<File>),
62 TarGzFile(Box<ZlibDecoder<File>>),
63 TarGzBufReader(Box<ZlibDecoder<BufReader<File>>>),
64 ZranReader(ZranReader<File>),
65}
66
67impl Read for TarReader {
68 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
69 match self {
70 TarReader::File(f) => f.read(buf),
71 TarReader::BufReader(f) => f.read(buf),
72 TarReader::BufReaderInfo(b) => b.read(buf),
73 TarReader::BufReaderInfoSeekable(b) => b.read(buf),
74 TarReader::TarGzFile(f) => f.read(buf),
75 TarReader::TarGzBufReader(b) => b.read(buf),
76 TarReader::ZranReader(f) => f.read(buf),
77 }
78 }
79}
80
81impl TarReader {
82 fn seekable(&self) -> bool {
83 matches!(
84 self,
85 TarReader::File(_) | TarReader::BufReaderInfoSeekable(_)
86 )
87 }
88}
89
90impl Seek for TarReader {
91 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
92 match self {
93 TarReader::File(f) => f.seek(pos),
94 TarReader::BufReaderInfoSeekable(b) => b.seek(pos),
95 _ => Err(enosys!("seek() not supported!")),
96 }
97 }
98}
99
100struct TarballTreeBuilder<'a> {
101 ty: ConversionType,
102 ctx: &'a mut BuildContext,
103 blob_mgr: &'a mut BlobManager,
104 blob_writer: &'a mut dyn Artifact,
105 buf: Vec<u8>,
106 builder: TarBuilder,
107}
108
109impl<'a> TarballTreeBuilder<'a> {
110 pub fn new(
112 ty: ConversionType,
113 ctx: &'a mut BuildContext,
114 blob_mgr: &'a mut BlobManager,
115 blob_writer: &'a mut dyn Artifact,
116 layer_idx: u16,
117 ) -> Self {
118 let builder = TarBuilder::new(ctx.explicit_uidgid, layer_idx, ctx.fs_version);
119 Self {
120 ty,
121 ctx,
122 blob_mgr,
123 buf: Vec::new(),
124 blob_writer,
125 builder,
126 }
127 }
128
129 fn build_tree(&mut self) -> Result<Tree> {
130 let file = OpenOptions::new()
131 .read(true)
132 .open(self.ctx.source_path.clone())
133 .context("tarball: can not open source file for conversion")?;
134 let mut is_file = match file.metadata() {
135 Ok(md) => md.file_type().is_file(),
136 Err(_) => false,
137 };
138
139 let reader = match self.ty {
140 ConversionType::EStargzToRef
141 | ConversionType::TargzToRef
142 | ConversionType::TarToRef => match Self::detect_compression_algo(file)? {
143 (CompressionType::Gzip, buf_reader) => {
144 let generator = ZranContextGenerator::from_buf_reader(buf_reader)?;
145 let reader = generator.reader();
146 self.ctx.blob_zran_generator = Some(Mutex::new(generator));
147 self.ctx.blob_features.insert(BlobFeatures::ZRAN);
148 TarReader::ZranReader(reader)
149 }
150 (CompressionType::None, buf_reader) => {
151 self.ty = ConversionType::TarToRef;
152 let reader = BufReaderInfo::from_buf_reader(buf_reader);
153 self.ctx.blob_tar_reader = Some(reader.clone());
154 TarReader::BufReaderInfo(reader)
155 }
156 },
157 ConversionType::EStargzToRafs
158 | ConversionType::TargzToRafs
159 | ConversionType::TarToRafs => match Self::detect_compression_algo(file)? {
160 (CompressionType::Gzip, buf_reader) => {
161 if is_file {
162 let mut file = buf_reader.into_inner();
163 file.seek(SeekFrom::Start(0))?;
164 TarReader::TarGzFile(Box::new(ZlibDecoder::new(file)))
165 } else {
166 TarReader::TarGzBufReader(Box::new(ZlibDecoder::new(buf_reader)))
167 }
168 }
169 (CompressionType::None, buf_reader) => {
170 if is_file {
171 let mut file = buf_reader.into_inner();
172 file.seek(SeekFrom::Start(0))?;
173 TarReader::File(file)
174 } else {
175 TarReader::BufReader(buf_reader)
176 }
177 }
178 },
179 ConversionType::TarToTarfs => {
180 let mut reader = BufReaderInfo::from_buf_reader(BufReader::new(file));
181 self.ctx.blob_tar_reader = Some(reader.clone());
182 if !self.ctx.blob_id.is_empty() {
183 reader.enable_digest_calculation(false);
184 } else {
185 is_file = false;
187 }
188 if is_file {
190 TarReader::BufReaderInfoSeekable(reader)
191 } else {
192 TarReader::BufReaderInfo(reader)
193 }
194 }
195 _ => return Err(anyhow!("tarball: unsupported image conversion type")),
196 };
197
198 let is_seekable = reader.seekable();
199 let mut tar = Archive::new(reader);
200 tar.set_ignore_zeros(true);
201 tar.set_preserve_mtime(true);
202 tar.set_preserve_permissions(true);
203 tar.set_unpack_xattrs(true);
204
205 if self.buf.len() < self.ctx.chunk_size as usize {
207 self.buf = vec![0u8; self.ctx.chunk_size as usize];
208 }
209
210 let root = self.builder.create_directory(&[OsString::from("/")])?;
212 let mut tree = Tree::new(root);
213
214 let entries = if is_seekable {
216 tar.entries_with_seek()
217 .context("tarball: failed to read entries from tar")?
218 } else {
219 tar.entries()
220 .context("tarball: failed to read entries from tar")?
221 };
222 for entry in entries {
223 let mut entry = entry.context("tarball: failed to read entry from tar")?;
224 let path = entry
225 .path()
226 .context("tarball: failed to to get path from tar entry")?;
227 let path = PathBuf::from("/").join(path);
228 let path = path.components().as_path();
229 if !self.builder.is_stargz_special_files(path) {
230 self.parse_entry(&mut tree, &mut entry, path)?;
231 }
232 }
233
234 if self.ctx.fs_version.is_v5() {
236 Self::set_v5_dir_size(&mut tree);
237 }
238
239 Ok(tree)
240 }
241
242 fn parse_entry<R: Read>(
243 &mut self,
244 tree: &mut Tree,
245 entry: &mut Entry<R>,
246 path: &Path,
247 ) -> Result<()> {
248 let header = entry.header();
249 let entry_type = header.entry_type();
250 if entry_type.is_gnu_longname() {
251 return Err(anyhow!("tarball: unsupported gnu_longname from tar header"));
252 } else if entry_type.is_gnu_longlink() {
253 return Err(anyhow!("tarball: unsupported gnu_longlink from tar header"));
254 } else if entry_type.is_pax_local_extensions() {
255 return Err(anyhow!(
256 "tarball: unsupported pax_local_extensions from tar header"
257 ));
258 } else if entry_type.is_pax_global_extensions() {
259 return Err(anyhow!(
260 "tarball: unsupported pax_global_extensions from tar header"
261 ));
262 } else if entry_type.is_contiguous() {
263 return Err(anyhow!(
264 "tarball: unsupported contiguous entry type from tar header"
265 ));
266 } else if entry_type.is_gnu_sparse() {
267 return Err(anyhow!(
268 "tarball: unsupported gnu sparse file extension from tar header"
269 ));
270 }
271
272 let mut file_size = entry.size();
273 let name = Self::get_file_name(path)?;
274 let mode = Self::get_mode(header)?;
275 let (uid, gid) = Self::get_uid_gid(self.ctx, header)?;
276 let mtime = header.mtime().unwrap_or_default();
277 let mut flags = match self.ctx.fs_version {
278 RafsVersion::V5 => RafsInodeFlags::default(),
279 RafsVersion::V6 => RafsInodeFlags::default(),
280 };
281
282 let rdev = if entry_type.is_block_special()
284 || entry_type.is_character_special()
285 || entry_type.is_fifo()
286 {
287 let major = header
288 .device_major()
289 .context("tarball: failed to get device major from tar entry")?
290 .ok_or_else(|| anyhow!("tarball: failed to get major device from tar entry"))?;
291 let minor = header
292 .device_minor()
293 .context("tarball: failed to get device major from tar entry")?
294 .ok_or_else(|| anyhow!("tarball: failed to get minor device from tar entry"))?;
295 makedev(major as u64, minor as u64) as u32
296 } else {
297 u32::MAX
298 };
299
300 let (symlink, symlink_size) = if entry_type.is_symlink() {
302 let symlink_link_path = entry
303 .link_name()
304 .context("tarball: failed to get target path for tar symlink entry")?
305 .ok_or_else(|| anyhow!("tarball: failed to get symlink target tor tar entry"))?;
306 let symlink_size = symlink_link_path.as_os_str().byte_size();
307 if symlink_size > u16::MAX as usize {
308 bail!("tarball: symlink target from tar entry is too big");
309 }
310 file_size = symlink_size as u64;
311 flags |= RafsInodeFlags::SYMLINK;
312 (
313 Some(symlink_link_path.as_os_str().to_owned()),
314 symlink_size as u16,
315 )
316 } else {
317 (None, 0)
318 };
319
320 let mut child_count = 0;
321 if entry_type.is_file() {
322 child_count = div_round_up(file_size, self.ctx.chunk_size as u64);
323 if child_count > RAFS_MAX_CHUNKS_PER_BLOB as u64 {
324 bail!("tarball: file size 0x{:x} is too big", file_size);
325 }
326 }
327
328 let mut hardlink_target = None;
330 let ino = if entry_type.is_hard_link() {
331 let link_path = entry
332 .link_name()
333 .context("tarball: failed to get target path for tar symlink entry")?
334 .ok_or_else(|| anyhow!("tarball: failed to get symlink target tor tar entry"))?;
335 let link_path = PathBuf::from("/").join(link_path);
336 let link_path = link_path.components().as_path();
337 let targets = Node::generate_target_vec(link_path);
338 assert!(!targets.is_empty());
339 let mut tmp_tree: &Tree = tree;
340 for name in &targets[1..] {
341 match tmp_tree.get_child_idx(name.as_bytes()) {
342 Some(idx) => tmp_tree = &tmp_tree.children[idx],
343 None => {
344 bail!(
345 "tarball: unknown target {} for hardlink {}",
346 link_path.display(),
347 path.display()
348 );
349 }
350 }
351 }
352 let mut tmp_node = tmp_tree.borrow_mut_node();
353 if !tmp_node.is_reg() {
354 bail!(
355 "tarball: target {} for hardlink {} is not a regular file",
356 link_path.display(),
357 path.display()
358 );
359 }
360 hardlink_target = Some(tmp_tree);
361 flags |= RafsInodeFlags::HARDLINK;
362 tmp_node.inode.set_has_hardlink(true);
363 tmp_node.inode.ino()
364 } else {
365 self.builder.next_ino()
366 };
367
368 let mut xattrs = RafsXAttrs::new();
370 if let Some(exts) = entry.pax_extensions()? {
371 for p in exts {
372 match p {
373 Ok(pax) => {
374 let prefix = b"SCHILY.xattr.";
375 let key = pax.key_bytes();
376 if key.starts_with(prefix) {
377 let x_key = OsStr::from_bytes(&key[prefix.len()..]);
378 xattrs.add(x_key.to_os_string(), pax.value_bytes().to_vec())?;
379 }
380 }
381 Err(e) => {
382 return Err(anyhow!(
383 "tarball: failed to parse PaxExtension from tar header, {}",
384 e
385 ))
386 }
387 }
388 }
389 }
390
391 let mut inode = match self.ctx.fs_version {
392 RafsVersion::V5 => InodeWrapper::V5(RafsV5Inode {
393 i_digest: RafsDigest::default(),
394 i_parent: 0,
395 i_ino: ino,
396 i_projid: 0,
397 i_uid: uid,
398 i_gid: gid,
399 i_mode: mode,
400 i_size: file_size,
401 i_nlink: 1,
402 i_blocks: 0,
403 i_flags: flags,
404 i_child_index: 0,
405 i_child_count: child_count as u32,
406 i_name_size: name.len() as u16,
407 i_symlink_size: symlink_size,
408 i_rdev: rdev,
409 i_mtime: mtime,
410 i_mtime_nsec: 0,
411 i_reserved: [0; 8],
412 }),
413 RafsVersion::V6 => InodeWrapper::V6(RafsV6Inode {
414 i_ino: ino,
415 i_projid: 0,
416 i_uid: uid,
417 i_gid: gid,
418 i_mode: mode,
419 i_size: file_size,
420 i_nlink: 1,
421 i_blocks: 0,
422 i_flags: flags,
423 i_child_count: child_count as u32,
424 i_name_size: name.len() as u16,
425 i_symlink_size: symlink_size,
426 i_rdev: rdev,
427 i_mtime: mtime,
428 i_mtime_nsec: 0,
429 }),
430 };
431 inode.set_has_xattr(!xattrs.is_empty());
432
433 let source = PathBuf::from("/");
434 let target = Node::generate_target(path, &source);
435 let target_vec = Node::generate_target_vec(&target);
436 let info = NodeInfo {
437 explicit_uidgid: self.ctx.explicit_uidgid,
438 src_ino: ino,
439 src_dev: u64::MAX,
440 rdev: rdev as u64,
441 path: path.to_path_buf(),
442 source,
443 target,
444 target_vec,
445 symlink,
446 xattrs,
447 v6_force_extended_inode: false,
448 };
449 let mut node = Node::new(inode, info, self.builder.layer_idx);
450
451 if let Some(t) = hardlink_target {
455 let n = t.borrow_mut_node();
456 if n.inode.is_v5() {
457 node.inode.set_digest(n.inode.digest().to_owned());
458 }
459 node.inode.set_size(n.inode.size());
460 node.inode.set_child_count(n.inode.child_count());
461 node.chunks = n.chunks.clone();
462 node.set_xattr(n.info.xattrs.clone());
463 } else {
464 node.dump_node_data_with_reader(
465 self.ctx,
466 self.blob_mgr,
467 self.blob_writer,
468 Some(entry),
469 &mut self.buf,
470 )?;
471 }
472
473 if self.ctx.fs_version == RafsVersion::V5 && !entry_type.is_dir() {
475 node.v5_set_inode_blocks();
476 }
477
478 self.builder.insert_into_tree(tree, node)
479 }
480
481 fn get_uid_gid(ctx: &BuildContext, header: &Header) -> Result<(u32, u32)> {
482 let uid = if ctx.explicit_uidgid {
483 header.uid().unwrap_or_default()
484 } else {
485 0
486 };
487 let gid = if ctx.explicit_uidgid {
488 header.gid().unwrap_or_default()
489 } else {
490 0
491 };
492 if uid > u32::MAX as u64 || gid > u32::MAX as u64 {
493 bail!(
494 "tarball: uid {:x} or gid {:x} from tar entry is out of range",
495 uid,
496 gid
497 );
498 }
499
500 Ok((uid as u32, gid as u32))
501 }
502
503 fn get_mode(header: &Header) -> Result<u32> {
504 let mode = header
505 .mode()
506 .context("tarball: failed to get permission/mode from tar entry")?;
507 let ty = match header.entry_type() {
508 EntryType::Regular | EntryType::Link => libc::S_IFREG,
509 EntryType::Directory => libc::S_IFDIR,
510 EntryType::Symlink => libc::S_IFLNK,
511 EntryType::Block => libc::S_IFBLK,
512 EntryType::Char => libc::S_IFCHR,
513 EntryType::Fifo => libc::S_IFIFO,
514 _ => bail!("tarball: unsupported tar entry type"),
515 };
516 Ok((mode & !libc::S_IFMT as u32) | ty as u32)
517 }
518
519 fn get_file_name(path: &Path) -> Result<&OsStr> {
520 let name = if path == Path::new("/") {
521 path.as_os_str()
522 } else {
523 path.file_name().ok_or_else(|| {
524 anyhow!(
525 "tarball: failed to get file name from tar entry with path {}",
526 path.display()
527 )
528 })?
529 };
530 if name.len() > u16::MAX as usize {
531 bail!(
532 "tarball: file name {} from tar entry is too long",
533 name.to_str().unwrap_or_default()
534 );
535 }
536 Ok(name)
537 }
538
539 fn set_v5_dir_size(tree: &mut Tree) {
540 for c in &mut tree.children {
541 Self::set_v5_dir_size(c);
542 }
543 let mut node = tree.borrow_mut_node();
544 node.v5_set_dir_size(RafsVersion::V5, &tree.children);
545 }
546
547 fn detect_compression_algo(file: File) -> Result<(CompressionType, BufReader<File>)> {
548 let mut buf_reader = BufReader::with_capacity(ZRAN_READER_BUF_SIZE, file);
550 let mut buf = [0u8; 3];
551 buf_reader.read_exact(&mut buf)?;
552 if buf[0] == 0x1f && buf[1] == 0x8b && buf[2] == 0x08 {
553 buf_reader.seek_relative(-3).unwrap();
554 Ok((CompressionType::Gzip, buf_reader))
555 } else {
556 buf_reader.seek_relative(-3).unwrap();
557 Ok((CompressionType::None, buf_reader))
558 }
559 }
560}
561
562pub struct TarballBuilder {
564 ty: ConversionType,
565}
566
567impl TarballBuilder {
568 pub fn new(conversion_type: ConversionType) -> Self {
570 Self {
571 ty: conversion_type,
572 }
573 }
574}
575
576impl Builder for TarballBuilder {
577 fn build(
578 &mut self,
579 ctx: &mut BuildContext,
580 bootstrap_mgr: &mut BootstrapManager,
581 blob_mgr: &mut BlobManager,
582 ) -> Result<BuildOutput> {
583 let mut bootstrap_ctx = bootstrap_mgr.create_ctx()?;
584 let layer_idx = u16::from(bootstrap_ctx.layered);
585 let mut blob_writer: Box<dyn Artifact> = match self.ty {
586 ConversionType::EStargzToRafs
587 | ConversionType::EStargzToRef
588 | ConversionType::TargzToRafs
589 | ConversionType::TargzToRef
590 | ConversionType::TarToRafs
591 | ConversionType::TarToTarfs => {
592 if let Some(blob_stor) = ctx.blob_storage.clone() {
593 Box::new(ArtifactWriter::new(blob_stor)?)
594 } else {
595 Box::<NoopArtifactWriter>::default()
596 }
597 }
598 _ => {
599 return Err(anyhow!(
600 "tarball: unsupported image conversion type '{}'",
601 self.ty
602 ))
603 }
604 };
605
606 let mut tree_builder =
607 TarballTreeBuilder::new(self.ty, ctx, blob_mgr, blob_writer.as_mut(), layer_idx);
608 let tree = timing_tracer!({ tree_builder.build_tree() }, "build_tree")?;
609
610 let mut bootstrap = timing_tracer!(
612 { build_bootstrap(ctx, bootstrap_mgr, &mut bootstrap_ctx, blob_mgr, tree) },
613 "build_bootstrap"
614 )?;
615
616 timing_tracer!(
618 { Blob::dump(ctx, blob_mgr, blob_writer.as_mut()) },
619 "dump_blob"
620 )?;
621
622 if let Some((_, blob_ctx)) = blob_mgr.get_current_blob() {
624 Blob::dump_meta_data(ctx, blob_ctx, blob_writer.as_mut())?;
625 }
626
627 if ctx.blob_inline_meta {
629 timing_tracer!(
630 {
631 dump_bootstrap(
632 ctx,
633 bootstrap_mgr,
634 &mut bootstrap_ctx,
635 &mut bootstrap,
636 blob_mgr,
637 blob_writer.as_mut(),
638 )
639 },
640 "dump_bootstrap"
641 )?;
642 finalize_blob(ctx, blob_mgr, blob_writer.as_mut())?;
643 } else {
644 finalize_blob(ctx, blob_mgr, blob_writer.as_mut())?;
645 timing_tracer!(
646 {
647 dump_bootstrap(
648 ctx,
649 bootstrap_mgr,
650 &mut bootstrap_ctx,
651 &mut bootstrap,
652 blob_mgr,
653 blob_writer.as_mut(),
654 )
655 },
656 "dump_bootstrap"
657 )?;
658 }
659
660 lazy_drop(bootstrap_ctx);
661
662 BuildOutput::new(blob_mgr, None, &bootstrap_mgr.bootstrap_storage, &None)
663 }
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669 use crate::attributes::Attributes;
670 use crate::{ArtifactStorage, Features, Prefetch, WhiteoutSpec};
671 use nydus_utils::{compress, digest};
672
673 #[test]
674 fn test_build_tarfs() {
675 let tmp_dir = vmm_sys_util::tempdir::TempDir::new().unwrap();
676 let tmp_dir = tmp_dir.as_path().to_path_buf();
677 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
678 let source_path = PathBuf::from(root_dir).join("../tests/texture/tar/all-entry-type.tar");
679 let prefetch = Prefetch::default();
680 let mut ctx = BuildContext::new(
681 "test".to_string(),
682 true,
683 0,
684 compress::Algorithm::None,
685 digest::Algorithm::Sha256,
686 true,
687 WhiteoutSpec::Oci,
688 ConversionType::TarToTarfs,
689 source_path,
690 prefetch,
691 Some(ArtifactStorage::FileDir((tmp_dir.clone(), String::new()))),
692 None,
693 false,
694 Features::new(),
695 false,
696 Attributes::default(),
697 );
698 let mut bootstrap_mgr = BootstrapManager::new(
699 Some(ArtifactStorage::FileDir((tmp_dir, String::new()))),
700 None,
701 );
702 let mut blob_mgr = BlobManager::new(digest::Algorithm::Sha256, false);
703 let mut builder = TarballBuilder::new(ConversionType::TarToTarfs);
704 builder
705 .build(&mut ctx, &mut bootstrap_mgr, &mut blob_mgr)
706 .unwrap();
707 }
708
709 #[test]
710 fn test_build_encrypted_tarfs() {
711 let tmp_dir = vmm_sys_util::tempdir::TempDir::new().unwrap();
712 let tmp_dir = tmp_dir.as_path().to_path_buf();
713 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
714 let source_path = PathBuf::from(root_dir).join("../tests/texture/tar/all-entry-type.tar");
715 let prefetch = Prefetch::default();
716 let mut ctx = BuildContext::new(
717 "test".to_string(),
718 true,
719 0,
720 compress::Algorithm::None,
721 digest::Algorithm::Sha256,
722 true,
723 WhiteoutSpec::Oci,
724 ConversionType::TarToTarfs,
725 source_path,
726 prefetch,
727 Some(ArtifactStorage::FileDir((tmp_dir.clone(), String::new()))),
728 None,
729 false,
730 Features::new(),
731 true,
732 Attributes::default(),
733 );
734 let mut bootstrap_mgr = BootstrapManager::new(
735 Some(ArtifactStorage::FileDir((tmp_dir, String::new()))),
736 None,
737 );
738 let mut blob_mgr = BlobManager::new(digest::Algorithm::Sha256, false);
739 let mut builder = TarballBuilder::new(ConversionType::TarToTarfs);
740 builder
741 .build(&mut ctx, &mut bootstrap_mgr, &mut blob_mgr)
742 .unwrap();
743 }
744}