From 9ba2a787f9dfa37850e697844a16448d5ae00726 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sat, 15 Oct 2022 17:16:51 +0800 Subject: [PATCH 1/7] storage: use filemap for blob meta Use FileMapState for blob meta to reduce duplicated code. Signed-off-by: Jiang Liu --- storage/src/meta/mod.rs | 57 ++++++++++------------------------------- utils/src/filemap.rs | 42 +++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index dfd3bc179a6..15024b43301 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -19,11 +19,11 @@ use std::fs::OpenOptions; use std::io::Result; use std::mem::{size_of, ManuallyDrop}; use std::ops::{Add, BitAnd, Not}; -use std::os::unix::io::AsRawFd; use std::sync::Arc; use nydus_utils::compress; use nydus_utils::digest::RafsDigest; +use nydus_utils::filemap::FileMapState; use crate::backend::BlobReader; use crate::device::{BlobChunkInfo, BlobInfo}; @@ -349,25 +349,9 @@ impl BlobMetaInfo { file.set_len(expected_size as u64)?; } - let fd = file.as_raw_fd(); - let base = unsafe { - libc::mmap( - std::ptr::null_mut(), - expected_size as usize, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_SHARED, - fd, - 0, - ) - }; - if base == libc::MAP_FAILED { - return Err(last_error!("failed to mmap blob chunk_map")); - } else if base.is_null() { - return Err(ebadf!("failed to mmap blob chunk_map")); - } - - let header = unsafe { (base as *mut u8).add(aligned_info_size as usize) }; - let header = unsafe { &mut *(header as *mut BlobMetaHeaderOndisk) }; + let mut filemap = FileMapState::new(file, 0, expected_size, enable_write)?; + let base = filemap.validate_range(0, expected_size)?; + let header = filemap.get_mut::(aligned_info_size as usize)?; if u32::from_le(header.s_magic) != BLOB_METADATA_MAGIC || u32::from_le(header.s_magic2) != BLOB_METADATA_MAGIC || u32::from_le(header.s_features) != blob_info.meta_flags() @@ -390,15 +374,17 @@ impl BlobMetaInfo { header.s_ci_offset = u64::to_le(blob_info.meta_ci_offset()); header.s_ci_compressed_size = u64::to_le(blob_info.meta_ci_compressed_size()); header.s_ci_uncompressed_size = u64::to_le(blob_info.meta_ci_uncompressed_size()); + filemap.sync_data()?; - file.sync_data()?; + let header = filemap.get_mut::(aligned_info_size as usize)?; header.s_magic = u32::to_le(BLOB_METADATA_MAGIC); header.s_magic2 = u32::to_le(BLOB_METADATA_MAGIC); + filemap.sync_data()?; } let chunk_infos = unsafe { ManuallyDrop::new(Vec::from_raw_parts( - base as *mut BlobChunkInfoOndisk, + base as *mut u8 as *mut BlobChunkInfoOndisk, chunk_count as usize, chunk_count as usize, )) @@ -410,8 +396,7 @@ impl BlobMetaInfo { uncompressed_size: round_up_4k(blob_info.uncompressed_size()), chunk_count, chunks: chunk_infos, - base: base as *const u8, - unmap_len: expected_size, + _filemap: filemap, is_stargz: blob_info.is_stargz(), }); @@ -712,26 +697,11 @@ pub struct BlobMetaState { uncompressed_size: u64, chunk_count: u32, chunks: ManuallyDrop>, - base: *const u8, - unmap_len: usize, + _filemap: FileMapState, /// The blob meta is for an stargz image. is_stargz: bool, } -// // Safe to Send/Sync because the underlying data structures are readonly -unsafe impl Send for BlobMetaState {} -unsafe impl Sync for BlobMetaState {} - -impl Drop for BlobMetaState { - fn drop(&mut self) { - if !self.base.is_null() { - let size = self.unmap_len; - unsafe { libc::munmap(self.base as *mut u8 as *mut libc::c_void, size) }; - self.base = std::ptr::null(); - } - } -} - impl BlobMetaState { fn get_chunk_index_nocheck(&self, addr: u64, compressed: bool) -> Result { let chunks = &self.chunks; @@ -848,6 +818,7 @@ mod tests { use nydus_utils::metrics::BackendMetrics; use std::fs::{File, OpenOptions}; use std::io::Write; + use std::os::unix::io::AsRawFd; use vmm_sys_util::tempfile::TempFile; #[test] @@ -867,8 +838,7 @@ mod tests { comp_info: 0x00ff_f000_0010_0000, }, ]), - base: std::ptr::null(), - unmap_len: 0, + _filemap: FileMapState::default(), is_stargz: false, }; @@ -952,8 +922,7 @@ mod tests { comp_info: 0x00ff_f000_0000_5000, }, ]), - base: std::ptr::null(), - unmap_len: 0, + _filemap: FileMapState::default(), is_stargz: false, }; let info = BlobMetaInfo { diff --git a/utils/src/filemap.rs b/utils/src/filemap.rs index 753ae27cadf..0e4832ddf4e 100644 --- a/utils/src/filemap.rs +++ b/utils/src/filemap.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::Result; use std::mem::size_of; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; /// Struct to manage memory range mapped from file objects. /// @@ -18,6 +18,10 @@ pub struct FileMapState { fd: RawFd, } +// Safe to Send/Sync because the underlying data structures are readonly +unsafe impl Send for FileMapState {} +unsafe impl Sync for FileMapState {} + impl Default for FileMapState { fn default() -> Self { FileMapState { @@ -48,12 +52,17 @@ impl FileMapState { /// Memory map a region of the file object into current process. /// /// It takes ownership of the file object and will close it when the returned object is dropped. - pub fn new(file: File, offset: libc::off_t, size: usize) -> Result { + pub fn new(file: File, offset: libc::off_t, size: usize, writable: bool) -> Result { + let prot = if writable { + libc::PROT_READ | libc::PROT_WRITE + } else { + libc::PROT_READ + }; let base = unsafe { libc::mmap( std::ptr::null_mut(), size, - libc::PROT_READ, + prot, libc::MAP_NORESERVE | libc::MAP_SHARED, file.as_raw_fd(), offset, @@ -96,6 +105,23 @@ impl FileMapState { Ok(unsafe { &*(start as *const T) }) } + /// Cast a subregion of the mapped area to an mutable object reference. + pub fn get_mut(&mut self, offset: usize) -> Result<&mut T> { + let start = self.base.wrapping_add(offset); + let end = start.wrapping_add(size_of::()); + + if start > end + || start < self.base + || end < self.base + || end > self.end + || start as usize & (std::mem::align_of::() - 1) != 0 + { + return Err(einval!("invalid mmap offset")); + } + + Ok(unsafe { &mut *(start as *const T as *mut T) }) + } + /// Check whether the range [offset, offset + size) is valid and return the start address. pub fn validate_range(&self, offset: usize, size: usize) -> Result<*const u8> { let start = self.base.wrapping_add(offset); @@ -115,6 +141,14 @@ impl FileMapState { pub unsafe fn offset(&self, offset: usize) -> *const u8 { self.base.wrapping_add(offset) } + + /// Sync mapped file data into disk. + pub fn sync_data(&self) -> Result<()> { + let file = unsafe { File::from_raw_fd(self.fd) }; + let result = file.sync_data(); + std::mem::forget(file); + result + } } #[cfg(test)] @@ -132,7 +166,7 @@ mod tests { .write(false) .open(&path) .unwrap(); - let map = FileMapState::new(file, 0, 4096).unwrap(); + let map = FileMapState::new(file, 0, 4096, false).unwrap(); let magic = map.get_ref::(0).unwrap(); assert_eq!(u32::from_le(*magic), 0x52414653); From df3115ae1ef1a5143e2a2adb8ddf2646b17133ad Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 16 Oct 2022 14:25:58 +0800 Subject: [PATCH 2/7] storage: rename BlobChunkInfoOndisk to BlobChunkInfoV1Ondisk Rename BlobChunkInfoOndisk to BlobChunkInfoV1Ondisk. Signed-off-by: Jiang Liu --- rafs/src/metadata/layout/v6.rs | 4 +- src/bin/nydus-image/core/blob.rs | 6 +- src/bin/nydus-image/core/context.rs | 6 +- storage/src/meta/chunk_info_v1.rs | 126 ++++++++++++++++++ storage/src/meta/mod.rs | 199 ++++++---------------------- 5 files changed, 178 insertions(+), 163 deletions(-) create mode 100644 storage/src/meta/chunk_info_v1.rs diff --git a/rafs/src/metadata/layout/v6.rs b/rafs/src/metadata/layout/v6.rs index c8e4fe236e0..66cb1d01cac 100644 --- a/rafs/src/metadata/layout/v6.rs +++ b/rafs/src/metadata/layout/v6.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use lazy_static::lazy_static; use nydus_storage::device::{BlobFeatures, BlobInfo}; -use nydus_storage::meta::{BlobMetaHeaderOndisk, BLOB_FEATURE_4K_ALIGNED}; +use nydus_storage::meta::{BlobMetaHeaderOndisk, BLOB_META_FEATURE_4K_ALIGNED}; use nydus_storage::RAFS_MAX_CHUNK_SIZE; use nydus_utils::{compress, digest, round_up, ByteSize}; @@ -1381,7 +1381,7 @@ impl RafsV6Blob { } // for now the uncompressed data chunk of v6 image is 4k aligned. - if u32::from_le(self.meta_features) & BLOB_FEATURE_4K_ALIGNED == 0 { + if u32::from_le(self.meta_features) & BLOB_META_FEATURE_4K_ALIGNED == 0 { error!("RafsV6Blob: idx {} invalid meta_features", blob_index); return false; } diff --git a/src/bin/nydus-image/core/blob.rs b/src/bin/nydus-image/core/blob.rs index e2315aec069..9f7da8b95b4 100644 --- a/src/bin/nydus-image/core/blob.rs +++ b/src/bin/nydus-image/core/blob.rs @@ -6,7 +6,7 @@ use std::io::Write; use anyhow::{Context, Result}; use nydus_rafs::metadata::RAFS_MAX_CHUNK_SIZE; -use nydus_storage::meta::{BlobChunkInfoOndisk, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaHeaderOndisk}; use nydus_utils::{compress, try_round_up_4k}; use sha2::Digest; @@ -75,13 +75,13 @@ impl Blob { fn dump_meta_data_raw( pos: u64, - blob_meta_info: &[BlobChunkInfoOndisk], + blob_meta_info: &[BlobChunkInfoV1Ondisk], compressor: compress::Algorithm, ) -> Result<(std::borrow::Cow<[u8]>, BlobMetaHeaderOndisk)> { let data = unsafe { std::slice::from_raw_parts( blob_meta_info.as_ptr() as *const u8, - blob_meta_info.len() * std::mem::size_of::(), + blob_meta_info.len() * std::mem::size_of::(), ) }; let (buf, compressed) = compress::compress(data, compressor) diff --git a/src/bin/nydus-image/core/context.rs b/src/bin/nydus-image/core/context.rs index 9cddd59b829..9f90a778d01 100644 --- a/src/bin/nydus-image/core/context.rs +++ b/src/bin/nydus-image/core/context.rs @@ -28,7 +28,7 @@ use nydus_rafs::metadata::{Inode, RAFS_DEFAULT_CHUNK_SIZE}; use nydus_rafs::metadata::{RafsSuperFlags, RafsVersion}; use nydus_rafs::{RafsIoReader, RafsIoWrite}; use nydus_storage::device::{BlobFeatures, BlobInfo}; -use nydus_storage::meta::{BlobChunkInfoOndisk, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaHeaderOndisk}; use nydus_utils::{compress, digest, div_round_up, round_down_4k}; use super::chunk_dict::{ChunkDict, HashChunkDict}; @@ -330,7 +330,7 @@ pub struct BlobContext { pub blob_meta_info_enabled: bool, /// Data chunks stored in the data blob, for v6. /// TODO: zran - pub blob_meta_info: Vec, + pub blob_meta_info: Vec, /// Blob metadata header stored in the data blob, for v6 pub blob_meta_header: BlobMetaHeaderOndisk, @@ -453,7 +453,7 @@ impl BlobContext { } debug_assert!(chunk.index() as usize == self.blob_meta_info.len()); - let mut meta = BlobChunkInfoOndisk::default(); + let mut meta = BlobChunkInfoV1Ondisk::default(); meta.set_compressed_offset(chunk.compressed_offset()); meta.set_compressed_size(chunk.compressed_size()); meta.set_uncompressed_offset(chunk.uncompressed_offset()); diff --git a/storage/src/meta/chunk_info_v1.rs b/storage/src/meta/chunk_info_v1.rs new file mode 100644 index 00000000000..7aa1999e3e0 --- /dev/null +++ b/storage/src/meta/chunk_info_v1.rs @@ -0,0 +1,126 @@ +// Copyright (C) 2021-2022 Alibaba Cloud. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::meta::{round_up_4k, BLOB_METADATA_CHUNK_SIZE_MASK}; + +const BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK: u64 = 0xff_ffff_ffff; +const BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK: u64 = 0xfff_ffff_f000; +const BLOB_METADATA_V1_CHUNK_SIZE_LOW_MASK: u64 = 0x0f_ffff; +const BLOB_METADATA_V1_CHUNK_SIZE_HIGH_MASK: u64 = 0xf0_0000; +const BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT: u64 = 44; +const BLOB_METADATA_V1_CHUNK_SIZE_HIGH_COMP_SHIFT: u64 = 20; +const BLOB_METADATA_V1_CHUNK_SIZE_HIGH_UNCOMP_SHIFT: u64 = 12; + +/// Blob chunk compression information on disk format. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct BlobChunkInfoV1Ondisk { + // 20bits: size (low), 32bits: offset, 4bits: size (high), 8bits reserved + pub(crate) uncomp_info: u64, + // 20bits: size (low), 4bits: size (high), offset: 40bits + pub(crate) comp_info: u64, +} + +impl BlobChunkInfoV1Ondisk { + /// Get compressed offset of the chunk. + #[inline] + pub fn compressed_offset(&self) -> u64 { + self.comp_info & BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK + } + + /// Set compressed offset of the chunk. + #[inline] + pub fn set_compressed_offset(&mut self, offset: u64) { + assert!(offset & !BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK == 0); + self.comp_info &= !BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK; + self.comp_info |= offset & BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK; + } + + /// Get compressed size of the chunk. + #[inline] + pub fn compressed_size(&self) -> u32 { + let bit20 = self.comp_info >> BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; + let bit4 = (self.comp_info & 0xf0000000000) >> BLOB_METADATA_V1_CHUNK_SIZE_HIGH_COMP_SHIFT; + (bit4 | bit20) as u32 + 1 + } + + /// Set compressed size of the chunk. + #[inline] + pub fn set_compressed_size(&mut self, size: u32) { + let size = size as u64; + assert!(size > 0 && size <= BLOB_METADATA_CHUNK_SIZE_MASK + 1); + + let size_low = ((size - 1) & BLOB_METADATA_V1_CHUNK_SIZE_LOW_MASK) + << BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; + let size_high = ((size - 1) & BLOB_METADATA_V1_CHUNK_SIZE_HIGH_MASK) + << BLOB_METADATA_V1_CHUNK_SIZE_HIGH_COMP_SHIFT; + let offset = self.comp_info & BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK; + + self.comp_info = size_low | size_high | offset; + } + + /// Get compressed end of the chunk. + #[inline] + pub fn compressed_end(&self) -> u64 { + self.compressed_offset() + self.compressed_size() as u64 + } + + /// Get uncompressed offset of the chunk. + #[inline] + pub fn uncompressed_offset(&self) -> u64 { + self.uncomp_info & BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK + } + + /// Set uncompressed offset of the chunk. + #[inline] + pub fn set_uncompressed_offset(&mut self, offset: u64) { + assert!(offset & !BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK == 0); + self.uncomp_info &= !BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK; + self.uncomp_info |= offset & BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK; + } + + /// Get uncompressed end of the chunk. + #[inline] + pub fn uncompressed_size(&self) -> u32 { + let size_high = (self.uncomp_info & 0xf00) << BLOB_METADATA_V1_CHUNK_SIZE_HIGH_UNCOMP_SHIFT; + let size_low = self.uncomp_info >> BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; + (size_high | size_low) as u32 + 1 + } + + /// Set uncompressed end of the chunk. + #[inline] + pub fn set_uncompressed_size(&mut self, size: u32) { + let size = size as u64; + assert!(size != 0 && size <= BLOB_METADATA_CHUNK_SIZE_MASK + 1); + + let size_low = ((size - 1) & BLOB_METADATA_V1_CHUNK_SIZE_LOW_MASK) + << BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; + let size_high = ((size - 1) & BLOB_METADATA_V1_CHUNK_SIZE_HIGH_MASK) + >> BLOB_METADATA_V1_CHUNK_SIZE_HIGH_UNCOMP_SHIFT; + let offset = self.uncomp_info & BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK; + + self.uncomp_info = size_low | offset | size_high; + } + + /// Get uncompressed size of the chunk. + #[inline] + pub fn uncompressed_end(&self) -> u64 { + self.uncompressed_offset() + self.uncompressed_size() as u64 + } + + /// Get 4k aligned uncompressed size of the chunk. + #[inline] + pub fn aligned_uncompressed_end(&self) -> u64 { + round_up_4k(self.uncompressed_end()) + } + + /// Check whether the blob chunk is compressed or not. + /// + /// Assume the image builder guarantee that compress_size < uncompress_size if the chunk is + /// compressed. + #[inline] + pub fn is_compressed(&self) -> bool { + self.compressed_size() != self.uncompressed_size() + } +} diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 15024b43301..0d51bf78478 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -28,23 +28,22 @@ use nydus_utils::filemap::FileMapState; use crate::backend::BlobReader; use crate::device::{BlobChunkInfo, BlobInfo}; use crate::utils::alloc_buf; +use crate::{RAFS_MAX_CHUNKS_PER_BLOB, RAFS_MAX_CHUNK_SIZE}; + +mod chunk_info_v1; +pub use chunk_info_v1::BlobChunkInfoV1Ondisk; -const BLOB_METADATA_MAX_CHUNKS: u32 = 0xf_ffff; -const BLOB_METADATA_MAX_SIZE: u64 = 0x100_0000u64; -const BLOB_METADATA_HEADER_SIZE: u64 = 0x1000u64; -const BLOB_METADATA_RESERVED_SIZE: u64 = BLOB_METADATA_HEADER_SIZE - 44; const BLOB_METADATA_MAGIC: u32 = 0xb10bb10bu32; -const BLOB_CHUNK_COMP_OFFSET_MASK: u64 = 0xff_ffff_ffff; -const BLOB_CHUNK_UNCOMP_OFFSET_MASK: u64 = 0xfff_ffff_f000; -const BLOB_CHUNK_SIZE_MASK: u64 = 0xff_ffff; -const BLOB_CHUNK_SIZE_LOW_MASK: u64 = 0x0f_ffff; -const BLOB_CHUNK_SIZE_HIGH_MASK: u64 = 0xf0_0000; -const BLOB_CHUNK_SIZE_LOW_SHIFT: u64 = 44; -const BLOB_CHUNK_SIZE_HIGH_COMP_SHIFT: u64 = 20; -const BLOB_CHUNK_SIZE_HIGH_UNCOMP_SHIFT: u64 = 12; -const FILE_SUFFIX: &str = "blob.meta"; - -pub const BLOB_FEATURE_4K_ALIGNED: u32 = 0x1; +const BLOB_METADATA_HEADER_SIZE: u64 = 0x1000u64; +const BLOB_METADATA_CHUNK_SIZE_MASK: u64 = 0xff_ffff; + +const BLOB_METADATA_V1_MAX_SIZE: u64 = RAFS_MAX_CHUNK_SIZE * 16; +const BLOB_METADATA_V1_RESERVED_SIZE: u64 = BLOB_METADATA_HEADER_SIZE - 44; + +/// File suffix for blob meta file. +pub const FILE_SUFFIX: &str = "blob.meta"; +/// Uncompressed chunk data is 4K aligned. +pub const BLOB_META_FEATURE_4K_ALIGNED: u32 = 0x1; /// Blob metadata on disk format. #[repr(C)] @@ -64,7 +63,7 @@ pub struct BlobMetaHeaderOndisk { s_ci_compressed_size: u64, /// Size of uncompressed chunk information array s_ci_uncompressed_size: u64, - s_reserved: [u8; BLOB_METADATA_RESERVED_SIZE as usize], + s_reserved: [u8; BLOB_METADATA_V1_RESERVED_SIZE as usize], /// Second blob metadata magic number s_magic2: u32, } @@ -79,7 +78,7 @@ impl Default for BlobMetaHeaderOndisk { s_ci_offset: 0, s_ci_compressed_size: 0, s_ci_uncompressed_size: 0, - s_reserved: [0u8; BLOB_METADATA_RESERVED_SIZE as usize], + s_reserved: [0u8; BLOB_METADATA_V1_RESERVED_SIZE as usize], s_magic2: BLOB_METADATA_MAGIC, } } @@ -145,15 +144,15 @@ impl BlobMetaHeaderOndisk { /// Check whether the uncompressed data chunk is 4k aligned. pub fn is_4k_aligned(&self) -> bool { - self.s_features & BLOB_FEATURE_4K_ALIGNED != 0 + self.s_features & BLOB_META_FEATURE_4K_ALIGNED != 0 } /// Set whether the uncompressed data chunk is 4k aligned. pub fn set_4k_aligned(&mut self, aligned: bool) { if aligned { - self.s_features |= BLOB_FEATURE_4K_ALIGNED; + self.s_features |= BLOB_META_FEATURE_4K_ALIGNED; } else { - self.s_features &= !BLOB_FEATURE_4K_ALIGNED; + self.s_features &= !BLOB_META_FEATURE_4K_ALIGNED; } } @@ -172,116 +171,6 @@ impl BlobMetaHeaderOndisk { } } -/// Blob chunk compression information on disk format. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct BlobChunkInfoOndisk { - // 20bits: size (low), 32bits: offset, 4bits: size (high), 8bits reserved - uncomp_info: u64, - // 20bits: size (low), 4bits: size (high), offset: 40bits - comp_info: u64, -} - -impl BlobChunkInfoOndisk { - /// Get compressed offset of the chunk. - #[inline] - pub fn compressed_offset(&self) -> u64 { - self.comp_info & BLOB_CHUNK_COMP_OFFSET_MASK - } - - /// Set compressed offset of the chunk. - #[inline] - pub fn set_compressed_offset(&mut self, offset: u64) { - debug_assert!(offset & !BLOB_CHUNK_COMP_OFFSET_MASK == 0); - self.comp_info &= !BLOB_CHUNK_COMP_OFFSET_MASK; - self.comp_info |= offset & BLOB_CHUNK_COMP_OFFSET_MASK; - } - - /// Get compressed size of the chunk. - #[inline] - pub fn compressed_size(&self) -> u32 { - let bit20 = self.comp_info >> BLOB_CHUNK_SIZE_LOW_SHIFT; - let bit4 = (self.comp_info & 0xf0000000000) >> BLOB_CHUNK_SIZE_HIGH_COMP_SHIFT; - (bit4 | bit20) as u32 + 1 - } - - /// Set compressed size of the chunk. - #[inline] - pub fn set_compressed_size(&mut self, size: u32) { - let size = size as u64; - assert!(size > 0 && size <= BLOB_CHUNK_SIZE_MASK + 1); - - let size_low = ((size - 1) & BLOB_CHUNK_SIZE_LOW_MASK) << BLOB_CHUNK_SIZE_LOW_SHIFT; - let size_high = ((size - 1) & BLOB_CHUNK_SIZE_HIGH_MASK) << BLOB_CHUNK_SIZE_HIGH_COMP_SHIFT; - let offset = self.comp_info & BLOB_CHUNK_COMP_OFFSET_MASK; - - self.comp_info = size_low | size_high | offset; - } - - /// Get compressed end of the chunk. - #[inline] - pub fn compressed_end(&self) -> u64 { - self.compressed_offset() + self.compressed_size() as u64 - } - - /// Get uncompressed offset of the chunk. - #[inline] - pub fn uncompressed_offset(&self) -> u64 { - self.uncomp_info & BLOB_CHUNK_UNCOMP_OFFSET_MASK - } - - /// Set uncompressed offset of the chunk. - #[inline] - pub fn set_uncompressed_offset(&mut self, offset: u64) { - debug_assert!(offset & !BLOB_CHUNK_UNCOMP_OFFSET_MASK == 0); - self.uncomp_info &= !BLOB_CHUNK_UNCOMP_OFFSET_MASK; - self.uncomp_info |= offset & BLOB_CHUNK_UNCOMP_OFFSET_MASK; - } - - /// Get uncompressed end of the chunk. - #[inline] - pub fn uncompressed_size(&self) -> u32 { - let size_high = (self.uncomp_info & 0xf00) << BLOB_CHUNK_SIZE_HIGH_UNCOMP_SHIFT; - let size_low = self.uncomp_info >> BLOB_CHUNK_SIZE_LOW_SHIFT; - (size_high | size_low) as u32 + 1 - } - - /// Set uncompressed end of the chunk. - #[inline] - pub fn set_uncompressed_size(&mut self, size: u32) { - let size = size as u64; - debug_assert!(size != 0 && size <= BLOB_CHUNK_SIZE_MASK + 1); - - let size_low = ((size - 1) & BLOB_CHUNK_SIZE_LOW_MASK) << BLOB_CHUNK_SIZE_LOW_SHIFT; - let size_high = - ((size - 1) & BLOB_CHUNK_SIZE_HIGH_MASK) >> BLOB_CHUNK_SIZE_HIGH_UNCOMP_SHIFT; - let offset = self.uncomp_info & BLOB_CHUNK_UNCOMP_OFFSET_MASK; - - self.uncomp_info = size_low | offset | size_high; - } - - /// Get uncompressed size of the chunk. - #[inline] - pub fn uncompressed_end(&self) -> u64 { - self.uncompressed_offset() + self.uncompressed_size() as u64 - } - - /// Get 4k aligned uncompressed size of the chunk. - #[inline] - pub fn aligned_uncompressed_end(&self) -> u64 { - round_up_4k(self.uncompressed_end()) - } - - /// Check whether the blob chunk is compressed or not. - /// - /// Assume the image builder guarantee that compress_size < uncompress_size if the chunk is - /// compressed. - #[inline] - pub fn is_compressed(&self) -> bool { - self.compressed_size() != self.uncompressed_size() - } -} - /// Struct to maintain metadata information for a blob object. /// /// Currently, the major responsibility of the `BlobMetaInfo` object is to query chunks covering @@ -309,9 +198,9 @@ impl BlobMetaInfo { size_of::() as u64, BLOB_METADATA_HEADER_SIZE ); - assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); let chunk_count = blob_info.chunk_count(); - if chunk_count == 0 || chunk_count > BLOB_METADATA_MAX_CHUNKS { + if chunk_count == 0 || chunk_count > RAFS_MAX_CHUNKS_PER_BLOB { return Err(einval!("chunk count should be greater than 0")); } @@ -338,8 +227,8 @@ impl BlobMetaInfo { let info_size = blob_info.meta_ci_uncompressed_size() as usize; let aligned_info_size = round_up_4k(info_size); let expected_size = BLOB_METADATA_HEADER_SIZE as usize + aligned_info_size; - if info_size != (chunk_count as usize) * (size_of::()) - || (aligned_info_size as u64) > BLOB_METADATA_MAX_SIZE + if info_size != (chunk_count as usize) * (size_of::()) + || (aligned_info_size as u64) > BLOB_METADATA_V1_MAX_SIZE { return Err(einval!("blob metadata size is too big!")); } @@ -384,7 +273,7 @@ impl BlobMetaInfo { let chunk_infos = unsafe { ManuallyDrop::new(Vec::from_raw_parts( - base as *mut u8 as *mut BlobChunkInfoOndisk, + base as *mut u8 as *mut BlobChunkInfoV1Ondisk, chunk_count as usize, chunk_count as usize, )) @@ -435,11 +324,11 @@ impl BlobMetaInfo { let infos = &*self.state.chunks; let mut index = self.state.get_chunk_index_nocheck(start, false)?; - debug_assert!(index < infos.len()); + assert!(index < infos.len()); let entry = &infos[index]; self.validate_chunk(entry)?; - debug_assert!(entry.uncompressed_offset() <= start); - debug_assert!(entry.uncompressed_end() > start); + assert!(entry.uncompressed_offset() <= start); + assert!(entry.uncompressed_end() > start); trace!( "get_chunks_uncompressed: entry {} {}", entry.uncompressed_offset(), @@ -606,7 +495,7 @@ impl BlobMetaInfo { } #[inline] - fn validate_chunk(&self, entry: &BlobChunkInfoOndisk) -> Result<()> { + fn validate_chunk(&self, entry: &BlobChunkInfoV1Ondisk) -> Result<()> { // For stargz blob, self.state.compressed_size == 0, so don't validate it. if (!self.state.is_stargz && entry.compressed_end() > self.state.compressed_size) || entry.uncompressed_end() > self.state.uncompressed_size @@ -696,7 +585,7 @@ pub struct BlobMetaState { // chunks, it usually refers to a blob file in cache(e.g. filecache). uncompressed_size: u64, chunk_count: u32, - chunks: ManuallyDrop>, + chunks: ManuallyDrop>, _filemap: FileMapState, /// The blob meta is for an stargz image. is_stargz: bool, @@ -829,11 +718,11 @@ mod tests { uncompressed_size: 0, chunk_count: 2, chunks: ManuallyDrop::new(vec![ - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0000_0000, comp_info: 0x00ff_f000_0000_0000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0010_0000, comp_info: 0x00ff_f000_0010_0000, }, @@ -853,7 +742,7 @@ mod tests { #[test] fn test_new_chunk_on_disk() { - let mut chunk = BlobChunkInfoOndisk::default(); + let mut chunk = BlobChunkInfoV1Ondisk::default(); assert_eq!(chunk.compressed_offset(), 0); assert_eq!(chunk.compressed_size(), 1); @@ -883,7 +772,7 @@ mod tests { assert_eq!(chunk.uncompressed_size(), 0x1000000); // For testing old format compatibility. - let chunk = BlobChunkInfoOndisk { + let chunk = BlobChunkInfoV1Ondisk { uncomp_info: 0xffff_ffff_f100_0000, comp_info: 0xffff_f0ff_ffff_ffff, }; @@ -901,23 +790,23 @@ mod tests { uncompressed_size: 0x102001, chunk_count: 5, chunks: ManuallyDrop::new(vec![ - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x0100_0000_0000_0000, comp_info: 0x00ff_f000_0000_0000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0000_2000, comp_info: 0x01ff_f000_0000_1000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0000_4000, comp_info: 0x00ff_f000_0000_3000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0010_0000, comp_info: 0x00ff_f000_0000_4000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0010_2000, comp_info: 0x00ff_f000_0000_5000, }, @@ -1014,11 +903,11 @@ mod tests { .unwrap(); let chunks = vec![ - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0000_0000, comp_info: 0x00ff_f000_0000_0000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0010_0000, comp_info: 0x00ff_f000_0010_0000, }, @@ -1027,7 +916,7 @@ mod tests { let data = unsafe { std::slice::from_raw_parts( chunks.as_ptr() as *const u8, - chunks.len() * std::mem::size_of::(), + chunks.len() * std::mem::size_of::(), ) }; @@ -1076,11 +965,11 @@ mod tests { .unwrap(); let chunks = vec![ - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0000_0000, comp_info: 0x00ff_f000_0000_0000, }, - BlobChunkInfoOndisk { + BlobChunkInfoV1Ondisk { uncomp_info: 0x01ff_f000_0010_0000, comp_info: 0x00ff_f000_0010_0000, }, @@ -1089,7 +978,7 @@ mod tests { let data = unsafe { std::slice::from_raw_parts( chunks.as_ptr() as *const u8, - chunks.len() * std::mem::size_of::(), + chunks.len() * std::mem::size_of::(), ) }; From 2455c7ef727a22aaaf40b0cdf5f6d1faaaf61a91 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 16 Oct 2022 14:52:42 +0800 Subject: [PATCH 3/7] storage: introduce trait BlobMetaChunkInfo Introduce trait BlobMetaChunkInfo so we could introduce other chunk info format later. Signed-off-by: Jiang Liu --- src/bin/nydus-image/core/context.rs | 2 +- storage/src/meta/chunk_info_v1.rs | 61 ++++++----------------------- storage/src/meta/mod.rs | 48 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/bin/nydus-image/core/context.rs b/src/bin/nydus-image/core/context.rs index 9f90a778d01..b436c64d707 100644 --- a/src/bin/nydus-image/core/context.rs +++ b/src/bin/nydus-image/core/context.rs @@ -28,7 +28,7 @@ use nydus_rafs::metadata::{Inode, RAFS_DEFAULT_CHUNK_SIZE}; use nydus_rafs::metadata::{RafsSuperFlags, RafsVersion}; use nydus_rafs::{RafsIoReader, RafsIoWrite}; use nydus_storage::device::{BlobFeatures, BlobInfo}; -use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaChunkInfo, BlobMetaHeaderOndisk}; use nydus_utils::{compress, digest, div_round_up, round_down_4k}; use super::chunk_dict::{ChunkDict, HashChunkDict}; diff --git a/storage/src/meta/chunk_info_v1.rs b/storage/src/meta/chunk_info_v1.rs index 7aa1999e3e0..2a9ec0ef182 100644 --- a/storage/src/meta/chunk_info_v1.rs +++ b/storage/src/meta/chunk_info_v1.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -use crate::meta::{round_up_4k, BLOB_METADATA_CHUNK_SIZE_MASK}; +use crate::meta::{BlobMetaChunkInfo, BLOB_METADATA_CHUNK_SIZE_MASK}; const BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK: u64 = 0xff_ffff_ffff; const BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK: u64 = 0xfff_ffff_f000; @@ -22,32 +22,24 @@ pub struct BlobChunkInfoV1Ondisk { pub(crate) comp_info: u64, } -impl BlobChunkInfoV1Ondisk { - /// Get compressed offset of the chunk. - #[inline] - pub fn compressed_offset(&self) -> u64 { +impl BlobMetaChunkInfo for BlobChunkInfoV1Ondisk { + fn compressed_offset(&self) -> u64 { self.comp_info & BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK } - /// Set compressed offset of the chunk. - #[inline] - pub fn set_compressed_offset(&mut self, offset: u64) { + fn set_compressed_offset(&mut self, offset: u64) { assert!(offset & !BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK == 0); self.comp_info &= !BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK; self.comp_info |= offset & BLOB_METADATA_V1_CHUNK_COMP_OFFSET_MASK; } - /// Get compressed size of the chunk. - #[inline] - pub fn compressed_size(&self) -> u32 { + fn compressed_size(&self) -> u32 { let bit20 = self.comp_info >> BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; let bit4 = (self.comp_info & 0xf0000000000) >> BLOB_METADATA_V1_CHUNK_SIZE_HIGH_COMP_SHIFT; (bit4 | bit20) as u32 + 1 } - /// Set compressed size of the chunk. - #[inline] - pub fn set_compressed_size(&mut self, size: u32) { + fn set_compressed_size(&mut self, size: u32) { let size = size as u64; assert!(size > 0 && size <= BLOB_METADATA_CHUNK_SIZE_MASK + 1); @@ -60,37 +52,23 @@ impl BlobChunkInfoV1Ondisk { self.comp_info = size_low | size_high | offset; } - /// Get compressed end of the chunk. - #[inline] - pub fn compressed_end(&self) -> u64 { - self.compressed_offset() + self.compressed_size() as u64 - } - - /// Get uncompressed offset of the chunk. - #[inline] - pub fn uncompressed_offset(&self) -> u64 { + fn uncompressed_offset(&self) -> u64 { self.uncomp_info & BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK } - /// Set uncompressed offset of the chunk. - #[inline] - pub fn set_uncompressed_offset(&mut self, offset: u64) { + fn set_uncompressed_offset(&mut self, offset: u64) { assert!(offset & !BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK == 0); self.uncomp_info &= !BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK; self.uncomp_info |= offset & BLOB_METADATA_V1_CHUNK_UNCOMP_OFFSET_MASK; } - /// Get uncompressed end of the chunk. - #[inline] - pub fn uncompressed_size(&self) -> u32 { + fn uncompressed_size(&self) -> u32 { let size_high = (self.uncomp_info & 0xf00) << BLOB_METADATA_V1_CHUNK_SIZE_HIGH_UNCOMP_SHIFT; let size_low = self.uncomp_info >> BLOB_METADATA_V1_CHUNK_SIZE_LOW_SHIFT; (size_high | size_low) as u32 + 1 } - /// Set uncompressed end of the chunk. - #[inline] - pub fn set_uncompressed_size(&mut self, size: u32) { + fn set_uncompressed_size(&mut self, size: u32) { let size = size as u64; assert!(size != 0 && size <= BLOB_METADATA_CHUNK_SIZE_MASK + 1); @@ -103,24 +81,7 @@ impl BlobChunkInfoV1Ondisk { self.uncomp_info = size_low | offset | size_high; } - /// Get uncompressed size of the chunk. - #[inline] - pub fn uncompressed_end(&self) -> u64 { - self.uncompressed_offset() + self.uncompressed_size() as u64 - } - - /// Get 4k aligned uncompressed size of the chunk. - #[inline] - pub fn aligned_uncompressed_end(&self) -> u64 { - round_up_4k(self.uncompressed_end()) - } - - /// Check whether the blob chunk is compressed or not. - /// - /// Assume the image builder guarantee that compress_size < uncompress_size if the chunk is - /// compressed. - #[inline] - pub fn is_compressed(&self) -> bool { + fn is_compressed(&self) -> bool { self.compressed_size() != self.uncompressed_size() } } diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 0d51bf78478..8f7e9c39b40 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -577,6 +577,7 @@ impl BlobMetaInfo { } } +/// Struct to maintain state and provide accessors to blob meta information. pub struct BlobMetaState { blob_index: u32, // The file size of blob file when it contains compressed chunks. @@ -693,6 +694,53 @@ impl BlobChunkInfo for BlobMetaChunk { } } +/// Trait to get blob meta chunk information. +pub trait BlobMetaChunkInfo { + /// Get compressed offset of the chunk. + fn compressed_offset(&self) -> u64; + + /// Set compressed offset of the chunk. + fn set_compressed_offset(&mut self, offset: u64); + + /// Get compressed size of the chunk. + fn compressed_size(&self) -> u32; + + /// Set compressed size of the chunk. + fn set_compressed_size(&mut self, size: u32); + + fn compressed_end(&self) -> u64 { + self.compressed_offset() + self.compressed_size() as u64 + } + + /// Get uncompressed offset of the chunk. + fn uncompressed_offset(&self) -> u64; + + /// Set uncompressed offset of the chunk. + fn set_uncompressed_offset(&mut self, offset: u64); + + /// Get uncompressed end of the chunk. + fn uncompressed_size(&self) -> u32; + + /// Set uncompressed end of the chunk. + fn set_uncompressed_size(&mut self, size: u32); + + /// Get uncompressed size of the chunk. + fn uncompressed_end(&self) -> u64 { + self.uncompressed_offset() + self.uncompressed_size() as u64 + } + + /// Get 4k aligned uncompressed size of the chunk. + fn aligned_uncompressed_end(&self) -> u64 { + round_up_4k(self.uncompressed_end()) + } + + /// Check whether the blob chunk is compressed or not. + /// + /// Assume the image builder guarantee that compress_size < uncompress_size if the chunk is + /// compressed. + fn is_compressed(&self) -> bool; +} + fn round_up_4k + BitAnd + Not + From>(val: T) -> T { (val + T::from(0xfff)) & !T::from(0xfff) } From 3dbc6f92f9cba4bfbfa92312c9bb8c809e58f781 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 16 Oct 2022 15:16:17 +0800 Subject: [PATCH 4/7] storage: make BlobChunkInfoV1Ondisk private Make BlobChunkInfoV1Ondisk private so we could support different chunk information format later. Signed-off-by: Jiang Liu --- src/bin/nydus-image/core/blob.rs | 11 ++---- src/bin/nydus-image/core/context.rs | 55 ++++++-------------------- storage/src/meta/mod.rs | 61 ++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 51 deletions(-) diff --git a/src/bin/nydus-image/core/blob.rs b/src/bin/nydus-image/core/blob.rs index 9f7da8b95b4..3644c19ecb5 100644 --- a/src/bin/nydus-image/core/blob.rs +++ b/src/bin/nydus-image/core/blob.rs @@ -6,7 +6,7 @@ use std::io::Write; use anyhow::{Context, Result}; use nydus_rafs::metadata::RAFS_MAX_CHUNK_SIZE; -use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{BlobMetaChunkArray, BlobMetaHeaderOndisk}; use nydus_utils::{compress, try_round_up_4k}; use sha2::Digest; @@ -75,15 +75,10 @@ impl Blob { fn dump_meta_data_raw( pos: u64, - blob_meta_info: &[BlobChunkInfoV1Ondisk], + blob_meta_info: &BlobMetaChunkArray, compressor: compress::Algorithm, ) -> Result<(std::borrow::Cow<[u8]>, BlobMetaHeaderOndisk)> { - let data = unsafe { - std::slice::from_raw_parts( - blob_meta_info.as_ptr() as *const u8, - blob_meta_info.len() * std::mem::size_of::(), - ) - }; + let data = blob_meta_info.as_byte_slice(); let (buf, compressed) = compress::compress(data, compressor) .with_context(|| "failed to compress blob chunk info array".to_string())?; diff --git a/src/bin/nydus-image/core/context.rs b/src/bin/nydus-image/core/context.rs index b436c64d707..67e3af227a5 100644 --- a/src/bin/nydus-image/core/context.rs +++ b/src/bin/nydus-image/core/context.rs @@ -28,7 +28,7 @@ use nydus_rafs::metadata::{Inode, RAFS_DEFAULT_CHUNK_SIZE}; use nydus_rafs::metadata::{RafsSuperFlags, RafsVersion}; use nydus_rafs::{RafsIoReader, RafsIoWrite}; use nydus_storage::device::{BlobFeatures, BlobInfo}; -use nydus_storage::meta::{BlobChunkInfoV1Ondisk, BlobMetaChunkInfo, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{BlobMetaChunkArray, BlobMetaHeaderOndisk}; use nydus_utils::{compress, digest, div_round_up, round_down_4k}; use super::chunk_dict::{ChunkDict, HashChunkDict}; @@ -329,8 +329,7 @@ pub struct BlobContext { /// Whether to generate blob metadata information. pub blob_meta_info_enabled: bool, /// Data chunks stored in the data blob, for v6. - /// TODO: zran - pub blob_meta_info: Vec, + pub blob_meta_info: BlobMetaChunkArray, /// Blob metadata header stored in the data blob, for v6 pub blob_meta_header: BlobMetaHeaderOndisk, @@ -351,37 +350,16 @@ pub struct BlobContext { pub chunk_source: ChunkSource, } -impl Clone for BlobContext { - fn clone(&self) -> Self { - Self { - blob_id: self.blob_id.clone(), - blob_hash: self.blob_hash.clone(), - blob_prefetch_size: self.blob_prefetch_size, - blob_meta_info_enabled: self.blob_meta_info_enabled, - blob_meta_info: self.blob_meta_info.clone(), - blob_meta_header: self.blob_meta_header, - - compressed_blob_size: self.compressed_blob_size, - uncompressed_blob_size: self.uncompressed_blob_size, - - compressed_offset: self.compressed_offset, - uncompressed_offset: self.uncompressed_offset, - - chunk_count: self.chunk_count, - chunk_size: self.chunk_size, - chunk_source: self.chunk_source.clone(), - } - } -} - impl BlobContext { pub fn new(blob_id: String, blob_offset: u64) -> Self { + let blob_meta_info = BlobMetaChunkArray::new_v1(); + Self { blob_id, blob_hash: Sha256::new(), blob_prefetch_size: 0, blob_meta_info_enabled: false, - blob_meta_info: Vec::new(), + blob_meta_info, blob_meta_header: BlobMetaHeaderOndisk::default(), compressed_blob_size: 0, @@ -448,23 +426,16 @@ impl BlobContext { } pub fn add_chunk_meta_info(&mut self, chunk: &ChunkWrapper) -> Result<()> { - if !self.blob_meta_info_enabled { - return Ok(()); + if self.blob_meta_info_enabled { + assert_eq!(chunk.index() as usize, self.blob_meta_info.len()); + self.blob_meta_info.add_v1( + chunk.compressed_offset(), + chunk.compressed_size(), + chunk.uncompressed_offset(), + chunk.uncompressed_size(), + ); } - debug_assert!(chunk.index() as usize == self.blob_meta_info.len()); - let mut meta = BlobChunkInfoV1Ondisk::default(); - meta.set_compressed_offset(chunk.compressed_offset()); - meta.set_compressed_size(chunk.compressed_size()); - meta.set_uncompressed_offset(chunk.uncompressed_offset()); - meta.set_uncompressed_size(chunk.uncompressed_size()); - trace!( - "chunk uncompressed {} size {}", - meta.uncompressed_offset(), - meta.uncompressed_size() - ); - self.blob_meta_info.push(meta); - Ok(()) } diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 8f7e9c39b40..67eb00f757e 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -31,7 +31,7 @@ use crate::utils::alloc_buf; use crate::{RAFS_MAX_CHUNKS_PER_BLOB, RAFS_MAX_CHUNK_SIZE}; mod chunk_info_v1; -pub use chunk_info_v1::BlobChunkInfoV1Ondisk; +use chunk_info_v1::BlobChunkInfoV1Ondisk; const BLOB_METADATA_MAGIC: u32 = 0xb10bb10bu32; const BLOB_METADATA_HEADER_SIZE: u64 = 0x1000u64; @@ -635,6 +635,65 @@ impl BlobMetaState { } } +/// A customized array to generate chunk information array. +pub enum BlobMetaChunkArray { + /// Chunk information V1 array. + V1(Vec), +} + +impl BlobMetaChunkArray { + /// Create a `BlokMetaChunkArray` for v1 chunk information format. + pub fn new_v1() -> Self { + BlobMetaChunkArray::V1(Vec::new()) + } + + /// Get number of entry in the blob chunk information array. + pub fn len(&self) -> usize { + match self { + BlobMetaChunkArray::V1(v) => v.len(), + } + } + + /// Check whether the chunk information array is empty. + pub fn is_empty(&self) -> bool { + match self { + BlobMetaChunkArray::V1(v) => v.is_empty(), + } + } + + /// Get the chunk information data as a u8 slice. + pub fn as_byte_slice(&self) -> &[u8] { + match self { + BlobMetaChunkArray::V1(v) => unsafe { + std::slice::from_raw_parts( + v.as_ptr() as *const u8, + v.len() * std::mem::size_of::(), + ) + }, + } + } + + /// Add an v1 chunk information entry. + pub fn add_v1( + &mut self, + compressed_offset: u64, + compressed_size: u32, + uncompressed_offset: u64, + uncompressed_size: u32, + ) { + match self { + BlobMetaChunkArray::V1(v) => { + let mut meta = BlobChunkInfoV1Ondisk::default(); + meta.set_compressed_offset(compressed_offset); + meta.set_compressed_size(compressed_size); + meta.set_uncompressed_offset(uncompressed_offset); + meta.set_uncompressed_size(uncompressed_size); + v.push(meta); + } + } + } +} + /// A fake `BlobChunkInfo` object created from blob metadata. /// /// It represents a chunk within memory mapped chunk maps, which From 7cfcc906f5f1d2c2732e268a45389e6bc5381c17 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sat, 15 Oct 2022 14:22:19 +0800 Subject: [PATCH 5/7] storage: prepare for supporting of multiple blob meta formats Change the blob meta implement to prepare for supporting of multiple blob meta formats. Signed-off-by: Jiang Liu --- storage/src/meta/mod.rs | 622 ++++++++++++++++++++++++++-------------- 1 file changed, 400 insertions(+), 222 deletions(-) diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 67eb00f757e..44cc3e066d1 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -165,7 +165,7 @@ impl BlobMetaHeaderOndisk { unsafe { std::slice::from_raw_parts( self as *const BlobMetaHeaderOndisk as *const u8, - std::mem::size_of::(), + size_of::(), ) } } @@ -227,12 +227,6 @@ impl BlobMetaInfo { let info_size = blob_info.meta_ci_uncompressed_size() as usize; let aligned_info_size = round_up_4k(info_size); let expected_size = BLOB_METADATA_HEADER_SIZE as usize + aligned_info_size; - if info_size != (chunk_count as usize) * (size_of::()) - || (aligned_info_size as u64) > BLOB_METADATA_V1_MAX_SIZE - { - return Err(einval!("blob metadata size is too big!")); - } - let file_size = file.metadata()?.len(); if file_size == 0 && enable_write { file.set_len(expected_size as u64)?; @@ -241,13 +235,7 @@ impl BlobMetaInfo { let mut filemap = FileMapState::new(file, 0, expected_size, enable_write)?; let base = filemap.validate_range(0, expected_size)?; let header = filemap.get_mut::(aligned_info_size as usize)?; - if u32::from_le(header.s_magic) != BLOB_METADATA_MAGIC - || u32::from_le(header.s_magic2) != BLOB_METADATA_MAGIC - || u32::from_le(header.s_features) != blob_info.meta_flags() - || u64::from_le(header.s_ci_offset) != blob_info.meta_ci_offset() - || u64::from_le(header.s_ci_compressed_size) != blob_info.meta_ci_compressed_size() - || u64::from_le(header.s_ci_uncompressed_size) != blob_info.meta_ci_uncompressed_size() - { + if !Self::validate_header(blob_info, header)? { if !enable_write { return Err(enoent!("blob metadata file is not ready")); } @@ -259,6 +247,7 @@ impl BlobMetaInfo { reader.as_ref().unwrap(), &mut buffer[..info_size], )?; + header.s_features = u32::to_le(blob_info.meta_flags()); header.s_ci_offset = u64::to_le(blob_info.meta_ci_offset()); header.s_ci_compressed_size = u64::to_le(blob_info.meta_ci_compressed_size()); @@ -271,20 +260,13 @@ impl BlobMetaInfo { filemap.sync_data()?; } - let chunk_infos = unsafe { - ManuallyDrop::new(Vec::from_raw_parts( - base as *mut u8 as *mut BlobChunkInfoV1Ondisk, - chunk_count as usize, - chunk_count as usize, - )) - }; - + let chunk_infos = BlobMetaChunkArray::from_file_map(&filemap, blob_info)?; + let chunk_infos = ManuallyDrop::new(chunk_infos); let state = Arc::new(BlobMetaState { blob_index: blob_info.blob_index(), compressed_size: blob_info.compressed_size(), uncompressed_size: round_up_4k(blob_info.uncompressed_size()), - chunk_count, - chunks: chunk_infos, + chunk_info_array: chunk_infos, _filemap: filemap, is_stargz: blob_info.is_stargz(), }); @@ -306,10 +288,15 @@ impl BlobMetaInfo { size: u64, batch_size: u64, ) -> Result>> { - let end = start.checked_add(size).ok_or_else(|| einval!())?; + let end = start.checked_add(size).ok_or_else(|| { + einval!(format!( + "get_chunks_uncompressed: invalid start {}/size {}", + start, size + )) + })?; if end > self.state.uncompressed_size { return Err(einval!(format!( - "get_chunks_uncompressed: end {} uncompressed_size {}", + "get_chunks_uncompressed: invalid end {}/uncompressed_size {}", end, self.state.uncompressed_size ))); } @@ -322,59 +309,7 @@ impl BlobMetaInfo { ) }; - let infos = &*self.state.chunks; - let mut index = self.state.get_chunk_index_nocheck(start, false)?; - assert!(index < infos.len()); - let entry = &infos[index]; - self.validate_chunk(entry)?; - assert!(entry.uncompressed_offset() <= start); - assert!(entry.uncompressed_end() > start); - trace!( - "get_chunks_uncompressed: entry {} {}", - entry.uncompressed_offset(), - entry.uncompressed_end() - ); - - let mut vec = Vec::with_capacity(512); - vec.push(BlobMetaChunk::new(index, &self.state)); - - let mut last_end = entry.aligned_uncompressed_end(); - if last_end >= batch_end { - Ok(vec) - } else { - while index + 1 < infos.len() { - index += 1; - let entry = &infos[index]; - self.validate_chunk(entry)?; - - // For stargz chunks, disable this check. - if !self.state.is_stargz && entry.uncompressed_offset() != last_end { - return Err(einval!(format!( - "mismatch uncompressed {} size {} last_end {}", - entry.uncompressed_offset(), - entry.uncompressed_size(), - last_end - ))); - } - - // Avoid read amplify if next chunk is too big. - if last_end >= end && entry.aligned_uncompressed_end() > batch_end { - return Ok(vec); - } - - vec.push(BlobMetaChunk::new(index, &self.state)); - last_end = entry.aligned_uncompressed_end(); - if last_end >= batch_end { - return Ok(vec); - } - } - - Err(einval!(format!( - "entry not found index {} infos.len {}", - index, - infos.len(), - ))) - } + self.state.get_chunks_uncompressed(start, end, batch_end) } /// Get blob chunks covering compressed data range [start, start + size). @@ -390,10 +325,15 @@ impl BlobMetaInfo { size: u64, batch_size: u64, ) -> Result>> { - let end = start.checked_add(size).ok_or_else(|| einval!())?; + let end = start.checked_add(size).ok_or_else(|| { + einval!(einval!(format!( + "get_chunks_compressed: invalid start {}/size {}", + start, size + ))) + })?; if end > self.state.compressed_size { return Err(einval!(format!( - "get_chunks_compressed: end {} compressed_size {}", + "get_chunks_compressed: invalid end {}/compressed_size {}", end, self.state.compressed_size ))); } @@ -406,41 +346,7 @@ impl BlobMetaInfo { ) }; - let infos = &*self.state.chunks; - let mut index = self.state.get_chunk_index_nocheck(start, true)?; - debug_assert!(index < infos.len()); - let entry = &infos[index]; - self.validate_chunk(entry)?; - - let mut vec = Vec::with_capacity(512); - vec.push(BlobMetaChunk::new(index, &self.state)); - - let mut last_end = entry.compressed_end(); - if last_end >= batch_end { - Ok(vec) - } else { - while index + 1 < infos.len() { - index += 1; - let entry = &infos[index]; - self.validate_chunk(entry)?; - if entry.compressed_offset() != last_end { - return Err(einval!()); - } - - // Avoid read amplify if next chunk is too big. - if last_end >= end && entry.compressed_end() > batch_end { - return Ok(vec); - } - - vec.push(BlobMetaChunk::new(index, &self.state)); - last_end = entry.compressed_end(); - if last_end >= batch_end { - return Ok(vec); - } - } - - Err(einval!()) - } + self.state.get_chunks_compressed(start, end, batch_end) } /// Try to amplify the request by appending more continuous chunks. @@ -449,68 +355,7 @@ impl BlobMetaInfo { chunks: &[Arc], max_size: u64, ) -> Option>> { - let infos = &*self.state.chunks; - let mut index = chunks[chunks.len() - 1].id() as usize; - debug_assert!(index < infos.len()); - let entry = &infos[index]; - if self.validate_chunk(entry).is_err() { - return None; - } - let end = entry.compressed_end(); - if end > self.state.compressed_size { - return None; - } - let batch_end = std::cmp::min( - end.checked_add(max_size).unwrap_or(end), - self.state.compressed_size, - ); - if batch_end <= end { - return None; - } - - let mut last_end = end; - let mut vec = chunks.to_vec(); - while index + 1 < infos.len() { - index += 1; - let entry = &infos[index]; - if self.validate_chunk(entry).is_err() || entry.compressed_offset() != last_end { - break; - } - - // Avoid read amplification if next chunk is too big. - if entry.compressed_end() > batch_end { - break; - } - - vec.push(BlobMetaChunk::new(index, &self.state)); - last_end = entry.compressed_end(); - if last_end >= batch_end { - break; - } - } - - trace!("try to extend request with {} more bytes", last_end - end); - - Some(vec) - } - - #[inline] - fn validate_chunk(&self, entry: &BlobChunkInfoV1Ondisk) -> Result<()> { - // For stargz blob, self.state.compressed_size == 0, so don't validate it. - if (!self.state.is_stargz && entry.compressed_end() > self.state.compressed_size) - || entry.uncompressed_end() > self.state.uncompressed_size - { - Err(einval!(format!( - "invalid chunk, blob_index {} compressed_end {} compressed_size {} uncompressed_end {} uncompressed_size {}", - self.state.blob_index, - entry.compressed_end(), - self.state.compressed_size, - entry.uncompressed_end(), - self.state.uncompressed_size, - ))) - } else { - Ok(()) - } + self.state.add_more_chunks(chunks, max_size) } fn read_metadata( @@ -571,10 +416,31 @@ impl BlobMetaInfo { buffer.copy_from_slice(&uncom_buf); } - // TODO: validate metadata - Ok(()) } + + fn validate_header(blob_info: &BlobInfo, header: &BlobMetaHeaderOndisk) -> Result { + if u32::from_le(header.s_magic) != BLOB_METADATA_MAGIC + || u32::from_le(header.s_magic2) != BLOB_METADATA_MAGIC + || u32::from_le(header.s_features) != blob_info.meta_flags() + || u64::from_le(header.s_ci_offset) != blob_info.meta_ci_offset() + || u64::from_le(header.s_ci_compressed_size) != blob_info.meta_ci_compressed_size() + || u64::from_le(header.s_ci_uncompressed_size) != blob_info.meta_ci_uncompressed_size() + { + return Ok(false); + } + + let chunk_count = blob_info.chunk_count(); + let info_size = u64::from_le(header.s_ci_uncompressed_size) as usize; + let aligned_info_size = round_up_4k(info_size); + if info_size != (chunk_count as usize) * (size_of::()) + || (aligned_info_size as u64) > BLOB_METADATA_V1_MAX_SIZE + { + return Err(einval!("blob metadata size is too big!")); + } + + Ok(true) + } } /// Struct to maintain state and provide accessors to blob meta information. @@ -585,53 +451,58 @@ pub struct BlobMetaState { // The file size of blob file when it contains raw(uncompressed) // chunks, it usually refers to a blob file in cache(e.g. filecache). uncompressed_size: u64, - chunk_count: u32, - chunks: ManuallyDrop>, + chunk_info_array: ManuallyDrop, _filemap: FileMapState, /// The blob meta is for an stargz image. is_stargz: bool, } impl BlobMetaState { - fn get_chunk_index_nocheck(&self, addr: u64, compressed: bool) -> Result { - let chunks = &self.chunks; - let mut size = self.chunk_count as usize; - let mut left = 0; - let mut right = size; - let mut start = 0; - let mut end = 0; + fn get_chunks_uncompressed( + self: &Arc, + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + self.chunk_info_array + .get_chunks_uncompressed(self, start, end, batch_end) + } - while left < right { - let mid = left + size / 2; - // SAFETY: the call is made safe by the following invariants: - // - `mid >= 0` - // - `mid < size`: `mid` is limited by `[left; right)` bound. - let entry = unsafe { chunks.get_unchecked(mid) }; - if compressed { - start = entry.compressed_offset(); - end = entry.compressed_end(); - } else { - start = entry.uncompressed_offset(); - end = entry.uncompressed_end(); - }; + fn get_chunks_compressed( + self: &Arc, + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + self.chunk_info_array + .get_chunks_compressed(self, start, end, batch_end) + } - if start > addr { - right = mid; - } else if end <= addr { - left = mid + 1; - } else { - return Ok(mid); - } + fn add_more_chunks( + self: &Arc, + chunks: &[Arc], + max_size: u64, + ) -> Option>> { + self.chunk_info_array + .add_more_chunks(self, chunks, max_size) + } - size = right - left; + fn validate_chunk(&self, entry: &T) -> Result<()> { + // For stargz blob, self.compressed_size == 0, so don't validate it. + if (!self.is_stargz && entry.compressed_end() > self.compressed_size) + || entry.uncompressed_end() > self.uncompressed_size + { + Err(einval!(format!( + "invalid chunk, blob_index {} compressed_end {} compressed_size {} uncompressed_end {} uncompressed_size {}", + self.blob_index, + entry.compressed_end(), + self.compressed_size, + entry.uncompressed_end(), + self.uncompressed_size, + ))) + } else { + Ok(()) } - - // if addr == self.chunks[last].compressed_offset, return einval - // with error msg. - Err(einval!(format!( - "start: {}, end: {}, addr: {}", - start, end, addr - ))) } } @@ -641,6 +512,7 @@ pub enum BlobMetaChunkArray { V1(Vec), } +// Methods for RAFS filesystem builder. impl BlobMetaChunkArray { /// Create a `BlokMetaChunkArray` for v1 chunk information format. pub fn new_v1() -> Self { @@ -694,6 +566,306 @@ impl BlobMetaChunkArray { } } +impl BlobMetaChunkArray { + fn from_file_map(filemap: &FileMapState, blob_info: &BlobInfo) -> Result { + let chunk_count = blob_info.chunk_count(); + let chunk_size = chunk_count as usize * size_of::(); + let base = filemap.validate_range(0, chunk_size)?; + let v = unsafe { + Vec::from_raw_parts( + base as *mut u8 as *mut BlobChunkInfoV1Ondisk, + chunk_count as usize, + chunk_count as usize, + ) + }; + Ok(BlobMetaChunkArray::V1(v)) + } + + #[cfg(test)] + fn get_chunk_index_nocheck(&self, addr: u64, compressed: bool) -> Result { + match self { + BlobMetaChunkArray::V1(v) => Self::_get_chunk_index_nocheck(v, addr, compressed), + } + } + + fn get_chunks_compressed( + &self, + state: &Arc, + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + match self { + BlobMetaChunkArray::V1(v) => { + Self::_get_chunks_compressed(state, v, start, end, batch_end) + } + } + } + + fn add_more_chunks( + &self, + state: &Arc, + chunks: &[Arc], + max_size: u64, + ) -> Option>> { + match self { + BlobMetaChunkArray::V1(v) => Self::_add_more_chunks(state, v, chunks, max_size), + } + } + + fn get_chunks_uncompressed( + &self, + state: &Arc, + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + match self { + BlobMetaChunkArray::V1(v) => { + Self::_get_chunks_uncompressed(state, v, start, end, batch_end) + } + } + } + + fn compressed_offset(&self, index: usize) -> u64 { + match self { + BlobMetaChunkArray::V1(v) => v[index].compressed_offset(), + } + } + + fn compressed_size(&self, index: usize) -> u32 { + match self { + BlobMetaChunkArray::V1(v) => v[index].compressed_size(), + } + } + + fn uncompressed_offset(&self, index: usize) -> u64 { + match self { + BlobMetaChunkArray::V1(v) => v[index].uncompressed_offset(), + } + } + + fn uncompressed_size(&self, index: usize) -> u32 { + match self { + BlobMetaChunkArray::V1(v) => v[index].uncompressed_size(), + } + } + + fn is_compressed(&self, index: usize) -> bool { + match self { + BlobMetaChunkArray::V1(v) => v[index].is_compressed(), + } + } + + fn _get_chunk_index_nocheck( + chunks: &[T], + addr: u64, + compressed: bool, + ) -> Result { + let mut size = chunks.len(); + let mut left = 0; + let mut right = size; + let mut start = 0; + let mut end = 0; + + while left < right { + let mid = left + size / 2; + // SAFETY: the call is made safe by the following invariants: + // - `mid >= 0` + // - `mid < size`: `mid` is limited by `[left; right)` bound. + let entry = &chunks[mid]; + if compressed { + start = entry.compressed_offset(); + end = entry.compressed_end(); + } else { + start = entry.uncompressed_offset(); + end = entry.uncompressed_end(); + }; + + if start > addr { + right = mid; + } else if end <= addr { + left = mid + 1; + } else { + return Ok(mid); + } + + size = right - left; + } + + // if addr == self.chunks[last].compressed_offset, return einval with error msg. + Err(einval!(format!( + "start: {}, end: {}, addr: {}", + start, end, addr + ))) + } + + fn _get_chunks_uncompressed( + state: &Arc, + chunk_info_array: &[T], + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + let mut vec = Vec::with_capacity(512); + + let mut index = Self::_get_chunk_index_nocheck(chunk_info_array, start, false)?; + assert!(index < chunk_info_array.len()); + let entry = &chunk_info_array[index]; + state.validate_chunk(entry)?; + assert!(entry.uncompressed_offset() <= start); + assert!(entry.uncompressed_end() > start); + trace!( + "get_chunks_uncompressed: entry {} {}", + entry.uncompressed_offset(), + entry.uncompressed_end() + ); + vec.push(BlobMetaChunk::new(index, state)); + + let mut last_end = entry.aligned_uncompressed_end(); + if last_end >= batch_end { + Ok(vec) + } else { + while index + 1 < chunk_info_array.len() { + index += 1; + let entry = &chunk_info_array[index]; + state.validate_chunk(entry)?; + + // For stargz chunks, disable this check. + if !state.is_stargz && entry.uncompressed_offset() != last_end { + return Err(einval!(format!( + "mismatch uncompressed {} size {} last_end {}", + entry.uncompressed_offset(), + entry.uncompressed_size(), + last_end + ))); + } + + // Avoid read amplify if next chunk is too big. + if last_end >= end && entry.aligned_uncompressed_end() > batch_end { + return Ok(vec); + } + + vec.push(BlobMetaChunk::new(index, state)); + last_end = entry.aligned_uncompressed_end(); + if last_end >= batch_end { + return Ok(vec); + } + } + + Err(einval!(format!( + "entry not found index {} chunk_info_array.len {}", + index, + chunk_info_array.len(), + ))) + } + } + + fn _get_chunks_compressed( + state: &Arc, + chunk_info_array: &[T], + start: u64, + end: u64, + batch_end: u64, + ) -> Result>> { + let mut vec = Vec::with_capacity(512); + + let mut index = Self::_get_chunk_index_nocheck(chunk_info_array, start, true)?; + assert!(index < chunk_info_array.len()); + let entry = &chunk_info_array[index]; + state.validate_chunk(entry)?; + assert!(entry.compressed_offset() <= start); + assert!(entry.compressed_end() > start); + vec.push(BlobMetaChunk::new(index, state)); + + let mut last_end = entry.compressed_end(); + if last_end >= batch_end { + Ok(vec) + } else { + while index + 1 < chunk_info_array.len() { + index += 1; + let entry = &chunk_info_array[index]; + state.validate_chunk(entry)?; + if !state.is_stargz && entry.compressed_offset() != last_end { + return Err(einval!(format!( + "mismatch compressed {} size {} last_end {}", + entry.compressed_offset(), + entry.compressed_size(), + last_end + ))); + } + + // Avoid read amplify if next chunk is too big. + if last_end >= end && entry.compressed_end() > batch_end { + return Ok(vec); + } + + vec.push(BlobMetaChunk::new(index, state)); + last_end = entry.compressed_end(); + if last_end >= batch_end { + return Ok(vec); + } + } + + Err(einval!(format!( + "entry not found index {} chunk_info_array.len {}", + index, + chunk_info_array.len(), + ))) + } + } + + fn _add_more_chunks( + state: &Arc, + chunk_info_array: &[T], + chunks: &[Arc], + max_size: u64, + ) -> Option>> { + let mut index = chunks[chunks.len() - 1].id() as usize; + assert!(index < chunk_info_array.len()); + let entry = &chunk_info_array[index]; + if state.validate_chunk(entry).is_err() { + return None; + } + let end = entry.compressed_end(); + if end > state.compressed_size { + return None; + } + let batch_end = std::cmp::min( + end.checked_add(max_size).unwrap_or(end), + state.compressed_size, + ); + if batch_end <= end { + return None; + } + + let mut last_end = end; + let mut vec = chunks.to_vec(); + while index + 1 < chunk_info_array.len() { + index += 1; + let entry = &chunk_info_array[index]; + if state.validate_chunk(entry).is_err() || entry.compressed_offset() != last_end { + break; + } + + // Avoid read amplification if next chunk is too big. + if entry.compressed_end() > batch_end { + break; + } + + vec.push(BlobMetaChunk::new(index, state)); + last_end = entry.compressed_end(); + if last_end >= batch_end { + break; + } + } + + trace!("try to extend request with {} more bytes", last_end - end); + + Some(vec) + } +} + /// A fake `BlobChunkInfo` object created from blob metadata. /// /// It represents a chunk within memory mapped chunk maps, which @@ -729,23 +901,29 @@ impl BlobChunkInfo for BlobMetaChunk { } fn compressed_offset(&self) -> u64 { - self.meta.chunks[self.chunk_index].compressed_offset() + self.meta + .chunk_info_array + .compressed_offset(self.chunk_index) } fn compressed_size(&self) -> u32 { - self.meta.chunks[self.chunk_index].compressed_size() + self.meta.chunk_info_array.compressed_size(self.chunk_index) } fn uncompressed_offset(&self) -> u64 { - self.meta.chunks[self.chunk_index].uncompressed_offset() + self.meta + .chunk_info_array + .uncompressed_offset(self.chunk_index) } fn uncompressed_size(&self) -> u32 { - self.meta.chunks[self.chunk_index].uncompressed_size() + self.meta + .chunk_info_array + .uncompressed_size(self.chunk_index) } fn is_compressed(&self) -> bool { - self.meta.chunks[self.chunk_index].is_compressed() + self.meta.chunk_info_array.is_compressed(self.chunk_index) } fn as_any(&self) -> &dyn Any { From 53c5c58968f784a834f3fd6053ea0f62296021b3 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Wed, 19 Oct 2022 10:36:57 +0800 Subject: [PATCH 6/7] nydusd: move chunk v1 related tests into mod v1 Move chunk v1 related tests into mod v1. Signed-off-by: Jiang Liu --- storage/src/meta/chunk_info_v1.rs | 188 ++++++++++++++++++++++++++++++ storage/src/meta/mod.rs | 148 ----------------------- 2 files changed, 188 insertions(+), 148 deletions(-) diff --git a/storage/src/meta/chunk_info_v1.rs b/storage/src/meta/chunk_info_v1.rs index 2a9ec0ef182..c3ed3f25c68 100644 --- a/storage/src/meta/chunk_info_v1.rs +++ b/storage/src/meta/chunk_info_v1.rs @@ -85,3 +85,191 @@ impl BlobMetaChunkInfo for BlobChunkInfoV1Ondisk { self.compressed_size() != self.uncompressed_size() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::meta::{BlobMetaChunkArray, BlobMetaInfo, BlobMetaState}; + use nydus_utils::filemap::FileMapState; + use std::mem::ManuallyDrop; + use std::sync::Arc; + + #[test] + fn test_new_chunk_on_disk() { + let mut chunk = BlobChunkInfoV1Ondisk::default(); + + assert_eq!(chunk.compressed_offset(), 0); + assert_eq!(chunk.compressed_size(), 1); + assert_eq!(chunk.compressed_end(), 1); + assert_eq!(chunk.uncompressed_offset(), 0); + assert_eq!(chunk.uncompressed_size(), 1); + assert_eq!(chunk.aligned_uncompressed_end(), 0x1000); + + chunk.set_compressed_offset(0x1000); + chunk.set_compressed_size(0x100); + assert_eq!(chunk.compressed_offset(), 0x1000); + assert_eq!(chunk.compressed_size(), 0x100); + + chunk.set_uncompressed_offset(0x1000); + chunk.set_uncompressed_size(0x100); + assert_eq!(chunk.uncompressed_offset(), 0x1000); + assert_eq!(chunk.uncompressed_size(), 0x100); + + chunk.set_compressed_offset(0xffffffffff); + chunk.set_compressed_size(0x1000000); + assert_eq!(chunk.compressed_offset(), 0xffffffffff); + assert_eq!(chunk.compressed_size(), 0x1000000); + + chunk.set_uncompressed_offset(0xffffffff000); + chunk.set_uncompressed_size(0x1000000); + assert_eq!(chunk.uncompressed_offset(), 0xffffffff000); + assert_eq!(chunk.uncompressed_size(), 0x1000000); + + // For testing old format compatibility. + let chunk = BlobChunkInfoV1Ondisk { + uncomp_info: 0xffff_ffff_f100_0000, + comp_info: 0xffff_f0ff_ffff_ffff, + }; + assert_eq!(chunk.uncompressed_size(), 0x000f_ffff + 1); + assert_eq!(chunk.uncompressed_offset(), 0xffff_1000 * 0x1000); + assert_eq!(chunk.compressed_size(), 0x000f_ffff + 1); + assert_eq!(chunk.compressed_offset(), 0x00ff_ffff_ffff); + } + + #[test] + fn test_get_chunk_index_with_hole() { + let state = BlobMetaState { + blob_index: 0, + compressed_size: 0, + uncompressed_size: 0, + chunk_info_array: ManuallyDrop::new(BlobMetaChunkArray::V1(vec![ + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0000_0000, + comp_info: 0x00ff_f000_0000_0000, + }, + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0010_0000, + comp_info: 0x00ff_f000_0010_0000, + }, + ])), + _filemap: FileMapState::default(), + is_stargz: false, + }; + + assert_eq!( + state + .chunk_info_array + .get_chunk_index_nocheck(0, false) + .unwrap(), + 0 + ); + assert_eq!( + state + .chunk_info_array + .get_chunk_index_nocheck(0x1fff, false) + .unwrap(), + 0 + ); + assert_eq!( + state + .chunk_info_array + .get_chunk_index_nocheck(0x100000, false) + .unwrap(), + 1 + ); + assert_eq!( + state + .chunk_info_array + .get_chunk_index_nocheck(0x101fff, false) + .unwrap(), + 1 + ); + state + .chunk_info_array + .get_chunk_index_nocheck(0x2000, false) + .unwrap_err(); + state + .chunk_info_array + .get_chunk_index_nocheck(0xfffff, false) + .unwrap_err(); + state + .chunk_info_array + .get_chunk_index_nocheck(0x102000, false) + .unwrap_err(); + } + + #[test] + fn test_get_chunks() { + let state = BlobMetaState { + blob_index: 1, + compressed_size: 0x6001, + uncompressed_size: 0x102001, + chunk_info_array: ManuallyDrop::new(BlobMetaChunkArray::V1(vec![ + BlobChunkInfoV1Ondisk { + uncomp_info: 0x0100_0000_0000_0000, + comp_info: 0x00ff_f000_0000_0000, + }, + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0000_2000, + comp_info: 0x01ff_f000_0000_1000, + }, + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0000_4000, + comp_info: 0x00ff_f000_0000_3000, + }, + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0010_0000, + comp_info: 0x00ff_f000_0000_4000, + }, + BlobChunkInfoV1Ondisk { + uncomp_info: 0x01ff_f000_0010_2000, + comp_info: 0x00ff_f000_0000_5000, + }, + ])), + _filemap: FileMapState::default(), + is_stargz: false, + }; + let info = BlobMetaInfo { + state: Arc::new(state), + }; + + let vec = info.get_chunks_uncompressed(0x0, 0x1001, 0).unwrap(); + assert_eq!(vec.len(), 1); + assert_eq!(vec[0].blob_index(), 1); + assert_eq!(vec[0].id(), 0); + assert_eq!(vec[0].compressed_offset(), 0); + assert_eq!(vec[0].compressed_size(), 0x1000); + assert_eq!(vec[0].uncompressed_offset(), 0); + assert_eq!(vec[0].uncompressed_size(), 0x1001); + assert!(vec[0].is_compressed()); + + let vec = info.get_chunks_uncompressed(0x0, 0x4000, 0).unwrap(); + assert_eq!(vec.len(), 2); + assert_eq!(vec[1].blob_index(), 1); + assert_eq!(vec[1].id(), 1); + assert_eq!(vec[1].compressed_offset(), 0x1000); + assert_eq!(vec[1].compressed_size(), 0x2000); + assert_eq!(vec[1].uncompressed_offset(), 0x2000); + assert_eq!(vec[1].uncompressed_size(), 0x2000); + assert!(!vec[1].is_compressed()); + + let vec = info.get_chunks_uncompressed(0x0, 0x4001, 0).unwrap(); + assert_eq!(vec.len(), 3); + + let vec = info.get_chunks_uncompressed(0x100000, 0x2000, 0).unwrap(); + assert_eq!(vec.len(), 1); + + assert!(info.get_chunks_uncompressed(0x0, 0x6001, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x0, 0xfffff, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x0, 0x100000, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x0, 0x104000, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x0, 0x104001, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x100000, 0x2001, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x100000, 0x4000, 0).is_err()); + assert!(info.get_chunks_uncompressed(0x100000, 0x4001, 0).is_err()); + assert!(info + .get_chunks_uncompressed(0x102000, 0xffff_ffff_ffff_ffff, 0) + .is_err()); + assert!(info.get_chunks_uncompressed(0x104000, 0x1, 0).is_err()); + } +} diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 44cc3e066d1..6ead563218b 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -995,154 +995,6 @@ mod tests { use std::os::unix::io::AsRawFd; use vmm_sys_util::tempfile::TempFile; - #[test] - fn test_get_chunk_index_with_hole() { - let state = BlobMetaState { - blob_index: 0, - compressed_size: 0, - uncompressed_size: 0, - chunk_count: 2, - chunks: ManuallyDrop::new(vec![ - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0000_0000, - comp_info: 0x00ff_f000_0000_0000, - }, - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0010_0000, - comp_info: 0x00ff_f000_0010_0000, - }, - ]), - _filemap: FileMapState::default(), - is_stargz: false, - }; - - assert_eq!(state.get_chunk_index_nocheck(0, false).unwrap(), 0); - assert_eq!(state.get_chunk_index_nocheck(0x1fff, false).unwrap(), 0); - assert_eq!(state.get_chunk_index_nocheck(0x100000, false).unwrap(), 1); - assert_eq!(state.get_chunk_index_nocheck(0x101fff, false).unwrap(), 1); - state.get_chunk_index_nocheck(0x2000, false).unwrap_err(); - state.get_chunk_index_nocheck(0xfffff, false).unwrap_err(); - state.get_chunk_index_nocheck(0x102000, false).unwrap_err(); - } - - #[test] - fn test_new_chunk_on_disk() { - let mut chunk = BlobChunkInfoV1Ondisk::default(); - - assert_eq!(chunk.compressed_offset(), 0); - assert_eq!(chunk.compressed_size(), 1); - assert_eq!(chunk.compressed_end(), 1); - assert_eq!(chunk.uncompressed_offset(), 0); - assert_eq!(chunk.uncompressed_size(), 1); - assert_eq!(chunk.aligned_uncompressed_end(), 0x1000); - - chunk.set_compressed_offset(0x1000); - chunk.set_compressed_size(0x100); - assert_eq!(chunk.compressed_offset(), 0x1000); - assert_eq!(chunk.compressed_size(), 0x100); - - chunk.set_uncompressed_offset(0x1000); - chunk.set_uncompressed_size(0x100); - assert_eq!(chunk.uncompressed_offset(), 0x1000); - assert_eq!(chunk.uncompressed_size(), 0x100); - - chunk.set_compressed_offset(0xffffffffff); - chunk.set_compressed_size(0x1000000); - assert_eq!(chunk.compressed_offset(), 0xffffffffff); - assert_eq!(chunk.compressed_size(), 0x1000000); - - chunk.set_uncompressed_offset(0xffffffff000); - chunk.set_uncompressed_size(0x1000000); - assert_eq!(chunk.uncompressed_offset(), 0xffffffff000); - assert_eq!(chunk.uncompressed_size(), 0x1000000); - - // For testing old format compatibility. - let chunk = BlobChunkInfoV1Ondisk { - uncomp_info: 0xffff_ffff_f100_0000, - comp_info: 0xffff_f0ff_ffff_ffff, - }; - assert_eq!(chunk.uncompressed_size(), 0x000f_ffff + 1); - assert_eq!(chunk.uncompressed_offset(), 0xffff_1000 * 0x1000); - assert_eq!(chunk.compressed_size(), 0x000f_ffff + 1); - assert_eq!(chunk.compressed_offset(), 0x00ff_ffff_ffff); - } - - #[test] - fn test_get_chunks() { - let state = BlobMetaState { - blob_index: 1, - compressed_size: 0x6001, - uncompressed_size: 0x102001, - chunk_count: 5, - chunks: ManuallyDrop::new(vec![ - BlobChunkInfoV1Ondisk { - uncomp_info: 0x0100_0000_0000_0000, - comp_info: 0x00ff_f000_0000_0000, - }, - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0000_2000, - comp_info: 0x01ff_f000_0000_1000, - }, - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0000_4000, - comp_info: 0x00ff_f000_0000_3000, - }, - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0010_0000, - comp_info: 0x00ff_f000_0000_4000, - }, - BlobChunkInfoV1Ondisk { - uncomp_info: 0x01ff_f000_0010_2000, - comp_info: 0x00ff_f000_0000_5000, - }, - ]), - _filemap: FileMapState::default(), - is_stargz: false, - }; - let info = BlobMetaInfo { - state: Arc::new(state), - }; - - let vec = info.get_chunks_uncompressed(0x0, 0x1001, 0).unwrap(); - assert_eq!(vec.len(), 1); - assert_eq!(vec[0].blob_index(), 1); - assert_eq!(vec[0].id(), 0); - assert_eq!(vec[0].compressed_offset(), 0); - assert_eq!(vec[0].compressed_size(), 0x1000); - assert_eq!(vec[0].uncompressed_offset(), 0); - assert_eq!(vec[0].uncompressed_size(), 0x1001); - assert!(vec[0].is_compressed()); - - let vec = info.get_chunks_uncompressed(0x0, 0x4000, 0).unwrap(); - assert_eq!(vec.len(), 2); - assert_eq!(vec[1].blob_index(), 1); - assert_eq!(vec[1].id(), 1); - assert_eq!(vec[1].compressed_offset(), 0x1000); - assert_eq!(vec[1].compressed_size(), 0x2000); - assert_eq!(vec[1].uncompressed_offset(), 0x2000); - assert_eq!(vec[1].uncompressed_size(), 0x2000); - assert!(!vec[1].is_compressed()); - - let vec = info.get_chunks_uncompressed(0x0, 0x4001, 0).unwrap(); - assert_eq!(vec.len(), 3); - - let vec = info.get_chunks_uncompressed(0x100000, 0x2000, 0).unwrap(); - assert_eq!(vec.len(), 1); - - assert!(info.get_chunks_uncompressed(0x0, 0x6001, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x0, 0xfffff, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x0, 0x100000, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x0, 0x104000, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x0, 0x104001, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x100000, 0x2001, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x100000, 0x4000, 0).is_err()); - assert!(info.get_chunks_uncompressed(0x100000, 0x4001, 0).is_err()); - assert!(info - .get_chunks_uncompressed(0x102000, 0xffff_ffff_ffff_ffff, 0) - .is_err()); - assert!(info.get_chunks_uncompressed(0x104000, 0x1, 0).is_err()); - } - #[test] fn test_round_up_4k() { assert_eq!(round_up_4k(0), 0x0u32); From 9449f648c1b48c2a2c66b96a129174a36445f0a4 Mon Sep 17 00:00:00 2001 From: Jiang Liu Date: Sun, 16 Oct 2022 22:45:32 +0800 Subject: [PATCH 7/7] storage: introduce blob meta chunk info v2 format Introduce blob meta chunk info v2 format with better encoding and a 32-bit data field. Signed-off-by: Jiang Liu --- rafs/src/metadata/chunk.rs | 8 ++ src/bin/nydus-image/builder/stargz.rs | 7 +- src/bin/nydus-image/core/blob.rs | 4 + src/bin/nydus-image/core/blob_compact.rs | 5 +- src/bin/nydus-image/core/context.rs | 51 +++++++--- src/bin/nydus-image/core/node.rs | 3 +- storage/src/meta/chunk_info_v1.rs | 4 + storage/src/meta/chunk_info_v2.rs | 109 ++++++++++++++++++++ storage/src/meta/mod.rs | 120 ++++++++++++++++++++--- 9 files changed, 279 insertions(+), 32 deletions(-) create mode 100644 storage/src/meta/chunk_info_v2.rs diff --git a/rafs/src/metadata/chunk.rs b/rafs/src/metadata/chunk.rs index e66978d4b9e..99c3ec48bda 100644 --- a/rafs/src/metadata/chunk.rs +++ b/rafs/src/metadata/chunk.rs @@ -176,6 +176,14 @@ impl ChunkWrapper { } } + /// Check whether the chunk is compressed or not. + pub fn is_compressed(&self) -> bool { + match self { + ChunkWrapper::V5(c) => c.flags.contains(BlobChunkFlags::COMPRESSED), + ChunkWrapper::V6(c) => c.flags.contains(BlobChunkFlags::COMPRESSED), + } + } + #[allow(clippy::too_many_arguments)] /// Set a group of chunk information fields. pub fn set_chunk_info( diff --git a/src/bin/nydus-image/builder/stargz.rs b/src/bin/nydus-image/builder/stargz.rs index 8aa87bd5d35..31047776321 100644 --- a/src/bin/nydus-image/builder/stargz.rs +++ b/src/bin/nydus-image/builder/stargz.rs @@ -19,7 +19,7 @@ use nydus_rafs::metadata::layout::v5::{RafsV5ChunkInfo, RafsV5Inode, RafsV5Inode use nydus_rafs::metadata::layout::RafsXAttrs; use nydus_rafs::metadata::{Inode, RafsVersion}; use nydus_storage::device::BlobChunkFlags; -use nydus_storage::meta::BlobMetaHeaderOndisk; +use nydus_storage::meta::{BlobMetaHeaderOndisk, BLOB_META_FEATURE_CHUNK_INFO_V2}; use nydus_storage::{RAFS_MAX_CHUNKS_PER_BLOB, RAFS_MAX_CHUNK_SIZE}; use nydus_utils::compact::makedev; use nydus_utils::digest::{self, Algorithm, DigestHasher, RafsDigest}; @@ -666,6 +666,7 @@ impl StargzBuilder { if ctx.fs_version == RafsVersion::V6 { let mut header = BlobMetaHeaderOndisk::default(); header.set_4k_aligned(true); + header.set_chunk_info_v2(ctx.blob_meta_features & BLOB_META_FEATURE_CHUNK_INFO_V2 != 0); blob_ctx.blob_meta_header = header; blob_ctx.set_meta_info_enabled(true); } else { @@ -715,7 +716,7 @@ impl StargzBuilder { if !chunk_map.contains_key(chunk.inner.id()) { let chunk_index = blob_ctx.alloc_chunk_index()?; chunk.inner.set_index(chunk_index); - blob_ctx.add_chunk_meta_info(&chunk.inner)?; + blob_ctx.add_chunk_meta_info(&chunk.inner, 0)?; chunk_map.insert(*chunk.inner.id(), chunk_index); } else { bail!("stargz unexpected duplicated data chunk"); @@ -801,7 +802,7 @@ impl Builder for StargzBuilder { build_bootstrap(ctx, bootstrap_mgr, &mut bootstrap_ctx, blob_mgr, tree)?; // Generate node chunks and digest - let mut blob_ctx = BlobContext::new(ctx.blob_id.clone(), 0); + let mut blob_ctx = BlobContext::new(ctx.blob_id.clone(), 0, ctx.blob_meta_features); self.generate_nodes(ctx, &mut bootstrap_ctx, &mut blob_ctx, blob_mgr)?; // Dump blob meta diff --git a/src/bin/nydus-image/core/blob.rs b/src/bin/nydus-image/core/blob.rs index 3644c19ecb5..386440e7318 100644 --- a/src/bin/nydus-image/core/blob.rs +++ b/src/bin/nydus-image/core/blob.rs @@ -93,6 +93,10 @@ impl Blob { header.set_ci_compressed_size(buf.len() as u64); header.set_ci_uncompressed_size(data.len() as u64); header.set_4k_aligned(true); + match blob_meta_info { + BlobMetaChunkArray::V1(_) => header.set_chunk_info_v2(false), + BlobMetaChunkArray::V2(_) => header.set_chunk_info_v2(true), + } Ok((buf, header)) } diff --git a/src/bin/nydus-image/core/blob_compact.rs b/src/bin/nydus-image/core/blob_compact.rs index 5880770abd6..c68c04d879b 100644 --- a/src/bin/nydus-image/core/blob_compact.rs +++ b/src/bin/nydus-image/core/blob_compact.rs @@ -164,7 +164,7 @@ impl ChunkSet { new_chunk.set_blob_index(new_blob_idx); new_chunk.set_compressed_offset(new_blob_ctx.compressed_offset); new_chunk.set_uncompressed_offset(new_blob_ctx.uncompressed_offset); - new_blob_ctx.add_chunk_meta_info(&new_chunk)?; + new_blob_ctx.add_chunk_meta_info(&new_chunk, 0)?; // insert change ops chunks_change.push((chunk.clone(), new_chunk)); @@ -520,7 +520,8 @@ impl BlobCompactor { } State::Rebuild(cs) => { let blob_storage = ArtifactStorage::FileDir(PathBuf::from(dir)); - let mut blob_ctx = BlobContext::new(String::from(""), 0); + let mut blob_ctx = + BlobContext::new(String::from(""), 0, build_ctx.blob_meta_features); blob_ctx.set_meta_info_enabled(self.is_v6()); let blob_idx = self.new_blob_mgr.alloc_index()?; let new_chunks = cs.dump( diff --git a/src/bin/nydus-image/core/context.rs b/src/bin/nydus-image/core/context.rs index 67e3af227a5..032407213bf 100644 --- a/src/bin/nydus-image/core/context.rs +++ b/src/bin/nydus-image/core/context.rs @@ -28,7 +28,9 @@ use nydus_rafs::metadata::{Inode, RAFS_DEFAULT_CHUNK_SIZE}; use nydus_rafs::metadata::{RafsSuperFlags, RafsVersion}; use nydus_rafs::{RafsIoReader, RafsIoWrite}; use nydus_storage::device::{BlobFeatures, BlobInfo}; -use nydus_storage::meta::{BlobMetaChunkArray, BlobMetaHeaderOndisk}; +use nydus_storage::meta::{ + BlobMetaChunkArray, BlobMetaHeaderOndisk, BLOB_META_FEATURE_CHUNK_INFO_V2, +}; use nydus_utils::{compress, digest, div_round_up, round_down_4k}; use super::chunk_dict::{ChunkDict, HashChunkDict}; @@ -351,8 +353,12 @@ pub struct BlobContext { } impl BlobContext { - pub fn new(blob_id: String, blob_offset: u64) -> Self { - let blob_meta_info = BlobMetaChunkArray::new_v1(); + pub fn new(blob_id: String, blob_offset: u64, features: u32) -> Self { + let blob_meta_info = if features & BLOB_META_FEATURE_CHUNK_INFO_V2 != 0 { + BlobMetaChunkArray::new_v2() + } else { + BlobMetaChunkArray::new_v1() + }; Self { blob_id, @@ -375,7 +381,7 @@ impl BlobContext { } pub fn from(ctx: &BuildContext, blob: &BlobInfo, chunk_source: ChunkSource) -> Self { - let mut blob_ctx = Self::new(blob.blob_id().to_owned(), 0); + let mut blob_ctx = Self::new(blob.blob_id().to_owned(), 0, blob.meta_flags()); blob_ctx.blob_prefetch_size = blob.prefetch_size(); blob_ctx.chunk_count = blob.chunk_count(); @@ -400,6 +406,9 @@ impl BlobContext { .blob_meta_header .set_ci_uncompressed_size(blob.meta_ci_uncompressed_size()); blob_ctx.blob_meta_header.set_4k_aligned(true); + blob_ctx + .blob_meta_header + .set_chunk_info_v2(blob.meta_flags() & BLOB_META_FEATURE_CHUNK_INFO_V2 != 0); blob_ctx.blob_meta_info_enabled = true; } @@ -425,15 +434,29 @@ impl BlobContext { self.blob_meta_info_enabled = enable; } - pub fn add_chunk_meta_info(&mut self, chunk: &ChunkWrapper) -> Result<()> { + pub fn add_chunk_meta_info(&mut self, chunk: &ChunkWrapper, data: u64) -> Result<()> { if self.blob_meta_info_enabled { assert_eq!(chunk.index() as usize, self.blob_meta_info.len()); - self.blob_meta_info.add_v1( - chunk.compressed_offset(), - chunk.compressed_size(), - chunk.uncompressed_offset(), - chunk.uncompressed_size(), - ); + match &self.blob_meta_info { + BlobMetaChunkArray::V1(_) => { + self.blob_meta_info.add_v1( + chunk.compressed_offset(), + chunk.compressed_size(), + chunk.uncompressed_offset(), + chunk.uncompressed_size(), + ); + } + BlobMetaChunkArray::V2(_) => { + self.blob_meta_info.add_v2( + chunk.compressed_offset(), + chunk.compressed_size(), + chunk.uncompressed_offset(), + chunk.uncompressed_size(), + chunk.is_compressed(), + data, + ); + } + } } Ok(()) @@ -492,7 +515,8 @@ impl BlobManager { } fn new_blob_ctx(ctx: &BuildContext) -> Result { - let mut blob_ctx = BlobContext::new(ctx.blob_id.clone(), ctx.blob_offset); + let mut blob_ctx = + BlobContext::new(ctx.blob_id.clone(), ctx.blob_offset, ctx.blob_meta_features); blob_ctx.set_chunk_size(ctx.chunk_size); blob_ctx.set_meta_info_enabled(ctx.fs_version == RafsVersion::V6); @@ -802,6 +826,7 @@ pub struct BuildContext { /// Storage writing blob to single file or a directory. pub blob_storage: Option, pub blob_meta_storage: Option, + pub blob_meta_features: u32, pub inline_bootstrap: bool, pub has_xattr: bool, } @@ -841,6 +866,7 @@ impl BuildContext { prefetch, blob_storage, blob_meta_storage, + blob_meta_features: 0, inline_bootstrap, has_xattr: false, } @@ -875,6 +901,7 @@ impl Default for BuildContext { prefetch: Prefetch::default(), blob_storage: None, blob_meta_storage: None, + blob_meta_features: 0, has_xattr: true, inline_bootstrap: false, } diff --git a/src/bin/nydus-image/core/node.rs b/src/bin/nydus-image/core/node.rs index bf0c0bddc73..be6844b9cb7 100644 --- a/src/bin/nydus-image/core/node.rs +++ b/src/bin/nydus-image/core/node.rs @@ -514,7 +514,8 @@ impl Node { is_compressed, )?; - blob_ctx.add_chunk_meta_info(&chunk)?; + // TODO: figure correct value for data + blob_ctx.add_chunk_meta_info(&chunk, 0)?; blob_mgr.layered_chunk_dict.add_chunk(chunk.clone()); self.chunks.push(NodeChunk { source: ChunkSource::Build, diff --git a/storage/src/meta/chunk_info_v1.rs b/storage/src/meta/chunk_info_v1.rs index c3ed3f25c68..d001007211d 100644 --- a/storage/src/meta/chunk_info_v1.rs +++ b/storage/src/meta/chunk_info_v1.rs @@ -84,6 +84,10 @@ impl BlobMetaChunkInfo for BlobChunkInfoV1Ondisk { fn is_compressed(&self) -> bool { self.compressed_size() != self.uncompressed_size() } + + fn get_data(&self) -> u64 { + 0 + } } #[cfg(test)] diff --git a/storage/src/meta/chunk_info_v2.rs b/storage/src/meta/chunk_info_v2.rs new file mode 100644 index 00000000000..b9f55eaed0b --- /dev/null +++ b/storage/src/meta/chunk_info_v2.rs @@ -0,0 +1,109 @@ +// Copyright (C) 2021-2022 Alibaba Cloud. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::{Display, Formatter}; + +use crate::meta::{BlobMetaChunkInfo, BLOB_METADATA_CHUNK_SIZE_MASK}; + +const CHUNK_V2_COMP_OFFSET_MASK: u64 = 0xff_ffff_ffff; +const CHUNK_V2_COMP_SIZE_SHIFT: u64 = 40; +const CHUNK_V2_UNCOMP_OFFSET_MASK: u64 = 0xffff_ffff; +const CHUNK_V2_UNCOMP_OFFSET_SHIFT: u64 = 12; +const CHUNK_V2_UNCOMP_SIZE_SHIFT: u64 = 32; +//const CHUNK_V2_FLAG_MASK: u64 = 0xff00_0000_0000_0000; +const CHUNK_V2_FLAG_COMPRESSED: u64 = 0x1 << 56; + +/// Blob chunk compression information on disk format. +#[repr(C, packed)] +#[derive(Clone, Copy, Default)] +pub struct BlobChunkInfoV2Ondisk { + // 32bits: offset, 24bits: size, 8bits: flags + pub(crate) uncomp_info: u64, + // offset: 40bits, 24bits: size + pub(crate) comp_info: u64, + // attached misc data + pub(crate) data: u64, +} + +impl BlobChunkInfoV2Ondisk { + pub(crate) fn set_compressed(&mut self, compressed: bool) { + if compressed { + self.uncomp_info |= CHUNK_V2_FLAG_COMPRESSED; + } else { + self.uncomp_info &= !CHUNK_V2_FLAG_COMPRESSED; + } + } + + pub(crate) fn set_data(&mut self, data: u64) { + self.data = data; + } +} + +impl BlobMetaChunkInfo for BlobChunkInfoV2Ondisk { + fn compressed_offset(&self) -> u64 { + self.comp_info & CHUNK_V2_COMP_OFFSET_MASK + } + + fn set_compressed_offset(&mut self, offset: u64) { + assert_eq!(offset & !CHUNK_V2_COMP_OFFSET_MASK, 0); + self.comp_info &= !CHUNK_V2_COMP_OFFSET_MASK; + self.comp_info |= offset & CHUNK_V2_COMP_OFFSET_MASK; + } + + fn compressed_size(&self) -> u32 { + ((self.comp_info >> CHUNK_V2_COMP_SIZE_SHIFT) & BLOB_METADATA_CHUNK_SIZE_MASK) as u32 + 1 + } + + fn set_compressed_size(&mut self, size: u32) { + let size = size as u64; + assert!(size > 0 && size - 1 <= BLOB_METADATA_CHUNK_SIZE_MASK); + self.comp_info &= !(BLOB_METADATA_CHUNK_SIZE_MASK << CHUNK_V2_COMP_SIZE_SHIFT); + self.comp_info |= (size - 1) << CHUNK_V2_COMP_SIZE_SHIFT; + } + + fn uncompressed_offset(&self) -> u64 { + (self.uncomp_info & CHUNK_V2_UNCOMP_OFFSET_MASK) << CHUNK_V2_UNCOMP_OFFSET_SHIFT + } + + fn set_uncompressed_offset(&mut self, offset: u64) { + let off = (offset >> CHUNK_V2_UNCOMP_OFFSET_SHIFT) & CHUNK_V2_UNCOMP_OFFSET_MASK; + assert_eq!(offset, off << CHUNK_V2_UNCOMP_OFFSET_SHIFT); + self.uncomp_info &= !CHUNK_V2_UNCOMP_OFFSET_MASK; + self.uncomp_info |= off; + } + + fn uncompressed_size(&self) -> u32 { + let size = self.uncomp_info >> CHUNK_V2_UNCOMP_SIZE_SHIFT; + (size & BLOB_METADATA_CHUNK_SIZE_MASK) as u32 + 1 + } + + fn set_uncompressed_size(&mut self, size: u32) { + let size = size as u64; + assert!(size != 0 && size - 1 <= BLOB_METADATA_CHUNK_SIZE_MASK); + self.uncomp_info &= !(BLOB_METADATA_CHUNK_SIZE_MASK << CHUNK_V2_UNCOMP_SIZE_SHIFT); + self.uncomp_info |= (size - 1) << CHUNK_V2_UNCOMP_SIZE_SHIFT; + } + + fn is_compressed(&self) -> bool { + self.uncomp_info & CHUNK_V2_FLAG_COMPRESSED != 0 + } + + fn get_data(&self) -> u64 { + self.data + } +} + +impl Display for BlobChunkInfoV2Ondisk { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{{ comp:{:x}/{:x}, uncomp:{:x}/{:x} data:{:x} }}", + self.compressed_offset(), + self.compressed_size(), + self.uncompressed_offset(), + self.uncompressed_size(), + self.get_data() + ) + } +} diff --git a/storage/src/meta/mod.rs b/storage/src/meta/mod.rs index 6ead563218b..9775ae2c274 100644 --- a/storage/src/meta/mod.rs +++ b/storage/src/meta/mod.rs @@ -32,6 +32,8 @@ use crate::{RAFS_MAX_CHUNKS_PER_BLOB, RAFS_MAX_CHUNK_SIZE}; mod chunk_info_v1; use chunk_info_v1::BlobChunkInfoV1Ondisk; +mod chunk_info_v2; +use chunk_info_v2::BlobChunkInfoV2Ondisk; const BLOB_METADATA_MAGIC: u32 = 0xb10bb10bu32; const BLOB_METADATA_HEADER_SIZE: u64 = 0x1000u64; @@ -40,10 +42,14 @@ const BLOB_METADATA_CHUNK_SIZE_MASK: u64 = 0xff_ffff; const BLOB_METADATA_V1_MAX_SIZE: u64 = RAFS_MAX_CHUNK_SIZE * 16; const BLOB_METADATA_V1_RESERVED_SIZE: u64 = BLOB_METADATA_HEADER_SIZE - 44; +const BLOB_METADATA_V2_MAX_SIZE: u64 = RAFS_MAX_CHUNK_SIZE * 24; + /// File suffix for blob meta file. pub const FILE_SUFFIX: &str = "blob.meta"; /// Uncompressed chunk data is 4K aligned. pub const BLOB_META_FEATURE_4K_ALIGNED: u32 = 0x1; +/// Blob chunk information format v2. +pub const BLOB_META_FEATURE_CHUNK_INFO_V2: u32 = 0x2; /// Blob metadata on disk format. #[repr(C)] @@ -156,6 +162,15 @@ impl BlobMetaHeaderOndisk { } } + /// Set whether the uncompressed data chunk is 4k aligned. + pub fn set_chunk_info_v2(&mut self, enable: bool) { + if enable { + self.s_features |= BLOB_META_FEATURE_CHUNK_INFO_V2; + } else { + self.s_features &= !BLOB_META_FEATURE_CHUNK_INFO_V2; + } + } + pub fn meta_flags(&self) -> u32 { self.s_features } @@ -199,6 +214,7 @@ impl BlobMetaInfo { BLOB_METADATA_HEADER_SIZE ); assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); let chunk_count = blob_info.chunk_count(); if chunk_count == 0 || chunk_count > RAFS_MAX_CHUNKS_PER_BLOB { return Err(einval!("chunk count should be greater than 0")); @@ -433,7 +449,13 @@ impl BlobMetaInfo { let chunk_count = blob_info.chunk_count(); let info_size = u64::from_le(header.s_ci_uncompressed_size) as usize; let aligned_info_size = round_up_4k(info_size); - if info_size != (chunk_count as usize) * (size_of::()) + if blob_info.meta_flags() & BLOB_META_FEATURE_CHUNK_INFO_V2 != 0 { + if info_size != (chunk_count as usize) * (size_of::()) + || (aligned_info_size as u64) > BLOB_METADATA_V2_MAX_SIZE + { + return Err(einval!("blob metadata size is too big!")); + } + } else if info_size != (chunk_count as usize) * (size_of::()) || (aligned_info_size as u64) > BLOB_METADATA_V1_MAX_SIZE { return Err(einval!("blob metadata size is too big!")); @@ -508,21 +530,29 @@ impl BlobMetaState { /// A customized array to generate chunk information array. pub enum BlobMetaChunkArray { - /// Chunk information V1 array. + /// V1 chunk information array. V1(Vec), + /// V2 chunk information array. + V2(Vec), } // Methods for RAFS filesystem builder. impl BlobMetaChunkArray { - /// Create a `BlokMetaChunkArray` for v1 chunk information format. + /// Create a `BlokMetaChunkArray` with v2 chunk information format. pub fn new_v1() -> Self { BlobMetaChunkArray::V1(Vec::new()) } + /// Create a `BlokMetaChunkArray` with v2 chunk information format. + pub fn new_v2() -> Self { + BlobMetaChunkArray::V2(Vec::new()) + } + /// Get number of entry in the blob chunk information array. pub fn len(&self) -> usize { match self { BlobMetaChunkArray::V1(v) => v.len(), + BlobMetaChunkArray::V2(v) => v.len(), } } @@ -530,6 +560,7 @@ impl BlobMetaChunkArray { pub fn is_empty(&self) -> bool { match self { BlobMetaChunkArray::V1(v) => v.is_empty(), + BlobMetaChunkArray::V2(v) => v.is_empty(), } } @@ -539,7 +570,13 @@ impl BlobMetaChunkArray { BlobMetaChunkArray::V1(v) => unsafe { std::slice::from_raw_parts( v.as_ptr() as *const u8, - v.len() * std::mem::size_of::(), + v.len() * size_of::(), + ) + }, + BlobMetaChunkArray::V2(v) => unsafe { + std::slice::from_raw_parts( + v.as_ptr() as *const u8, + v.len() * size_of::(), ) }, } @@ -562,6 +599,32 @@ impl BlobMetaChunkArray { meta.set_uncompressed_size(uncompressed_size); v.push(meta); } + BlobMetaChunkArray::V2(_v) => unimplemented!(), + } + } + + /// Add an v2 chunk information entry. + pub fn add_v2( + &mut self, + compressed_offset: u64, + compressed_size: u32, + uncompressed_offset: u64, + uncompressed_size: u32, + compressed: bool, + data: u64, + ) { + match self { + BlobMetaChunkArray::V2(v) => { + let mut meta = BlobChunkInfoV2Ondisk::default(); + meta.set_compressed_offset(compressed_offset); + meta.set_compressed_size(compressed_size); + meta.set_uncompressed_offset(uncompressed_offset); + meta.set_uncompressed_size(uncompressed_size); + meta.set_compressed(compressed); + meta.set_data(data); + v.push(meta); + } + BlobMetaChunkArray::V1(_v) => unimplemented!(), } } } @@ -569,22 +632,36 @@ impl BlobMetaChunkArray { impl BlobMetaChunkArray { fn from_file_map(filemap: &FileMapState, blob_info: &BlobInfo) -> Result { let chunk_count = blob_info.chunk_count(); - let chunk_size = chunk_count as usize * size_of::(); - let base = filemap.validate_range(0, chunk_size)?; - let v = unsafe { - Vec::from_raw_parts( - base as *mut u8 as *mut BlobChunkInfoV1Ondisk, - chunk_count as usize, - chunk_count as usize, - ) - }; - Ok(BlobMetaChunkArray::V1(v)) + if blob_info.meta_flags() & BLOB_META_FEATURE_CHUNK_INFO_V2 != 0 { + let chunk_size = chunk_count as usize * size_of::(); + let base = filemap.validate_range(0, chunk_size)?; + let v = unsafe { + Vec::from_raw_parts( + base as *mut u8 as *mut BlobChunkInfoV2Ondisk, + chunk_count as usize, + chunk_count as usize, + ) + }; + Ok(BlobMetaChunkArray::V2(v)) + } else { + let chunk_size = chunk_count as usize * size_of::(); + let base = filemap.validate_range(0, chunk_size)?; + let v = unsafe { + Vec::from_raw_parts( + base as *mut u8 as *mut BlobChunkInfoV1Ondisk, + chunk_count as usize, + chunk_count as usize, + ) + }; + Ok(BlobMetaChunkArray::V1(v)) + } } #[cfg(test)] fn get_chunk_index_nocheck(&self, addr: u64, compressed: bool) -> Result { match self { BlobMetaChunkArray::V1(v) => Self::_get_chunk_index_nocheck(v, addr, compressed), + BlobMetaChunkArray::V2(v) => Self::_get_chunk_index_nocheck(v, addr, compressed), } } @@ -599,6 +676,9 @@ impl BlobMetaChunkArray { BlobMetaChunkArray::V1(v) => { Self::_get_chunks_compressed(state, v, start, end, batch_end) } + BlobMetaChunkArray::V2(v) => { + Self::_get_chunks_compressed(state, v, start, end, batch_end) + } } } @@ -610,6 +690,7 @@ impl BlobMetaChunkArray { ) -> Option>> { match self { BlobMetaChunkArray::V1(v) => Self::_add_more_chunks(state, v, chunks, max_size), + BlobMetaChunkArray::V2(v) => Self::_add_more_chunks(state, v, chunks, max_size), } } @@ -624,36 +705,44 @@ impl BlobMetaChunkArray { BlobMetaChunkArray::V1(v) => { Self::_get_chunks_uncompressed(state, v, start, end, batch_end) } + BlobMetaChunkArray::V2(v) => { + Self::_get_chunks_uncompressed(state, v, start, end, batch_end) + } } } fn compressed_offset(&self, index: usize) -> u64 { match self { BlobMetaChunkArray::V1(v) => v[index].compressed_offset(), + BlobMetaChunkArray::V2(v) => v[index].compressed_offset(), } } fn compressed_size(&self, index: usize) -> u32 { match self { BlobMetaChunkArray::V1(v) => v[index].compressed_size(), + BlobMetaChunkArray::V2(v) => v[index].compressed_size(), } } fn uncompressed_offset(&self, index: usize) -> u64 { match self { BlobMetaChunkArray::V1(v) => v[index].uncompressed_offset(), + BlobMetaChunkArray::V2(v) => v[index].uncompressed_offset(), } } fn uncompressed_size(&self, index: usize) -> u32 { match self { BlobMetaChunkArray::V1(v) => v[index].uncompressed_size(), + BlobMetaChunkArray::V2(v) => v[index].uncompressed_size(), } } fn is_compressed(&self, index: usize) -> bool { match self { BlobMetaChunkArray::V1(v) => v[index].is_compressed(), + BlobMetaChunkArray::V2(v) => v[index].is_compressed(), } } @@ -976,6 +1065,9 @@ pub trait BlobMetaChunkInfo { /// Assume the image builder guarantee that compress_size < uncompress_size if the chunk is /// compressed. fn is_compressed(&self) -> bool; + + /// Get misc data associated with the entry. V2 only, V1 just returns zero. + fn get_data(&self) -> u64; } fn round_up_4k + BitAnd + Not + From>(val: T) -> T {