nydus_builder/
tarball.rs

1// Copyright 2022 Alibaba Cloud. All rights reserved.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Generate RAFS filesystem from a tarball.
6//!
7//! It support generating RAFS filesystem from a tar/targz/stargz file with or without data blob.
8//!
9//! The tarball data is arrange as a sequence of tar headers with associated file data interleaved.
10//! - (tar header) (tar header) (file data) (tar header) (file data) (tar header)
11//!   And to support read tarball data from FIFO, we could only go over the tarball stream once.
12//!   So the workflow is as:
13//! - for each tar header from the stream
14//!   -- generate RAFS filesystem node from the tar header
15//!   -- optionally dump file data associated with the tar header into RAFS data blob
16//! - arrange all generated RAFS nodes into a RAFS filesystem tree
17//! - dump the RAFS filesystem tree into RAFS metadata blob
18use 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    /// Create a new instance of `TarballBuilder`.
111    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                    // Disable seek when need to calculate hash value.
186                    is_file = false;
187                }
188                // only enable seek when hash computing is disabled.
189                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        // Prepare scratch buffer for dumping file data.
206        if self.buf.len() < self.ctx.chunk_size as usize {
207            self.buf = vec![0u8; self.ctx.chunk_size as usize];
208        }
209
210        // Generate the root node in advance, it may be overwritten by entries from the tar stream.
211        let root = self.builder.create_directory(&[OsString::from("/")])?;
212        let mut tree = Tree::new(root);
213
214        // Generate RAFS node for each tar entry, and optionally adding missing parents.
215        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        // Update directory size for RAFS V5 after generating the tree.
235        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        // Parse special files
283        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        // Parse symlink
301        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        // Handle hardlink ino
329        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        // Parse xattrs
369        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        // Special handling of hardlink.
452        // Tar hardlink header has zero file size and no file data associated, so copy value from
453        // the associated regular file.
454        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        // Update inode.i_blocks for RAFS v5.
474        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        // Use 64K buffer to keep consistence with zlib-random.
549        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
562/// Builder to create RAFS filesystems from tarballs.
563pub struct TarballBuilder {
564    ty: ConversionType,
565}
566
567impl TarballBuilder {
568    /// Create a new instance of [TarballBuilder] to build a RAFS filesystem from a tarball.
569    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        // Build bootstrap
611        let mut bootstrap = timing_tracer!(
612            { build_bootstrap(ctx, bootstrap_mgr, &mut bootstrap_ctx, blob_mgr, tree) },
613            "build_bootstrap"
614        )?;
615
616        // Dump blob file
617        timing_tracer!(
618            { Blob::dump(ctx, blob_mgr, blob_writer.as_mut()) },
619            "dump_blob"
620        )?;
621
622        // Dump blob meta information
623        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        // Dump RAFS meta/bootstrap and finalize the data blob.
628        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}