Skip to main content

nydus_rafs/metadata/
mod.rs

1// Copyright 2020 Ant Group. All rights reserved.
2// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved.
3//
4// SPDX-License-Identifier: Apache-2.0
5
6//! Enums, Structs and Traits to access and manage Rafs filesystem metadata.
7
8use std::any::Any;
9use std::collections::{HashMap, HashSet};
10use std::convert::{TryFrom, TryInto};
11use std::ffi::{OsStr, OsString};
12use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
13use std::fs::OpenOptions;
14use std::io::{Error, Result};
15use std::os::unix::ffi::OsStrExt;
16use std::path::{Component, Path, PathBuf};
17use std::str::FromStr;
18use std::sync::Arc;
19use std::time::Duration;
20use thiserror::Error;
21
22use anyhow::{bail, ensure};
23use fuse_backend_rs::abi::fuse_abi::Attr;
24use fuse_backend_rs::api::filesystem::Entry;
25use nydus_api::{ConfigV2, RafsConfigV2};
26use nydus_storage::device::{
27    BlobChunkInfo, BlobDevice, BlobFeatures, BlobInfo, BlobIoMerge, BlobIoVec,
28};
29use nydus_storage::meta::toc::TocEntryList;
30use nydus_utils::digest::{self, RafsDigest};
31use nydus_utils::{compress, crypt};
32use serde::Serialize;
33
34use self::layout::v5::RafsV5PrefetchTable;
35use self::layout::v6::RafsV6PrefetchTable;
36use self::layout::{XattrName, XattrValue, RAFS_SUPER_VERSION_V5, RAFS_SUPER_VERSION_V6};
37use self::noop::NoopSuperBlock;
38use crate::fs::{RAFS_DEFAULT_ATTR_TIMEOUT, RAFS_DEFAULT_ENTRY_TIMEOUT};
39use crate::{RafsError, RafsIoReader, RafsIoWrite, RafsResult};
40
41mod md_v5;
42mod md_v6;
43mod noop;
44
45pub mod cached_v5;
46pub mod chunk;
47pub mod direct_v5;
48pub mod direct_v6;
49pub mod inode;
50pub mod layout;
51
52// Reexport from nydus_storage crate.
53pub use nydus_storage::{RAFS_DEFAULT_CHUNK_SIZE, RAFS_MAX_CHUNK_SIZE};
54
55/// Maximum size of blob identifier string.
56pub const RAFS_BLOB_ID_MAX_LENGTH: usize = 64;
57/// Block size reported by get_attr().
58pub const RAFS_ATTR_BLOCK_SIZE: u32 = 4096;
59/// Maximum size of file name supported by RAFS.
60pub const RAFS_MAX_NAME: usize = 255;
61/// Maximum size of RAFS filesystem metadata blobs.
62pub const RAFS_MAX_METADATA_SIZE: usize = 0x8000_0000;
63/// File name for Unix current directory.
64pub const DOT: &str = ".";
65/// File name for Unix parent directory.
66pub const DOTDOT: &str = "..";
67
68/// Type for RAFS filesystem inode number.
69pub type Inode = u64;
70pub type ArcRafsInodeExt = Arc<dyn RafsInodeExt>;
71
72#[derive(Debug, Clone)]
73pub struct RafsBlobExtraInfo {
74    /// Mapped block address from RAFS v6 devslot table.
75    ///
76    /// It's the offset of the uncompressed blob used to convert an image into a disk.
77    pub mapped_blkaddr: u32,
78}
79
80/// Trait to access filesystem inodes managed by a RAFS filesystem.
81pub trait RafsSuperInodes {
82    /// Get the maximum inode number managed by the RAFS filesystem.
83    fn get_max_ino(&self) -> Inode;
84
85    /// Get the `RafsInode` trait object corresponding to the inode number `ino`.
86    fn get_inode(&self, ino: Inode, validate_inode: bool) -> Result<Arc<dyn RafsInode>>;
87
88    /// Get the `RafsInodeExt` trait object corresponding to the 'ino`.
89    fn get_extended_inode(&self, ino: Inode, validate_inode: bool)
90        -> Result<Arc<dyn RafsInodeExt>>;
91}
92
93/// Trait to access RAFS filesystem metadata, including the RAFS super block and inodes.
94pub trait RafsSuperBlock: RafsSuperInodes + Send + Sync {
95    /// Load and validate the RAFS filesystem super block from the specified reader.
96    fn load(&mut self, r: &mut RafsIoReader) -> Result<()>;
97
98    /// Update/reload the RAFS filesystem super block from the specified reader.
99    fn update(&self, r: &mut RafsIoReader) -> RafsResult<()>;
100
101    /// Destroy the RAFS filesystem super block object.
102    fn destroy(&mut self);
103
104    /// Get all blob objects referenced by the RAFS filesystem.
105    fn get_blob_infos(&self) -> Vec<Arc<BlobInfo>>;
106
107    /// Get extra information associated with blob objects.
108    fn get_blob_extra_infos(&self) -> Result<HashMap<String, RafsBlobExtraInfo>> {
109        Ok(HashMap::new())
110    }
111
112    /// Get the inode number of the RAFS filesystem root.
113    fn root_ino(&self) -> u64;
114
115    /// Get the `BlobChunkInfo` object by a chunk index, used by RAFS v6.
116    fn get_chunk_info(&self, _idx: usize) -> Result<Arc<dyn BlobChunkInfo>>;
117
118    /// Associate `BlobDevice` object with the `RafsSuperBlock` object, used by RAFS v6.
119    fn set_blob_device(&self, blob_device: BlobDevice);
120}
121
122/// Result codes for `RafsInodeWalkHandler`.
123pub enum RafsInodeWalkAction {
124    /// Indicates the need to continue iterating
125    Continue,
126    /// Indicates that it is necessary to stop continuing to iterate
127    Break,
128}
129
130/// Callback handler for RafsInode::walk_children_inodes().
131pub type RafsInodeWalkHandler<'a> = &'a mut dyn FnMut(
132    Option<Arc<dyn RafsInode>>,
133    OsString,
134    u64,
135    u64,
136) -> Result<RafsInodeWalkAction>;
137
138/// Trait to provide readonly accessors for RAFS filesystem inode.
139///
140/// The RAFS filesystem is a readonly filesystem, so does its inodes. The `RafsInode` trait provides
141/// readonly accessors for RAFS filesystem inode. The `nydus-image` crate provides its own
142/// InodeWrapper to generate RAFS filesystem inodes.
143pub trait RafsInode: Any {
144    /// RAFS: validate format and integrity of the RAFS filesystem inode.
145    ///
146    /// Inodes objects may be transmuted from raw buffers or loaded from untrusted source.
147    /// It must be validated for integrity before accessing any of its data fields .
148    fn validate(&self, max_inode: Inode, chunk_size: u64) -> Result<()>;
149
150    /// RAFS: allocate blob io vectors to read file data in range [offset, offset + size).
151    fn alloc_bio_vecs(
152        &self,
153        device: &BlobDevice,
154        offset: u64,
155        size: usize,
156        user_io: bool,
157    ) -> Result<Vec<BlobIoVec>>;
158
159    /// RAFS: collect all descendants of the inode for image building.
160    fn collect_descendants_inodes(
161        &self,
162        descendants: &mut Vec<Arc<dyn RafsInode>>,
163    ) -> Result<usize>;
164
165    /// Posix: generate a `Entry` object required by libc/fuse from the inode.
166    fn get_entry(&self) -> Entry;
167
168    /// Posix: generate a posix `Attr` object required by libc/fuse from the inode.
169    fn get_attr(&self) -> Attr;
170
171    /// Posix: get the inode number.
172    fn ino(&self) -> u64;
173
174    /// Posix: get real device number.
175    fn rdev(&self) -> u32;
176
177    /// Posix: get project id associated with the inode.
178    fn projid(&self) -> u32;
179
180    /// Mode: check whether the inode is a block device.
181    fn is_blkdev(&self) -> bool;
182
183    /// Mode: check whether the inode is a char device.
184    fn is_chrdev(&self) -> bool;
185
186    /// Mode: check whether the inode is a sock.
187    fn is_sock(&self) -> bool;
188
189    /// Mode: check whether the inode is a fifo.
190    fn is_fifo(&self) -> bool;
191
192    /// Mode: check whether the inode is a directory.
193    fn is_dir(&self) -> bool;
194
195    /// Mode: check whether the inode is a symlink.
196    fn is_symlink(&self) -> bool;
197
198    /// Mode: check whether the inode is a regular file.
199    fn is_reg(&self) -> bool;
200
201    /// Mode: check whether the inode is a hardlink.
202    fn is_hardlink(&self) -> bool;
203
204    /// Xattr: check whether the inode has extended attributes.
205    fn has_xattr(&self) -> bool;
206
207    /// Xattr: get the value of xattr with key `name`.
208    fn get_xattr(&self, name: &OsStr) -> Result<Option<XattrValue>>;
209
210    /// Xattr: get all xattr keys.
211    fn get_xattrs(&self) -> Result<Vec<XattrName>>;
212
213    /// Symlink: get the symlink target.
214    fn get_symlink(&self) -> Result<OsString>;
215
216    /// Symlink: get size of the symlink target path.
217    fn get_symlink_size(&self) -> u16;
218
219    /// Directory: walk/enumerate child inodes.
220    fn walk_children_inodes(&self, entry_offset: u64, handler: RafsInodeWalkHandler) -> Result<()>;
221
222    /// Directory: get child inode by name.
223    fn get_child_by_name(&self, name: &OsStr) -> Result<Arc<dyn RafsInodeExt>>;
224
225    /// Directory: get child inode by child index, child index starts from 0.
226    fn get_child_by_index(&self, idx: u32) -> Result<Arc<dyn RafsInodeExt>>;
227
228    /// Directory: get number of child inodes.
229    fn get_child_count(&self) -> u32;
230
231    /// Directory: get the inode number corresponding to the first child inode.
232    fn get_child_index(&self) -> Result<u32>;
233
234    /// Regular: get size of file content
235    fn size(&self) -> u64;
236
237    /// Regular: check whether the inode has no content.
238    fn is_empty_size(&self) -> bool {
239        self.size() == 0
240    }
241
242    /// Regular: get number of data chunks.
243    fn get_chunk_count(&self) -> u32;
244
245    fn as_any(&self) -> &dyn Any;
246}
247
248/// Extended inode information for builder and directory walker.
249pub trait RafsInodeExt: RafsInode {
250    /// Convert to the base type `RafsInode`.
251    fn as_inode(&self) -> &dyn RafsInode;
252
253    /// Posix: get inode number of the parent inode.
254    fn parent(&self) -> u64;
255
256    /// Posix: get file name.
257    fn name(&self) -> OsString;
258
259    /// Posix: get file name size.
260    fn get_name_size(&self) -> u16;
261
262    /// RAFS V5: get RAFS v5 specific inode flags.
263    fn flags(&self) -> u64;
264
265    /// RAFS v5: get digest value of the inode metadata.
266    fn get_digest(&self) -> RafsDigest;
267
268    /// RAFS v5: get chunk info object by chunk index, chunk index starts from 0.
269    fn get_chunk_info(&self, idx: u32) -> Result<Arc<dyn BlobChunkInfo>>;
270}
271
272/// Trait to write out RAFS filesystem meta objects into the metadata blob.
273pub trait RafsStore {
274    /// Write out the Rafs filesystem meta object to the writer.
275    fn store(&self, w: &mut dyn RafsIoWrite) -> Result<usize>;
276}
277
278bitflags! {
279    /// Rafs filesystem feature flags.
280    #[derive(Serialize)]
281    pub struct RafsSuperFlags: u64 {
282        /// Data chunks are not compressed.
283        const COMPRESSION_NONE = 0x0000_0001;
284        /// Data chunks are compressed with lz4_block.
285        const COMPRESSION_LZ4 = 0x0000_0002;
286        /// Use blake3 hash algorithm to calculate digest.
287        const HASH_BLAKE3 = 0x0000_0004;
288        /// Use sha256 hash algorithm to calculate digest.
289        const HASH_SHA256 = 0x0000_0008;
290        /// Inode has explicit uid gid fields.
291        ///
292        /// If unset, use nydusd process euid/egid for all inodes at runtime.
293        const EXPLICIT_UID_GID = 0x0000_0010;
294        /// Inode may have associated extended attributes.
295        const HAS_XATTR = 0x0000_0020;
296        /// Data chunks are compressed with gzip
297        const COMPRESSION_GZIP = 0x0000_0040;
298        /// Data chunks are compressed with zstd
299        const COMPRESSION_ZSTD = 0x0000_0080;
300        /// Chunk digests are inlined in RAFS v6 data blob.
301        const INLINED_CHUNK_DIGEST = 0x0000_0100;
302        /// RAFS works in Tarfs mode, which directly uses tar streams as data blobs.
303        const TARTFS_MODE = 0x0000_0200;
304        /// Data chunks are not encrypted.
305        const ENCRYPTION_NONE = 0x0100_0000;
306        /// Data chunks are encrypted with AES-128-XTS.
307        const ENCRYPTION_ASE_128_XTS = 0x0200_0000;
308
309        // Reserved for future compatible changes.
310        const PRESERVED_COMPAT_5 = 0x0400_0000;
311        const PRESERVED_COMPAT_4 = 0x0800_0000;
312        const PRESERVED_COMPAT_3 = 0x1000_0000;
313        const PRESERVED_COMPAT_2 = 0x2000_0000;
314        const PRESERVED_COMPAT_1 = 0x4000_0000;
315        const PRESERVED_COMPAT_0 = 0x8000_0000;
316    }
317}
318
319impl Default for RafsSuperFlags {
320    fn default() -> Self {
321        RafsSuperFlags::empty()
322    }
323}
324
325impl Display for RafsSuperFlags {
326    fn fmt(&self, f: &mut Formatter) -> FmtResult {
327        write!(f, "{:?}", self)?;
328        Ok(())
329    }
330}
331
332impl From<RafsSuperFlags> for digest::Algorithm {
333    fn from(flags: RafsSuperFlags) -> Self {
334        match flags {
335            x if x.contains(RafsSuperFlags::HASH_BLAKE3) => digest::Algorithm::Blake3,
336            x if x.contains(RafsSuperFlags::HASH_SHA256) => digest::Algorithm::Sha256,
337            _ => digest::Algorithm::Blake3,
338        }
339    }
340}
341
342impl From<digest::Algorithm> for RafsSuperFlags {
343    fn from(d: digest::Algorithm) -> RafsSuperFlags {
344        match d {
345            digest::Algorithm::Blake3 => RafsSuperFlags::HASH_BLAKE3,
346            digest::Algorithm::Sha256 => RafsSuperFlags::HASH_SHA256,
347        }
348    }
349}
350
351impl From<RafsSuperFlags> for compress::Algorithm {
352    fn from(flags: RafsSuperFlags) -> Self {
353        match flags {
354            x if x.contains(RafsSuperFlags::COMPRESSION_NONE) => compress::Algorithm::None,
355            x if x.contains(RafsSuperFlags::COMPRESSION_LZ4) => compress::Algorithm::Lz4Block,
356            x if x.contains(RafsSuperFlags::COMPRESSION_GZIP) => compress::Algorithm::GZip,
357            x if x.contains(RafsSuperFlags::COMPRESSION_ZSTD) => compress::Algorithm::Zstd,
358            _ => compress::Algorithm::Lz4Block,
359        }
360    }
361}
362
363impl From<compress::Algorithm> for RafsSuperFlags {
364    fn from(c: compress::Algorithm) -> RafsSuperFlags {
365        match c {
366            compress::Algorithm::None => RafsSuperFlags::COMPRESSION_NONE,
367            compress::Algorithm::Lz4Block => RafsSuperFlags::COMPRESSION_LZ4,
368            compress::Algorithm::GZip => RafsSuperFlags::COMPRESSION_GZIP,
369            compress::Algorithm::Zstd => RafsSuperFlags::COMPRESSION_ZSTD,
370        }
371    }
372}
373
374impl From<RafsSuperFlags> for crypt::Algorithm {
375    fn from(flags: RafsSuperFlags) -> Self {
376        match flags {
377            // NOTE: only aes-128-xts encryption algorithm supported.
378            x if x.contains(RafsSuperFlags::ENCRYPTION_ASE_128_XTS) => crypt::Algorithm::Aes128Xts,
379            _ => crypt::Algorithm::None,
380        }
381    }
382}
383
384impl From<crypt::Algorithm> for RafsSuperFlags {
385    fn from(c: crypt::Algorithm) -> RafsSuperFlags {
386        match c {
387            // NOTE: only aes-128-xts encryption algorithm supported.
388            crypt::Algorithm::Aes128Xts => RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
389            _ => RafsSuperFlags::ENCRYPTION_NONE,
390        }
391    }
392}
393
394/// Configuration information to check compatibility between RAFS filesystems.
395#[derive(Clone, Copy, Debug)]
396pub struct RafsSuperConfig {
397    /// RAFS filesystem version.
398    pub version: RafsVersion,
399    /// Compression algorithm.
400    pub compressor: compress::Algorithm,
401    /// Digest algorithm.
402    pub digester: digest::Algorithm,
403    /// Size of data chunks.
404    pub chunk_size: u32,
405    /// Size of batch data chunks.
406    pub batch_size: u32,
407    /// Whether `explicit_uidgid` enabled or not.
408    pub explicit_uidgid: bool,
409    /// RAFS in TARFS mode.
410    pub is_tarfs_mode: bool,
411}
412
413#[derive(Error, Debug)]
414pub enum MergeError {
415    #[error("Inconsistent RAFS Filesystem: {0}")]
416    InconsistentFilesystem(String),
417    #[error(transparent)]
418    Other(#[from] anyhow::Error),
419}
420
421impl RafsSuperConfig {
422    /// Check compatibility for two RAFS filesystems.
423    pub fn check_compatibility(&self, meta: &RafsSuperMeta) -> anyhow::Result<()> {
424        ensure!(
425            self.chunk_size == meta.chunk_size,
426            MergeError::InconsistentFilesystem(format!(
427                "Inconsistent configuration of chunk_size: {} vs {}",
428                self.chunk_size, meta.chunk_size
429            ))
430        );
431
432        ensure!(
433            self.explicit_uidgid == meta.explicit_uidgid(),
434            MergeError::InconsistentFilesystem(format!(
435                    "Using inconsistent explicit_uidgid setting {:?}, target explicit_uidgid setting {:?}",
436                    self.explicit_uidgid,
437                    meta.explicit_uidgid()
438                ))
439        );
440
441        let meta_version = RafsVersion::try_from(meta.version);
442        ensure!(
443            u32::from(self.version) == meta.version,
444            MergeError::InconsistentFilesystem(format!(
445                "Using inconsistent RAFS version {:?}, target RAFS version {:?}",
446                self.version, meta_version
447            ))
448        );
449
450        ensure!(
451            self.version != RafsVersion::V5 || self.digester == meta.get_digester(),
452            MergeError::InconsistentFilesystem(format!(
453                "RAFS v5 can not support different digest algorithm due to inode digest, {} vs {}",
454                self.digester,
455                meta.get_digester()
456            ))
457        );
458        let is_tarfs_mode = meta.flags.contains(RafsSuperFlags::TARTFS_MODE);
459        ensure!(
460            is_tarfs_mode == self.is_tarfs_mode,
461            MergeError::InconsistentFilesystem("Using inconsistent RAFS TARFS mode".to_string(),)
462        );
463
464        Ok(())
465    }
466}
467
468/// Rafs filesystem meta-data cached from on disk RAFS super block.
469#[derive(Clone, Copy, Debug, Serialize)]
470pub struct RafsSuperMeta {
471    /// Filesystem magic number.
472    pub magic: u32,
473    /// Filesystem version number.
474    pub version: u32,
475    /// Size of on disk super block.
476    pub sb_size: u32,
477    /// Inode number of root inode.
478    pub root_inode: Inode,
479    /// Chunk size.
480    pub chunk_size: u32,
481    /// Batch chunk size.
482    pub batch_size: u32,
483    /// Number of inodes in the filesystem.
484    pub inodes_count: u64,
485    /// V5: superblock flags for Rafs v5.
486    pub flags: RafsSuperFlags,
487    /// Number of inode entries in inode offset table.
488    pub inode_table_entries: u32,
489    /// Offset of the inode offset table into the metadata blob.
490    pub inode_table_offset: u64,
491    /// Size of blob information table.
492    pub blob_table_size: u32,
493    /// Offset of the blob information table into the metadata blob.
494    pub blob_table_offset: u64,
495    /// Size of extended blob information table.
496    pub extended_blob_table_offset: u64,
497    /// Offset of the extended blob information table into the metadata blob.
498    pub extended_blob_table_entries: u32,
499    /// Number of RAFS v6 blob device entries in the devslot table.
500    pub blob_device_table_count: u32,
501    /// Offset of the RAFS v6 devslot table.
502    pub blob_device_table_offset: u64,
503    /// Offset of the inode prefetch table into the metadata blob.
504    pub prefetch_table_offset: u64,
505    /// Size of the inode prefetch table.
506    pub prefetch_table_entries: u32,
507    /// Default attribute timeout value.
508    pub attr_timeout: Duration,
509    /// Default inode timeout value.
510    pub entry_timeout: Duration,
511    /// Whether the RAFS instance is a chunk dictionary.
512    pub is_chunk_dict: bool,
513    /// Metadata block address for RAFS v6.
514    pub meta_blkaddr: u32,
515    /// Root nid for RAFS v6.
516    pub root_nid: u16,
517    /// Offset of the chunk table for RAFS v6.
518    pub chunk_table_offset: u64,
519    /// Size  of the chunk table for RAFS v6.
520    pub chunk_table_size: u64,
521}
522
523impl RafsSuperMeta {
524    /// Check whether the superblock is for Rafs v5 filesystems.
525    pub fn is_v5(&self) -> bool {
526        self.version == RAFS_SUPER_VERSION_V5
527    }
528
529    /// Check whether the superblock is for Rafs v6 filesystems.
530    pub fn is_v6(&self) -> bool {
531        self.version == RAFS_SUPER_VERSION_V6
532    }
533
534    /// Check whether the RAFS instance is a chunk dictionary.
535    pub fn is_chunk_dict(&self) -> bool {
536        self.is_chunk_dict
537    }
538
539    /// Check whether the explicit UID/GID feature has been enable or not.
540    pub fn explicit_uidgid(&self) -> bool {
541        self.flags.contains(RafsSuperFlags::EXPLICIT_UID_GID)
542    }
543
544    /// Check whether the filesystem supports extended attribute or not.
545    pub fn has_xattr(&self) -> bool {
546        self.flags.contains(RafsSuperFlags::HAS_XATTR)
547    }
548
549    /// Check whether data blobs have inlined chunk digest array.
550    pub fn has_inlined_chunk_digest(&self) -> bool {
551        self.is_v6() && self.flags.contains(RafsSuperFlags::INLINED_CHUNK_DIGEST)
552    }
553
554    /// Get compression algorithm to handle chunk data for the filesystem.
555    pub fn get_compressor(&self) -> compress::Algorithm {
556        if self.is_v5() || self.is_v6() {
557            self.flags.into()
558        } else {
559            compress::Algorithm::None
560        }
561    }
562
563    /// V5: get message digest algorithm to validate chunk data for the filesystem.
564    pub fn get_digester(&self) -> digest::Algorithm {
565        if self.is_v5() || self.is_v6() {
566            self.flags.into()
567        } else {
568            digest::Algorithm::Blake3
569        }
570    }
571
572    /// V6: Check whether any data blobs may be encrypted.
573    pub fn get_cipher(&self) -> crypt::Algorithm {
574        if self.is_v6() {
575            self.flags.into()
576        } else {
577            crypt::Algorithm::None
578        }
579    }
580
581    /// Get `RafsSuperConfig` object to check compatibility.
582    pub fn get_config(&self) -> RafsSuperConfig {
583        RafsSuperConfig {
584            version: self.version.try_into().unwrap_or_default(),
585            compressor: self.get_compressor(),
586            digester: self.get_digester(),
587            chunk_size: self.chunk_size,
588            batch_size: self.batch_size,
589            explicit_uidgid: self.explicit_uidgid(),
590            is_tarfs_mode: self.flags.contains(RafsSuperFlags::TARTFS_MODE),
591        }
592    }
593}
594
595impl Default for RafsSuperMeta {
596    fn default() -> Self {
597        RafsSuperMeta {
598            magic: 0,
599            version: 0,
600            sb_size: 0,
601            inodes_count: 0,
602            root_inode: 0,
603            chunk_size: 0,
604            batch_size: 0,
605            flags: RafsSuperFlags::empty(),
606            inode_table_entries: 0,
607            inode_table_offset: 0,
608            blob_table_size: 0,
609            blob_table_offset: 0,
610            extended_blob_table_offset: 0,
611            extended_blob_table_entries: 0,
612            blob_device_table_count: 0,
613            blob_device_table_offset: 0,
614            prefetch_table_offset: 0,
615            prefetch_table_entries: 0,
616            attr_timeout: Duration::from_secs(RAFS_DEFAULT_ATTR_TIMEOUT),
617            entry_timeout: Duration::from_secs(RAFS_DEFAULT_ENTRY_TIMEOUT),
618            meta_blkaddr: 0,
619            root_nid: 0,
620            is_chunk_dict: false,
621            chunk_table_offset: 0,
622            chunk_table_size: 0,
623        }
624    }
625}
626
627/// RAFS filesystem versions.
628#[derive(Clone, Copy, Debug, Default, PartialEq)]
629pub enum RafsVersion {
630    /// RAFS v5
631    #[default]
632    V5,
633    /// RAFS v6
634    V6,
635}
636
637impl TryFrom<u32> for RafsVersion {
638    type Error = Error;
639
640    fn try_from(version: u32) -> std::result::Result<Self, Self::Error> {
641        if version == RAFS_SUPER_VERSION_V5 {
642            return Ok(RafsVersion::V5);
643        } else if version == RAFS_SUPER_VERSION_V6 {
644            return Ok(RafsVersion::V6);
645        }
646        Err(einval!(format!("invalid RAFS version number {}", version)))
647    }
648}
649
650impl From<RafsVersion> for u32 {
651    fn from(v: RafsVersion) -> Self {
652        match v {
653            RafsVersion::V5 => RAFS_SUPER_VERSION_V5,
654            RafsVersion::V6 => RAFS_SUPER_VERSION_V6,
655        }
656    }
657}
658
659impl std::fmt::Display for RafsVersion {
660    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
661        match self {
662            RafsVersion::V5 => write!(f, "5"),
663            RafsVersion::V6 => write!(f, "6"),
664        }
665    }
666}
667
668impl RafsVersion {
669    /// Check whether it's RAFS v5.
670    pub fn is_v5(&self) -> bool {
671        self == &Self::V5
672    }
673
674    /// Check whether it's RAFS v6.
675    pub fn is_v6(&self) -> bool {
676        self == &Self::V6
677    }
678}
679
680/// Rafs metadata working mode.
681#[derive(Clone, Debug, Default, Eq, PartialEq)]
682pub enum RafsMode {
683    /// Directly mapping and accessing metadata into process by mmap().
684    #[default]
685    Direct,
686    /// Read metadata into memory before using, for RAFS v5.
687    Cached,
688}
689
690impl FromStr for RafsMode {
691    type Err = Error;
692
693    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
694        match s {
695            "direct" => Ok(Self::Direct),
696            "cached" => Ok(Self::Cached),
697            _ => Err(einval!("rafs mode should be direct or cached")),
698        }
699    }
700}
701
702impl Display for RafsMode {
703    fn fmt(&self, f: &mut Formatter) -> FmtResult {
704        match self {
705            Self::Direct => write!(f, "direct"),
706            Self::Cached => write!(f, "cached"),
707        }
708    }
709}
710
711/// Cached Rafs super block and inode information.
712pub struct RafsSuper {
713    /// Rafs metadata working mode.
714    pub mode: RafsMode,
715    /// Whether validate data read from storage backend.
716    pub validate_digest: bool,
717    /// Cached metadata from on disk super block.
718    pub meta: RafsSuperMeta,
719    /// Rafs filesystem super block.
720    pub superblock: Arc<dyn RafsSuperBlock>,
721}
722
723impl Default for RafsSuper {
724    fn default() -> Self {
725        Self {
726            mode: RafsMode::Direct,
727            validate_digest: false,
728            meta: RafsSuperMeta::default(),
729            superblock: Arc::new(NoopSuperBlock::new()),
730        }
731    }
732}
733
734impl RafsSuper {
735    /// Create a new `RafsSuper` instance from a `RafsConfigV2` object.
736    pub fn new(conf: &RafsConfigV2) -> Result<Self> {
737        Ok(Self {
738            mode: RafsMode::from_str(conf.mode.as_str())?,
739            validate_digest: conf.validate,
740            ..Default::default()
741        })
742    }
743
744    /// Destroy the filesystem super block.
745    pub fn destroy(&mut self) {
746        Arc::get_mut(&mut self.superblock)
747            .expect("Inodes are no longer used.")
748            .destroy();
749    }
750
751    /// Load Rafs super block from a metadata file.
752    pub fn load_from_file<P: AsRef<Path>>(
753        path: P,
754        config: Arc<ConfigV2>,
755        is_chunk_dict: bool,
756    ) -> Result<(Self, RafsIoReader)> {
757        let validate_digest = config
758            .rafs
759            .as_ref()
760            .map(|rafs| rafs.validate)
761            .unwrap_or_default();
762        let mut rs = RafsSuper {
763            mode: RafsMode::Direct,
764            validate_digest,
765            ..Default::default()
766        };
767        rs.meta.is_chunk_dict = is_chunk_dict;
768
769        // open bootstrap file
770        let file = OpenOptions::new()
771            .read(true)
772            .write(false)
773            .open(path.as_ref())?;
774        let mut reader = Box::new(file) as RafsIoReader;
775        let mut blob_accessible = config.internal.blob_accessible();
776
777        if let Err(e) = rs.load(&mut reader) {
778            let id = BlobInfo::get_blob_id_from_meta_path(path.as_ref())?;
779            let new_path = match TocEntryList::extract_rafs_meta(&id, config.clone()) {
780                Ok(v) => v,
781                Err(_e) => {
782                    debug!("failed to load inlined RAFS meta, {}", _e);
783                    return Err(e);
784                }
785            };
786            let file = OpenOptions::new().read(true).write(false).open(new_path)?;
787            reader = Box::new(file) as RafsIoReader;
788            rs.load(&mut reader)?;
789            rs.set_blob_id_from_meta_path(path.as_ref())?;
790            blob_accessible = true;
791        } else {
792            // Backward compatibility: try to fix blob id for old converters.
793            // Old converters extracts bootstraps from data blobs with inlined bootstrap
794            // use blob digest as the bootstrap file name. The last blob in the blob table from
795            // the bootstrap has wrong blob id, so we need to fix it.
796            let blobs = rs.superblock.get_blob_infos();
797            for blob in blobs.iter() {
798                // Fix blob id for new images with old converters.
799                if blob.has_feature(BlobFeatures::INLINED_FS_META) {
800                    blob.set_blob_id_from_meta_path(path.as_ref())?;
801                }
802            }
803        }
804
805        if !config.is_fs_cache()
806            && blob_accessible
807            && (validate_digest || config.is_chunk_validation_enabled())
808            && rs.meta.has_inlined_chunk_digest()
809        {
810            rs.create_blob_device(config)?;
811        }
812
813        Ok((rs, reader))
814    }
815
816    /// Load RAFS metadata and optionally cache inodes.
817    pub(crate) fn load(&mut self, r: &mut RafsIoReader) -> Result<()> {
818        // Try to load the filesystem as Rafs v5
819        if self.try_load_v5(r)? {
820            return Ok(());
821        }
822
823        if self.try_load_v6(r)? {
824            return Ok(());
825        }
826
827        Err(Error::other("invalid RAFS superblock"))
828    }
829
830    /// Set meta blob file path from which the `RafsSuper` object is loaded from.
831    ///
832    /// It's used to support inlined-meta and ZRan blobs.
833    pub fn set_blob_id_from_meta_path(&self, meta_path: &Path) -> Result<()> {
834        let blobs = self.superblock.get_blob_infos();
835        for blob in blobs.iter() {
836            if blob.has_feature(BlobFeatures::INLINED_FS_META)
837                || !blob.has_feature(BlobFeatures::CAP_TAR_TOC)
838            {
839                blob.set_blob_id_from_meta_path(meta_path)?;
840            }
841        }
842        Ok(())
843    }
844
845    /// Create a `BlobDevice` object and associated it with the `RafsSuper` object.
846    ///
847    /// The `BlobDevice` object is needed to get meta information from RAFS V6 data blobs.
848    pub fn create_blob_device(&self, config: Arc<ConfigV2>) -> Result<()> {
849        let blobs = self.superblock.get_blob_infos();
850        let device = BlobDevice::new(&config, &blobs)?;
851        self.superblock.set_blob_device(device);
852        Ok(())
853    }
854
855    /// Update the filesystem metadata and storage backend.
856    pub fn update(&self, r: &mut RafsIoReader) -> RafsResult<()> {
857        if self.meta.is_v5() {
858            self.skip_v5_superblock(r)
859                .map_err(RafsError::FillSuperBlock)?;
860        }
861
862        self.superblock.update(r)
863    }
864
865    /// Get the maximum inode number supported by the filesystem instance.
866    pub fn get_max_ino(&self) -> Inode {
867        self.superblock.get_max_ino()
868    }
869
870    /// Get the `RafsInode` object corresponding to `ino`.
871    pub fn get_inode(&self, ino: Inode, validate_inode: bool) -> Result<Arc<dyn RafsInode>> {
872        self.superblock.get_inode(ino, validate_inode)
873    }
874
875    /// Get the `RafsInodeExt` object corresponding to `ino`.
876    pub fn get_extended_inode(
877        &self,
878        ino: Inode,
879        validate_inode: bool,
880    ) -> Result<Arc<dyn RafsInodeExt>> {
881        self.superblock.get_extended_inode(ino, validate_inode)
882    }
883
884    /// Convert a file path to an inode number.
885    pub fn ino_from_path(&self, f: &Path) -> Result<Inode> {
886        let root_ino = self.superblock.root_ino();
887        if f == Path::new("/") {
888            return Ok(root_ino);
889        } else if !f.starts_with("/") {
890            return Err(einval!());
891        }
892
893        let entries = f
894            .components()
895            .filter(|comp| *comp != Component::RootDir)
896            .map(|comp| match comp {
897                Component::Normal(name) => Some(name),
898                Component::ParentDir => Some(OsStr::from_bytes(DOTDOT.as_bytes())),
899                Component::CurDir => Some(OsStr::from_bytes(DOT.as_bytes())),
900                _ => None,
901            })
902            .collect::<Vec<_>>();
903        if entries.is_empty() {
904            warn!("Path can't be parsed {:?}", f);
905            return Err(enoent!());
906        }
907
908        let mut parent = self.get_extended_inode(root_ino, self.validate_digest)?;
909        for p in entries {
910            match p {
911                None => {
912                    error!("Illegal specified path {:?}", f);
913                    return Err(einval!());
914                }
915                Some(name) => {
916                    parent = parent.get_child_by_name(name).map_err(|e| {
917                        warn!("File {:?} not in RAFS filesystem, {}", name, e);
918                        enoent!()
919                    })?;
920                }
921            }
922        }
923
924        Ok(parent.ino())
925    }
926
927    /// Prefetch filesystem and file data to improve performance.
928    ///
929    /// To improve application filesystem access performance, the filesystem may prefetch file or
930    /// metadata in advance. There are ways to configure the file list to be prefetched.
931    /// 1. Static file prefetch list configured during image building, recorded in prefetch list
932    ///    in Rafs v5 file system metadata.
933    ///    Base on prefetch table which is persisted to bootstrap when building image.
934    /// 2. Dynamic file prefetch list configured by command line. The dynamic file prefetch list
935    ///    has higher priority and the static file prefetch list will be ignored if there's dynamic
936    ///    prefetch list. When a directory is specified for dynamic prefetch list, all sub directory
937    ///    and files under the directory will be prefetched.
938    ///
939    /// Each inode passed into should correspond to directory. And it already does the file type
940    /// check inside.
941    pub fn prefetch_files(
942        &self,
943        device: &BlobDevice,
944        r: &mut RafsIoReader,
945        root_ino: Inode,
946        files: Option<Vec<Inode>>,
947        fetcher: &dyn Fn(&mut BlobIoVec, bool),
948    ) -> RafsResult<bool> {
949        // Try to prefetch files according to the list specified by the `--prefetch-files` option.
950        if let Some(files) = files {
951            // Avoid prefetching multiple times for hardlinks to the same file.
952            let mut hardlinks: HashSet<u64> = HashSet::new();
953            let mut state = BlobIoMerge::default();
954            for f_ino in files {
955                self.prefetch_data(device, f_ino, &mut state, &mut hardlinks, fetcher)
956                    .map_err(|e| RafsError::Prefetch(e.to_string()))?;
957            }
958            for (_id, mut desc) in state.drain() {
959                fetcher(&mut desc, true);
960            }
961            // Flush the pending prefetch requests.
962            Ok(false)
963        } else if self.meta.is_v5() {
964            self.prefetch_data_v5(device, r, root_ino, fetcher)
965        } else if self.meta.is_v6() {
966            self.prefetch_data_v6(device, r, root_ino, fetcher)
967        } else {
968            Err(RafsError::Prefetch(
969                "Unknown filesystem version, prefetch disabled".to_string(),
970            ))
971        }
972    }
973
974    #[inline]
975    fn prefetch_inode(
976        device: &BlobDevice,
977        inode: &Arc<dyn RafsInode>,
978        state: &mut BlobIoMerge,
979        hardlinks: &mut HashSet<u64>,
980        fetcher: &dyn Fn(&mut BlobIoVec, bool),
981    ) -> Result<()> {
982        // Check for duplicated hardlinks.
983        if inode.is_hardlink() {
984            if hardlinks.contains(&inode.ino()) {
985                return Ok(());
986            } else {
987                hardlinks.insert(inode.ino());
988            }
989        }
990
991        let descs = inode.alloc_bio_vecs(device, 0, inode.size() as usize, false)?;
992        for desc in descs {
993            state.append(desc);
994            if let Some(desc) = state.get_current_element() {
995                fetcher(desc, false);
996            }
997        }
998
999        Ok(())
1000    }
1001
1002    fn prefetch_data(
1003        &self,
1004        device: &BlobDevice,
1005        ino: u64,
1006        state: &mut BlobIoMerge,
1007        hardlinks: &mut HashSet<u64>,
1008        fetcher: &dyn Fn(&mut BlobIoVec, bool),
1009    ) -> Result<()> {
1010        let inode = self
1011            .superblock
1012            .get_inode(ino, self.validate_digest)
1013            .map_err(|_e| enoent!("Can't find inode"))?;
1014
1015        if inode.is_dir() {
1016            let mut descendants = Vec::new();
1017            let _ = inode.collect_descendants_inodes(&mut descendants)?;
1018            for i in descendants.iter() {
1019                Self::prefetch_inode(device, i, state, hardlinks, fetcher)?;
1020            }
1021        } else if !inode.is_empty_size() && inode.is_reg() {
1022            // An empty regular file will also be packed into nydus image,
1023            // then it has a size of zero.
1024            // Moreover, for rafs v5, symlink has size of zero but non-zero size
1025            // for symlink size. For rafs v6, symlink size is also represented by i_size.
1026            // So we have to restrain the condition here.
1027            Self::prefetch_inode(device, &inode, state, hardlinks, fetcher)?;
1028        }
1029
1030        Ok(())
1031    }
1032}
1033
1034// For nydus-image
1035impl RafsSuper {
1036    /// Convert an inode number to a file path.
1037    pub fn path_from_ino(&self, ino: Inode) -> Result<PathBuf> {
1038        if ino == self.superblock.root_ino() {
1039            return Ok(self.get_extended_inode(ino, false)?.name().into());
1040        }
1041
1042        let mut path = PathBuf::new();
1043        let mut cur_ino = ino;
1044        let mut inode;
1045
1046        loop {
1047            inode = self.get_extended_inode(cur_ino, false)?;
1048            let e: PathBuf = inode.name().into();
1049            path = e.join(path);
1050
1051            if inode.ino() == self.superblock.root_ino() {
1052                break;
1053            } else {
1054                cur_ino = inode.parent();
1055            }
1056        }
1057
1058        Ok(path)
1059    }
1060
1061    /// Get prefetched inos
1062    pub fn get_prefetched_inos(&self, bootstrap: &mut RafsIoReader) -> Result<Vec<u32>> {
1063        if self.meta.is_v5() {
1064            let mut pt = RafsV5PrefetchTable::new();
1065            pt.load_prefetch_table_from(
1066                bootstrap,
1067                self.meta.prefetch_table_offset,
1068                self.meta.prefetch_table_entries as usize,
1069            )?;
1070            Ok(pt.inodes)
1071        } else {
1072            let mut pt = RafsV6PrefetchTable::new();
1073            pt.load_prefetch_table_from(
1074                bootstrap,
1075                self.meta.prefetch_table_offset,
1076                self.meta.prefetch_table_entries as usize,
1077            )?;
1078            Ok(pt.inodes)
1079        }
1080    }
1081
1082    /// Walk through the file tree rooted at ino, calling cb for each file or directory
1083    /// in the tree by DFS order, including ino, please ensure ino is a directory.
1084    pub fn walk_directory<P: AsRef<Path>>(
1085        &self,
1086        ino: Inode,
1087        parent: Option<P>,
1088        cb: &mut dyn FnMut(ArcRafsInodeExt, &Path) -> anyhow::Result<()>,
1089    ) -> anyhow::Result<()> {
1090        let inode = self.get_extended_inode(ino, false)?;
1091        if !inode.is_dir() {
1092            bail!("inode {} is not a directory", ino);
1093        }
1094        self.do_walk_directory(inode, parent, cb)
1095    }
1096
1097    #[allow(clippy::only_used_in_recursion)]
1098    fn do_walk_directory<P: AsRef<Path>>(
1099        &self,
1100        inode: Arc<dyn RafsInodeExt>,
1101        parent: Option<P>,
1102        cb: &mut dyn FnMut(ArcRafsInodeExt, &Path) -> anyhow::Result<()>,
1103    ) -> anyhow::Result<()> {
1104        let path = if let Some(parent) = parent {
1105            parent.as_ref().join(inode.name())
1106        } else {
1107            PathBuf::from("/")
1108        };
1109        cb(inode.clone(), &path)?;
1110        if inode.is_dir() {
1111            for idx in 0..inode.get_child_count() {
1112                let child = inode.get_child_by_index(idx)?;
1113                self.do_walk_directory(child, Some(&path), cb)?;
1114            }
1115        }
1116        Ok(())
1117    }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122    use super::*;
1123
1124    #[test]
1125    fn test_rafs_mode() {
1126        assert!(RafsMode::from_str("").is_err());
1127        assert!(RafsMode::from_str("directed").is_err());
1128        assert!(RafsMode::from_str("Direct").is_err());
1129        assert!(RafsMode::from_str("Cached").is_err());
1130        assert_eq!(RafsMode::from_str("direct").unwrap(), RafsMode::Direct);
1131        assert_eq!(RafsMode::from_str("cached").unwrap(), RafsMode::Cached);
1132        assert_eq!(&format!("{}", RafsMode::Direct), "direct");
1133        assert_eq!(&format!("{}", RafsMode::Cached), "cached");
1134    }
1135
1136    #[test]
1137    fn test_rafs_compressor() {
1138        assert_eq!(
1139            compress::Algorithm::from(RafsSuperFlags::COMPRESSION_NONE),
1140            compress::Algorithm::None
1141        );
1142        assert_eq!(
1143            compress::Algorithm::from(RafsSuperFlags::COMPRESSION_GZIP),
1144            compress::Algorithm::GZip
1145        );
1146        assert_eq!(
1147            compress::Algorithm::from(RafsSuperFlags::COMPRESSION_LZ4),
1148            compress::Algorithm::Lz4Block
1149        );
1150        assert_eq!(
1151            compress::Algorithm::from(RafsSuperFlags::COMPRESSION_ZSTD),
1152            compress::Algorithm::Zstd
1153        );
1154        assert_eq!(
1155            compress::Algorithm::from(
1156                RafsSuperFlags::COMPRESSION_ZSTD | RafsSuperFlags::COMPRESSION_LZ4,
1157            ),
1158            compress::Algorithm::Lz4Block
1159        );
1160        assert_eq!(
1161            compress::Algorithm::from(RafsSuperFlags::empty()),
1162            compress::Algorithm::Lz4Block
1163        );
1164    }
1165
1166    #[test]
1167    fn test_rafs_digestor() {
1168        assert_eq!(
1169            digest::Algorithm::from(RafsSuperFlags::HASH_BLAKE3),
1170            digest::Algorithm::Blake3
1171        );
1172        assert_eq!(
1173            digest::Algorithm::from(RafsSuperFlags::HASH_SHA256),
1174            digest::Algorithm::Sha256
1175        );
1176        assert_eq!(
1177            digest::Algorithm::from(RafsSuperFlags::HASH_SHA256 | RafsSuperFlags::HASH_BLAKE3,),
1178            digest::Algorithm::Blake3
1179        );
1180        assert_eq!(
1181            digest::Algorithm::from(RafsSuperFlags::empty()),
1182            digest::Algorithm::Blake3
1183        );
1184    }
1185
1186    #[test]
1187    fn test_rafs_crypt_from() {
1188        assert_eq!(
1189            crypt::Algorithm::from(RafsSuperFlags::ENCRYPTION_ASE_128_XTS),
1190            crypt::Algorithm::Aes128Xts
1191        );
1192        assert_eq!(
1193            crypt::Algorithm::from(RafsSuperFlags::empty()),
1194            crypt::Algorithm::None
1195        );
1196    }
1197
1198    #[test]
1199    fn test_rafs_super_meta() {
1200        let mut meta = RafsSuperMeta::default();
1201        assert!(!meta.has_xattr());
1202        assert!(!meta.has_inlined_chunk_digest());
1203        assert_eq!(meta.get_compressor(), compress::Algorithm::None);
1204        assert_eq!(meta.get_digester(), digest::Algorithm::Blake3);
1205        assert_eq!(meta.get_cipher(), crypt::Algorithm::None);
1206
1207        meta.version = RAFS_SUPER_VERSION_V6;
1208        meta.flags |= RafsSuperFlags::INLINED_CHUNK_DIGEST;
1209        meta.flags |= RafsSuperFlags::HASH_SHA256;
1210        meta.flags |= RafsSuperFlags::COMPRESSION_GZIP;
1211        meta.flags |= RafsSuperFlags::ENCRYPTION_ASE_128_XTS;
1212
1213        assert!(meta.has_inlined_chunk_digest());
1214        assert_eq!(meta.get_compressor(), compress::Algorithm::GZip);
1215        assert_eq!(meta.get_digester(), digest::Algorithm::Sha256);
1216        assert_eq!(meta.get_cipher(), crypt::Algorithm::Aes128Xts);
1217
1218        meta.version = RAFS_SUPER_VERSION_V5;
1219        assert_eq!(meta.get_compressor(), compress::Algorithm::GZip);
1220        assert_eq!(meta.get_digester(), digest::Algorithm::Sha256);
1221        assert_eq!(meta.get_cipher(), crypt::Algorithm::None);
1222
1223        let cfg = meta.get_config();
1224        assert!(cfg.check_compatibility(&meta).is_ok());
1225    }
1226
1227    #[test]
1228    fn test_rafs_super_new() {
1229        let cfg = RafsConfigV2 {
1230            mode: "direct".into(),
1231            ..RafsConfigV2::default()
1232        };
1233        let mut rs = RafsSuper::new(&cfg).unwrap();
1234        rs.destroy();
1235    }
1236
1237    fn get_meta(
1238        chunk_size: u32,
1239        explice_uidgid: bool,
1240        tartfs_mode: bool,
1241        hash: RafsSuperFlags,
1242        comp: RafsSuperFlags,
1243        crypt: RafsSuperFlags,
1244        version: u32,
1245    ) -> RafsSuperMeta {
1246        let mut meta = RafsSuperMeta {
1247            chunk_size,
1248            ..Default::default()
1249        };
1250        if explice_uidgid {
1251            meta.flags |= RafsSuperFlags::EXPLICIT_UID_GID;
1252        }
1253        if tartfs_mode {
1254            meta.flags |= RafsSuperFlags::TARTFS_MODE;
1255        }
1256        meta.flags |= hash;
1257        meta.flags |= comp;
1258        meta.flags |= crypt;
1259        meta.version = version;
1260        meta
1261    }
1262
1263    #[test]
1264    fn test_rafs_super_config_check_compatibility_fail() {
1265        let meta1 = get_meta(
1266            1024 as u32,
1267            true,
1268            true,
1269            RafsSuperFlags::HASH_BLAKE3,
1270            RafsSuperFlags::COMPRESSION_GZIP,
1271            RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
1272            RAFS_SUPER_VERSION_V5,
1273        );
1274        let meta2 = get_meta(
1275            2048 as u32,
1276            true,
1277            true,
1278            RafsSuperFlags::HASH_BLAKE3,
1279            RafsSuperFlags::COMPRESSION_GZIP,
1280            RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
1281            RAFS_SUPER_VERSION_V5,
1282        );
1283        let meta3 = get_meta(
1284            1024 as u32,
1285            false,
1286            true,
1287            RafsSuperFlags::HASH_BLAKE3,
1288            RafsSuperFlags::COMPRESSION_GZIP,
1289            RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
1290            RAFS_SUPER_VERSION_V5,
1291        );
1292        let meta4 = get_meta(
1293            1024 as u32,
1294            true,
1295            false,
1296            RafsSuperFlags::HASH_BLAKE3,
1297            RafsSuperFlags::COMPRESSION_GZIP,
1298            RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
1299            RAFS_SUPER_VERSION_V5,
1300        );
1301        let meta5 = get_meta(
1302            1024 as u32,
1303            true,
1304            true,
1305            RafsSuperFlags::HASH_SHA256,
1306            RafsSuperFlags::COMPRESSION_GZIP,
1307            RafsSuperFlags::ENCRYPTION_ASE_128_XTS,
1308            RAFS_SUPER_VERSION_V5,
1309        );
1310        let meta6 = get_meta(
1311            1024 as u32,
1312            true,
1313            true,
1314            RafsSuperFlags::HASH_BLAKE3,
1315            RafsSuperFlags::COMPRESSION_GZIP,
1316            RafsSuperFlags::ENCRYPTION_NONE,
1317            RAFS_SUPER_VERSION_V6,
1318        );
1319
1320        assert!(meta1.get_config().check_compatibility(&meta2).is_err());
1321        assert!(meta1.get_config().check_compatibility(&meta3).is_err());
1322        assert!(meta1.get_config().check_compatibility(&meta4).is_err());
1323        assert!(meta1.get_config().check_compatibility(&meta5).is_err());
1324        assert!(meta1.get_config().check_compatibility(&meta6).is_err());
1325    }
1326}