1use std::any::Any;
28use std::borrow::Cow;
29use std::fs::OpenOptions;
30use std::io::Result;
31use std::mem::{size_of, ManuallyDrop};
32use std::ops::{Add, BitAnd, Not};
33use std::path::PathBuf;
34use std::sync::Arc;
35
36use nydus_utils::compress::zlib_random::ZranContext;
37use nydus_utils::crypt::decrypt_with_context;
38use nydus_utils::digest::{DigestData, RafsDigest};
39use nydus_utils::filemap::FileMapState;
40use nydus_utils::{compress, crypt};
41
42use crate::backend::BlobReader;
43use crate::device::v5::BlobV5ChunkInfo;
44use crate::device::{BlobChunkFlags, BlobChunkInfo, BlobFeatures, BlobInfo};
45use crate::meta::toc::{TocEntryList, TocLocation};
46use crate::utils::alloc_buf;
47use crate::{RAFS_MAX_CHUNKS_PER_BLOB, RAFS_MAX_CHUNK_SIZE};
48
49mod chunk_info_v1;
50pub use chunk_info_v1::BlobChunkInfoV1Ondisk;
51mod chunk_info_v2;
52pub use chunk_info_v2::BlobChunkInfoV2Ondisk;
53
54pub mod toc;
55
56mod zran;
57pub use zran::{ZranContextGenerator, ZranInflateContext};
58
59mod batch;
60pub use batch::{BatchContextGenerator, BatchInflateContext};
61
62const BLOB_CCT_MAGIC: u32 = 0xb10bb10bu32;
63const BLOB_CCT_HEADER_SIZE: u64 = 0x1000u64;
64const BLOB_CCT_CHUNK_SIZE_MASK: u64 = 0xff_ffff;
65
66const BLOB_CCT_V1_MAX_SIZE: u64 = RAFS_MAX_CHUNK_SIZE * 16;
67const BLOB_CCT_V2_MAX_SIZE: u64 = RAFS_MAX_CHUNK_SIZE * 24;
68const BLOB_CCT_V2_RESERVED_SIZE: u64 = BLOB_CCT_HEADER_SIZE - 64;
70
71const BLOB_CCT_FILE_SUFFIX: &str = "blob.meta";
73const BLOB_DIGEST_FILE_SUFFIX: &str = "blob.digest";
75const BLOB_TOC_FILE_SUFFIX: &str = "blob.toc";
77
78#[repr(C)]
89#[derive(Clone, Copy, Debug)]
90pub struct BlobCompressionContextHeader {
91 s_magic: u32,
93 s_features: u32,
95 s_ci_compressor: u32,
97 s_ci_entries: u32,
99 s_ci_offset: u64,
101 s_ci_compressed_size: u64,
103 s_ci_uncompressed_size: u64,
105 s_ci_zran_offset: u64,
107 s_ci_zran_size: u64,
109 s_ci_zran_count: u32,
111
112 s_reserved: [u8; BLOB_CCT_V2_RESERVED_SIZE as usize],
113 s_magic2: u32,
115}
116
117impl Default for BlobCompressionContextHeader {
118 fn default() -> Self {
119 BlobCompressionContextHeader {
120 s_magic: BLOB_CCT_MAGIC,
121 s_features: 0,
122 s_ci_compressor: compress::Algorithm::Lz4Block as u32,
123 s_ci_entries: 0,
124 s_ci_offset: 0,
125 s_ci_compressed_size: 0,
126 s_ci_uncompressed_size: 0,
127 s_ci_zran_offset: 0,
128 s_ci_zran_size: 0,
129 s_ci_zran_count: 0,
130 s_reserved: [0u8; BLOB_CCT_V2_RESERVED_SIZE as usize],
131 s_magic2: BLOB_CCT_MAGIC,
132 }
133 }
134}
135
136impl BlobCompressionContextHeader {
137 pub fn has_feature(&self, feature: BlobFeatures) -> bool {
139 self.s_features & feature.bits() != 0
140 }
141
142 pub fn ci_compressor(&self) -> compress::Algorithm {
144 if self.s_ci_compressor == compress::Algorithm::Lz4Block as u32 {
145 compress::Algorithm::Lz4Block
146 } else if self.s_ci_compressor == compress::Algorithm::GZip as u32 {
147 compress::Algorithm::GZip
148 } else if self.s_ci_compressor == compress::Algorithm::Zstd as u32 {
149 compress::Algorithm::Zstd
150 } else {
151 compress::Algorithm::None
152 }
153 }
154
155 pub fn set_ci_compressor(&mut self, algo: compress::Algorithm) {
157 self.s_ci_compressor = algo as u32;
158 }
159
160 pub fn ci_entries(&self) -> u32 {
162 self.s_ci_entries
163 }
164
165 pub fn set_ci_entries(&mut self, entries: u32) {
167 self.s_ci_entries = entries;
168 }
169
170 pub fn ci_compressed_offset(&self) -> u64 {
172 self.s_ci_offset
173 }
174
175 pub fn set_ci_compressed_offset(&mut self, offset: u64) {
177 self.s_ci_offset = offset;
178 }
179
180 pub fn ci_compressed_size(&self) -> u64 {
182 self.s_ci_compressed_size
183 }
184
185 pub fn set_ci_compressed_size(&mut self, size: u64) {
187 self.s_ci_compressed_size = size;
188 }
189
190 pub fn ci_uncompressed_size(&self) -> u64 {
192 self.s_ci_uncompressed_size
193 }
194
195 pub fn set_ci_uncompressed_size(&mut self, size: u64) {
197 self.s_ci_uncompressed_size = size;
198 }
199
200 pub fn ci_zran_count(&self) -> u32 {
202 self.s_ci_zran_count
203 }
204
205 pub fn set_ci_zran_count(&mut self, count: u32) {
207 self.s_ci_zran_count = count;
208 }
209
210 pub fn ci_zran_offset(&self) -> u64 {
212 self.s_ci_zran_offset
213 }
214
215 pub fn set_ci_zran_offset(&mut self, offset: u64) {
217 self.s_ci_zran_offset = offset;
218 }
219
220 pub fn ci_zran_size(&self) -> u64 {
222 self.s_ci_zran_size
223 }
224
225 pub fn set_ci_zran_size(&mut self, size: u64) {
227 self.s_ci_zran_size = size;
228 }
229
230 pub fn is_4k_aligned(&self) -> bool {
232 self.has_feature(BlobFeatures::ALIGNED)
233 }
234
235 pub fn set_aligned(&mut self, aligned: bool) {
237 if aligned {
238 self.s_features |= BlobFeatures::ALIGNED.bits();
239 } else {
240 self.s_features &= !BlobFeatures::ALIGNED.bits();
241 }
242 }
243
244 pub fn set_inlined_fs_meta(&mut self, inlined: bool) {
246 if inlined {
247 self.s_features |= BlobFeatures::INLINED_FS_META.bits();
248 } else {
249 self.s_features &= !BlobFeatures::INLINED_FS_META.bits();
250 }
251 }
252
253 pub fn set_chunk_info_v2(&mut self, enable: bool) {
255 if enable {
256 self.s_features |= BlobFeatures::CHUNK_INFO_V2.bits();
257 } else {
258 self.s_features &= !BlobFeatures::CHUNK_INFO_V2.bits();
259 }
260 }
261
262 pub fn set_ci_zran(&mut self, enable: bool) {
264 if enable {
265 self.s_features |= BlobFeatures::ZRAN.bits();
266 } else {
267 self.s_features &= !BlobFeatures::ZRAN.bits();
268 }
269 }
270
271 pub fn set_separate_blob(&mut self, enable: bool) {
273 if enable {
274 self.s_features |= BlobFeatures::SEPARATE.bits();
275 } else {
276 self.s_features &= !BlobFeatures::SEPARATE.bits();
277 }
278 }
279
280 pub fn set_ci_batch(&mut self, enable: bool) {
282 if enable {
283 self.s_features |= BlobFeatures::BATCH.bits();
284 } else {
285 self.s_features &= !BlobFeatures::BATCH.bits();
286 }
287 }
288
289 pub fn set_inlined_chunk_digest(&mut self, enable: bool) {
291 if enable {
292 self.s_features |= BlobFeatures::INLINED_CHUNK_DIGEST.bits();
293 } else {
294 self.s_features &= !BlobFeatures::INLINED_CHUNK_DIGEST.bits();
295 }
296 }
297
298 pub fn set_has_tar_header(&mut self, enable: bool) {
300 if enable {
301 self.s_features |= BlobFeatures::HAS_TAR_HEADER.bits();
302 } else {
303 self.s_features &= !BlobFeatures::HAS_TAR_HEADER.bits();
304 }
305 }
306
307 pub fn set_has_toc(&mut self, enable: bool) {
309 if enable {
310 self.s_features |= BlobFeatures::HAS_TOC.bits();
311 } else {
312 self.s_features &= !BlobFeatures::HAS_TOC.bits();
313 }
314 }
315
316 pub fn set_cap_tar_toc(&mut self, enable: bool) {
318 if enable {
319 self.s_features |= BlobFeatures::CAP_TAR_TOC.bits();
320 } else {
321 self.s_features &= !BlobFeatures::CAP_TAR_TOC.bits();
322 }
323 }
324
325 pub fn set_tarfs(&mut self, enable: bool) {
327 if enable {
328 self.s_features |= BlobFeatures::TARFS.bits();
329 } else {
330 self.s_features &= !BlobFeatures::TARFS.bits();
331 }
332 }
333
334 pub fn set_encrypted(&mut self, enable: bool) {
336 if enable {
337 self.s_features |= BlobFeatures::ENCRYPTED.bits();
338 } else {
339 self.s_features &= !BlobFeatures::ENCRYPTED.bits();
340 }
341 }
342
343 pub fn set_external(&mut self, external: bool) {
345 if external {
346 self.s_features |= BlobFeatures::EXTERNAL.bits();
347 } else {
348 self.s_features &= !BlobFeatures::EXTERNAL.bits();
349 }
350 }
351
352 pub fn features(&self) -> u32 {
354 self.s_features
355 }
356
357 pub fn as_bytes(&self) -> &[u8] {
359 unsafe {
360 std::slice::from_raw_parts(
361 self as *const BlobCompressionContextHeader as *const u8,
362 size_of::<BlobCompressionContextHeader>(),
363 )
364 }
365 }
366
367 pub fn set_is_chunkdict_generated(&mut self, enable: bool) {
369 if enable {
370 self.s_features |= BlobFeatures::IS_CHUNKDICT_GENERATED.bits();
371 } else {
372 self.s_features &= !BlobFeatures::IS_CHUNKDICT_GENERATED.bits();
373 }
374 }
375}
376
377#[derive(Clone)]
382pub struct BlobCompressionContextInfo {
383 pub(crate) state: Arc<BlobCompressionContext>,
384}
385
386impl BlobCompressionContextInfo {
387 pub fn new(
396 blob_path: &str,
397 blob_info: &BlobInfo,
398 reader: Option<&Arc<dyn BlobReader>>,
399 load_chunk_digest: bool,
400 ) -> Result<Self> {
401 assert_eq!(
402 size_of::<BlobCompressionContextHeader>() as u64,
403 BLOB_CCT_HEADER_SIZE
404 );
405 assert_eq!(size_of::<BlobChunkInfoV1Ondisk>(), 16);
406 assert_eq!(size_of::<BlobChunkInfoV2Ondisk>(), 24);
407 assert_eq!(size_of::<ZranInflateContext>(), 40);
408
409 let chunk_count = blob_info.chunk_count();
410 if chunk_count == 0 || chunk_count > RAFS_MAX_CHUNKS_PER_BLOB {
411 return Err(einval!("invalid chunk count in blob meta header"));
412 }
413
414 let uncompressed_size = blob_info.meta_ci_uncompressed_size() as usize;
415 let meta_path = format!("{}.{}", blob_path, BLOB_CCT_FILE_SUFFIX);
416 trace!(
417 "try to open blob meta file: path {:?} uncompressed_size {} chunk_count {}",
418 meta_path,
419 uncompressed_size,
420 chunk_count
421 );
422 let enable_write = reader.is_some();
423 let file = OpenOptions::new()
424 .read(true)
425 .write(enable_write)
426 .create(enable_write)
427 .open(&meta_path)
428 .map_err(|err| {
429 einval!(format!(
430 "failed to open/create blob meta file {}: {}",
431 meta_path, err
432 ))
433 })?;
434
435 let aligned_uncompressed_size = round_up_4k(uncompressed_size);
436 let expected_size = BLOB_CCT_HEADER_SIZE as usize + aligned_uncompressed_size;
437 let mut file_size = file.metadata()?.len();
438 if file_size == 0 && enable_write {
439 file.set_len(expected_size as u64)?;
440 file_size = expected_size as u64;
441 }
442 if file_size != expected_size as u64 {
443 return Err(einval!(format!(
444 "size of blob meta file '{}' doesn't match, expect {:x}, got {:x}",
445 meta_path, expected_size, file_size
446 )));
447 }
448
449 let mut filemap = FileMapState::new(file, 0, expected_size, enable_write)?;
450 let base = filemap.validate_range(0, expected_size)?;
451 let header =
452 filemap.get_mut::<BlobCompressionContextHeader>(aligned_uncompressed_size as usize)?;
453 if !Self::validate_header(blob_info, header)? {
454 if let Some(reader) = reader {
455 let buffer =
456 unsafe { std::slice::from_raw_parts_mut(base as *mut u8, expected_size) };
457 Self::read_metadata(blob_info, reader, buffer)?;
458 if !Self::validate_header(blob_info, header)? {
459 return Err(enoent!(format!("double check blob_info still invalid",)));
460 }
461 filemap.sync_data()?;
462 } else {
463 return Err(enoent!(format!(
464 "blob meta header from file '{}' is invalid",
465 meta_path
466 )));
467 }
468 }
469
470 let chunk_infos = BlobMetaChunkArray::from_file_map(&filemap, blob_info)?;
471 let chunk_infos = ManuallyDrop::new(chunk_infos);
472 let mut state = BlobCompressionContext {
473 blob_index: blob_info.blob_index(),
474 blob_features: blob_info.features().bits(),
475 compressed_size: blob_info.compressed_data_size(),
476 uncompressed_size: round_up_4k(blob_info.uncompressed_size()),
477 chunk_info_array: chunk_infos,
478 blob_meta_file_map: filemap,
479 ..Default::default()
480 };
481
482 if blob_info.has_feature(BlobFeatures::BATCH) {
483 let header = state
484 .blob_meta_file_map
485 .get_mut::<BlobCompressionContextHeader>(aligned_uncompressed_size as usize)?;
486 let inflate_offset = header.s_ci_zran_offset as usize;
487 let inflate_count = header.s_ci_zran_count as usize;
488 let batch_inflate_size = inflate_count * size_of::<BatchInflateContext>();
489 let ptr = state
490 .blob_meta_file_map
491 .validate_range(inflate_offset, batch_inflate_size)?;
492 let array = unsafe {
493 Vec::from_raw_parts(
494 ptr as *mut u8 as *mut BatchInflateContext,
495 inflate_count,
496 inflate_count,
497 )
498 };
499 state.batch_info_array = ManuallyDrop::new(array);
500 } else if blob_info.has_feature(BlobFeatures::ZRAN) {
501 let header = state
502 .blob_meta_file_map
503 .get_mut::<BlobCompressionContextHeader>(aligned_uncompressed_size as usize)?;
504 let zran_offset = header.s_ci_zran_offset as usize;
505 let zran_count = header.s_ci_zran_count as usize;
506 let ci_zran_size = header.s_ci_zran_size as usize;
507 let zran_size = zran_count * size_of::<ZranInflateContext>();
508 let ptr = state
509 .blob_meta_file_map
510 .validate_range(zran_offset, zran_size)?;
511 let array = unsafe {
512 Vec::from_raw_parts(
513 ptr as *mut u8 as *mut ZranInflateContext,
514 zran_count,
515 zran_count,
516 )
517 };
518 state.zran_info_array = ManuallyDrop::new(array);
519
520 let zran_dict_size = ci_zran_size - zran_size;
521 let ptr = state
522 .blob_meta_file_map
523 .validate_range(zran_offset + zran_size, zran_dict_size)?;
524 let array =
525 unsafe { Vec::from_raw_parts(ptr as *mut u8, zran_dict_size, zran_dict_size) };
526 state.zran_dict_table = ManuallyDrop::new(array);
527 }
528
529 if load_chunk_digest && blob_info.has_feature(BlobFeatures::INLINED_CHUNK_DIGEST) {
530 let digest_path = PathBuf::from(format!("{}.{}", blob_path, BLOB_DIGEST_FILE_SUFFIX));
531 if let Some(reader) = reader {
532 let toc_path = format!("{}.{}", blob_path, BLOB_TOC_FILE_SUFFIX);
533 let location = if blob_info.blob_toc_size() != 0 {
534 let blob_size = reader
535 .blob_size()
536 .map_err(|_e| eio!("failed to get blob size"))?;
537 let offset = blob_size - blob_info.blob_toc_size() as u64;
538 let mut location = TocLocation::new(offset, blob_info.blob_toc_size() as u64);
539 let digest = blob_info.blob_toc_digest();
540 for c in digest {
541 if *c != 0 {
542 location.validate_digest = true;
543 location.digest.data = *digest;
544 break;
545 }
546 }
547 location
548 } else {
549 TocLocation::default()
550 };
551 let toc_list =
552 TocEntryList::read_from_cache_file(toc_path, reader.as_ref(), &location)?;
553 toc_list.extract_from_blob(reader.clone(), None, Some(&digest_path))?;
554 }
555 if !digest_path.exists() {
556 return Err(eother!("failed to download chunk digest file from blob"));
557 }
558
559 let file = OpenOptions::new().read(true).open(&digest_path)?;
560 let md = file.metadata()?;
561 let size = 32 * blob_info.chunk_count() as usize;
562 if md.len() != size as u64 {
563 return Err(eother!(format!(
564 "size of chunk digest file doesn't match, expect {}, got {}",
565 size,
566 md.len()
567 )));
568 }
569
570 let file_map = FileMapState::new(file, 0, size, false)?;
571 let ptr = file_map.validate_range(0, size)?;
572 let array = unsafe {
573 Vec::from_raw_parts(
574 ptr as *mut u8 as *mut _,
575 chunk_count as usize,
576 chunk_count as usize,
577 )
578 };
579 state.chunk_digest_file_map = file_map;
580 state.chunk_digest_array = ManuallyDrop::new(array);
581 }
582
583 Ok(BlobCompressionContextInfo {
584 state: Arc::new(state),
585 })
586 }
587
588 pub fn get_chunks_uncompressed(
598 &self,
599 start: u64,
600 size: u64,
601 batch_size: u64,
602 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
603 let end = start.checked_add(size).ok_or_else(|| {
604 einval!(format!(
605 "get_chunks_uncompressed: invalid start {}/size {}",
606 start, size
607 ))
608 })?;
609 if end > self.state.uncompressed_size {
610 return Err(einval!(format!(
611 "get_chunks_uncompressed: invalid end {}/uncompressed_size {}",
612 end, self.state.uncompressed_size
613 )));
614 }
615 let batch_end = if batch_size <= size {
616 end
617 } else {
618 std::cmp::min(
619 start.checked_add(batch_size).unwrap_or(end),
620 self.state.uncompressed_size,
621 )
622 };
623 let batch_size = if batch_size < size { size } else { batch_size };
624
625 self.state
626 .get_chunks_uncompressed(start, end, batch_end, batch_size)
627 }
628
629 pub fn get_chunks_compressed(
637 &self,
638 start: u64,
639 size: u64,
640 batch_size: u64,
641 prefetch: bool,
642 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
643 let end = start.checked_add(size).ok_or_else(|| {
644 einval!(einval!(format!(
645 "get_chunks_compressed: invalid start {}/size {}",
646 start, size
647 )))
648 })?;
649 if end > self.state.compressed_size {
650 return Err(einval!(format!(
651 "get_chunks_compressed: invalid end {}/compressed_size {}",
652 end, self.state.compressed_size
653 )));
654 }
655 let batch_end = if batch_size <= size {
656 end
657 } else {
658 std::cmp::min(
659 start.checked_add(batch_size).unwrap_or(end),
660 self.state.compressed_size,
661 )
662 };
663
664 self.state
665 .get_chunks_compressed(start, end, batch_end, batch_size, prefetch)
666 }
667
668 pub fn add_more_chunks(
670 &self,
671 chunks: &[Arc<dyn BlobChunkInfo>],
672 max_size: u64,
673 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
674 self.state.add_more_chunks(chunks, max_size)
675 }
676
677 pub fn get_chunk_count(&self) -> usize {
679 self.state.chunk_info_array.len()
680 }
681
682 pub fn get_chunk_index(&self, addr: u64) -> Result<usize> {
684 self.state.get_chunk_index(addr)
685 }
686
687 pub fn get_uncompressed_offset(&self, chunk_index: usize) -> u64 {
689 self.state.get_uncompressed_offset(chunk_index)
690 }
691
692 pub fn get_chunk_digest(&self, chunk_index: usize) -> Option<&[u8]> {
694 self.state.get_chunk_digest(chunk_index)
695 }
696
697 pub fn get_chunk_info(&self, chunk_index: usize) -> Arc<dyn BlobChunkInfo> {
699 BlobMetaChunk::new(chunk_index, &self.state)
700 }
701
702 pub fn is_batch_chunk(&self, chunk_index: u32) -> bool {
706 self.state.is_batch_chunk(chunk_index as usize)
707 }
708
709 pub fn get_batch_index(&self, chunk_index: u32) -> Result<u32> {
711 self.state.get_batch_index(chunk_index as usize)
712 }
713
714 pub fn get_uncompressed_offset_in_batch_buf(&self, chunk_index: u32) -> Result<u32> {
716 self.state
717 .get_uncompressed_offset_in_batch_buf(chunk_index as usize)
718 }
719
720 pub fn get_batch_context(&self, batch_index: u32) -> Result<&BatchInflateContext> {
722 self.state.get_batch_context(batch_index as usize)
723 }
724
725 pub fn get_compressed_size(&self, chunk_index: u32) -> Result<u32> {
728 self.state.get_compressed_size(chunk_index as usize)
729 }
730
731 pub fn get_zran_index(&self, chunk_index: u32) -> Result<u32> {
733 self.state.get_zran_index(chunk_index as usize)
734 }
735
736 pub fn get_zran_offset(&self, chunk_index: u32) -> Result<u32> {
738 self.state.get_zran_offset(chunk_index as usize)
739 }
740
741 pub fn get_zran_context(&self, zran_index: u32) -> Result<(ZranContext, &[u8])> {
743 self.state.get_zran_context(zran_index as usize)
744 }
745
746 fn read_metadata(
747 blob_info: &BlobInfo,
748 reader: &Arc<dyn BlobReader>,
749 buffer: &mut [u8],
750 ) -> Result<()> {
751 trace!(
752 "blob_info compressor {} ci_compressor {} ci_compressed_size {} ci_uncompressed_size {}",
753 blob_info.compressor(),
754 blob_info.meta_ci_compressor(),
755 blob_info.meta_ci_compressed_size(),
756 blob_info.meta_ci_uncompressed_size(),
757 );
758
759 let compressed_size = blob_info.meta_ci_compressed_size();
760 let uncompressed_size = blob_info.meta_ci_uncompressed_size();
761 let aligned_uncompressed_size = round_up_4k(uncompressed_size);
762 let expected_raw_size = (compressed_size + BLOB_CCT_HEADER_SIZE) as usize;
763 let mut raw_data = alloc_buf(expected_raw_size);
764
765 let read_size = (|| {
766 let mut retry_count = 3;
768
769 loop {
770 match reader.read_all(&mut raw_data, blob_info.meta_ci_offset()) {
771 Ok(size) => return Ok(size),
772 Err(e) => {
773 if retry_count > 0 {
775 warn!(
776 "failed to read metadata for blob {} from backend, {}, retry read metadata",
777 blob_info.blob_id(),
778 e
779 );
780 retry_count -= 1;
781 continue;
782 }
783
784 return Err(eio!(format!(
785 "failed to read metadata for blob {} from backend, {}",
786 blob_info.blob_id(),
787 e
788 )));
789 }
790 }
791 }
792 })()?;
793
794 if read_size != expected_raw_size {
795 return Err(eio!(format!(
796 "failed to read metadata for blob {} from backend, compressor {}, got {} bytes, expect {} bytes",
797 blob_info.blob_id(),
798 blob_info.meta_ci_compressor(),
799 read_size,
800 expected_raw_size
801 )));
802 }
803
804 let decrypted = match decrypt_with_context(
805 &raw_data[0..compressed_size as usize],
806 &blob_info.cipher_object(),
807 &blob_info.cipher_context(),
808 blob_info.cipher() != crypt::Algorithm::None,
809 ){
810 Ok(data) => data,
811 Err(e) => return Err(eio!(format!(
812 "failed to decrypt metadata for blob {} from backend, cipher {}, encrypted data size {}, {}",
813 blob_info.blob_id(),
814 blob_info.cipher(),
815 compressed_size,
816 e
817 ))),
818 };
819 let header = match decrypt_with_context(
820 &raw_data[compressed_size as usize..expected_raw_size],
821 &blob_info.cipher_object(),
822 &blob_info.cipher_context(),
823 blob_info.cipher() != crypt::Algorithm::None,
824 ){
825 Ok(data) => data,
826 Err(e) => return Err(eio!(format!(
827 "failed to decrypt meta header for blob {} from backend, cipher {}, encrypted data size {}, {}",
828 blob_info.blob_id(),
829 blob_info.cipher(),
830 compressed_size,
831 e
832 ))),
833 };
834
835 let uncompressed = if blob_info.meta_ci_compressor() != compress::Algorithm::None {
836 let mut uncompressed = vec![0u8; uncompressed_size as usize];
849 compress::decompress(
850 &decrypted,
851 &mut uncompressed,
852 blob_info.meta_ci_compressor(),
853 )
854 .map_err(|e| {
855 error!("failed to decompress blob meta data: {}", e);
856 e
857 })?;
858 Cow::Owned(uncompressed)
859 } else {
860 decrypted
861 };
862 buffer[0..uncompressed_size as usize].copy_from_slice(&uncompressed);
863 buffer[aligned_uncompressed_size as usize
864 ..(aligned_uncompressed_size + BLOB_CCT_HEADER_SIZE) as usize]
865 .copy_from_slice(&header);
866 Ok(())
867 }
868
869 fn validate_header(
870 blob_info: &BlobInfo,
871 header: &BlobCompressionContextHeader,
872 ) -> Result<bool> {
873 trace!("blob meta header magic {:x}/{:x}, entries {:x}/{:x}, features {:x}/{:x}, compressor {:x}/{:x}, ci_offset {:x}/{:x}, compressed_size {:x}/{:x}, uncompressed_size {:x}/{:x}",
874 u32::from_le(header.s_magic),
875 BLOB_CCT_MAGIC,
876 u32::from_le(header.s_ci_entries),
877 blob_info.chunk_count(),
878 u32::from_le(header.s_features),
879 blob_info.features().bits(),
880 u32::from_le(header.s_ci_compressor),
881 blob_info.meta_ci_compressor() as u32,
882 u64::from_le(header.s_ci_offset),
883 blob_info.meta_ci_offset(),
884 u64::from_le(header.s_ci_compressed_size),
885 blob_info.meta_ci_compressed_size(),
886 u64::from_le(header.s_ci_uncompressed_size),
887 blob_info.meta_ci_uncompressed_size());
888
889 if u32::from_le(header.s_magic) != BLOB_CCT_MAGIC
890 || u32::from_le(header.s_magic2) != BLOB_CCT_MAGIC
891 || (!blob_info.has_feature(BlobFeatures::IS_CHUNKDICT_GENERATED)
892 && u32::from_le(header.s_ci_entries) != blob_info.chunk_count())
893 || u32::from_le(header.s_ci_compressor) != blob_info.meta_ci_compressor() as u32
894 || u64::from_le(header.s_ci_offset) != blob_info.meta_ci_offset()
895 || u64::from_le(header.s_ci_compressed_size) != blob_info.meta_ci_compressed_size()
896 || u64::from_le(header.s_ci_uncompressed_size) != blob_info.meta_ci_uncompressed_size()
897 {
898 return Ok(false);
899 }
900
901 let chunk_count = blob_info.chunk_count();
902 if chunk_count == 0 || chunk_count > RAFS_MAX_CHUNKS_PER_BLOB {
903 return Err(einval!(format!(
904 "chunk count {:x} in blob meta header is invalid!",
905 chunk_count
906 )));
907 }
908
909 let info_size = u64::from_le(header.s_ci_uncompressed_size) as usize;
910 let aligned_info_size = round_up_4k(info_size);
911 if blob_info.has_feature(BlobFeatures::CHUNK_INFO_V2)
912 && (blob_info.has_feature(BlobFeatures::ZRAN)
913 || blob_info.has_feature(BlobFeatures::BATCH))
914 {
915 if info_size < (chunk_count as usize) * (size_of::<BlobChunkInfoV2Ondisk>()) {
916 return Err(einval!("uncompressed size in blob meta header is invalid!"));
917 }
918 } else if blob_info.has_feature(BlobFeatures::CHUNK_INFO_V2) {
919 if info_size != (chunk_count as usize) * (size_of::<BlobChunkInfoV2Ondisk>())
920 || (aligned_info_size as u64) > BLOB_CCT_V2_MAX_SIZE
921 {
922 return Err(einval!("uncompressed size in blob meta header is invalid!"));
923 }
924 } else if blob_info.has_feature(BlobFeatures::ZRAN)
925 || blob_info.has_feature(BlobFeatures::BATCH)
926 {
927 return Err(einval!("invalid feature flags in blob meta header!"));
928 } else if !blob_info.has_feature(BlobFeatures::IS_CHUNKDICT_GENERATED)
929 && (info_size != (chunk_count as usize) * (size_of::<BlobChunkInfoV1Ondisk>())
930 || (aligned_info_size as u64) > BLOB_CCT_V1_MAX_SIZE)
931 {
932 return Err(einval!("uncompressed size in blob meta header is invalid!"));
933 }
934
935 if blob_info.has_feature(BlobFeatures::ZRAN) {
936 let offset = header.s_ci_zran_offset;
937 if offset != (chunk_count as u64) * (size_of::<BlobChunkInfoV2Ondisk>() as u64) {
938 return Ok(false);
939 }
940 if offset + header.s_ci_zran_size > info_size as u64 {
941 return Ok(false);
942 }
943 let zran_count = header.s_ci_zran_count as u64;
944 let size = zran_count * size_of::<ZranInflateContext>() as u64;
945 if zran_count > chunk_count as u64 {
946 return Ok(false);
947 }
948 if size > header.s_ci_zran_size {
949 return Ok(false);
950 }
951 }
952
953 Ok(true)
954 }
955}
956
957#[derive(Default)]
959pub struct BlobCompressionContext {
960 pub(crate) blob_index: u32,
961 pub(crate) blob_features: u32,
962 pub(crate) compressed_size: u64,
963 pub(crate) uncompressed_size: u64,
964 pub(crate) chunk_info_array: ManuallyDrop<BlobMetaChunkArray>,
965 pub(crate) chunk_digest_array: ManuallyDrop<Vec<DigestData>>,
966 pub(crate) batch_info_array: ManuallyDrop<Vec<BatchInflateContext>>,
967 pub(crate) zran_info_array: ManuallyDrop<Vec<ZranInflateContext>>,
968 pub(crate) zran_dict_table: ManuallyDrop<Vec<u8>>,
969 blob_meta_file_map: FileMapState,
970 chunk_digest_file_map: FileMapState,
971 chunk_digest_default: RafsDigest,
972}
973
974impl BlobCompressionContext {
975 fn get_chunks_uncompressed(
976 self: &Arc<BlobCompressionContext>,
977 start: u64,
978 end: u64,
979 batch_end: u64,
980 batch_size: u64,
981 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
982 self.chunk_info_array
983 .get_chunks_uncompressed(self, start, end, batch_end, batch_size)
984 }
985
986 fn get_chunks_compressed(
987 self: &Arc<BlobCompressionContext>,
988 start: u64,
989 end: u64,
990 batch_end: u64,
991 batch_size: u64,
992 prefetch: bool,
993 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
994 self.chunk_info_array
995 .get_chunks_compressed(self, start, end, batch_end, batch_size, prefetch)
996 }
997
998 fn add_more_chunks(
999 self: &Arc<BlobCompressionContext>,
1000 chunks: &[Arc<dyn BlobChunkInfo>],
1001 max_size: u64,
1002 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1003 self.chunk_info_array
1004 .add_more_chunks(self, chunks, max_size)
1005 }
1006
1007 fn get_uncompressed_offset(&self, chunk_index: usize) -> u64 {
1008 self.chunk_info_array.uncompressed_offset(chunk_index)
1009 }
1010
1011 fn get_chunk_digest(&self, chunk_index: usize) -> Option<&[u8]> {
1012 if chunk_index < self.chunk_digest_array.len() {
1013 Some(&self.chunk_digest_array[chunk_index])
1014 } else {
1015 None
1016 }
1017 }
1018
1019 fn get_chunk_index(&self, addr: u64) -> Result<usize> {
1020 self.chunk_info_array
1021 .get_chunk_index_nocheck(self, addr, false)
1022 }
1023
1024 fn is_batch_chunk(&self, chunk_index: usize) -> bool {
1028 self.chunk_info_array.is_batch(chunk_index)
1029 }
1030
1031 fn get_batch_index(&self, chunk_index: usize) -> Result<u32> {
1032 self.chunk_info_array.batch_index(chunk_index)
1033 }
1034
1035 fn get_uncompressed_offset_in_batch_buf(&self, chunk_index: usize) -> Result<u32> {
1036 self.chunk_info_array
1037 .uncompressed_offset_in_batch_buf(chunk_index)
1038 }
1039
1040 fn get_batch_context(&self, batch_index: usize) -> Result<&BatchInflateContext> {
1042 if batch_index < self.batch_info_array.len() {
1043 let ctx = &self.batch_info_array[batch_index];
1044 Ok(ctx)
1045 } else {
1046 Err(einval!(format!(
1047 "Invalid batch index, current: {}, max: {}",
1048 batch_index,
1049 self.batch_info_array.len()
1050 )))
1051 }
1052 }
1053
1054 pub fn get_compressed_size(&self, chunk_index: usize) -> Result<u32> {
1057 if self.is_batch_chunk(chunk_index) {
1058 let ctx = self
1059 .get_batch_context(self.get_batch_index(chunk_index)? as usize)
1060 .unwrap();
1061 Ok(ctx.compressed_size())
1062 } else {
1063 Ok(self.chunk_info_array.compressed_size(chunk_index))
1064 }
1065 }
1066
1067 fn get_zran_index(&self, chunk_index: usize) -> Result<u32> {
1068 self.chunk_info_array.zran_index(chunk_index)
1069 }
1070
1071 fn get_zran_offset(&self, chunk_index: usize) -> Result<u32> {
1072 self.chunk_info_array.zran_offset(chunk_index)
1073 }
1074
1075 fn get_zran_context(&self, zran_index: usize) -> Result<(ZranContext, &[u8])> {
1077 if zran_index < self.zran_info_array.len() {
1078 let entry = &self.zran_info_array[zran_index];
1079 let dict_off = entry.dict_offset() as usize;
1080 let dict_size = entry.dict_size() as usize;
1081 if dict_off.checked_add(dict_size).is_none()
1082 || dict_off + dict_size > self.zran_dict_table.len()
1083 {
1084 return Err(einval!(format!(
1085 "Invalid ZRan context, dict_off: {}, dict_size: {}, max: {}",
1086 dict_off,
1087 dict_size,
1088 self.zran_dict_table.len()
1089 )));
1090 };
1091 let dict = &self.zran_dict_table[dict_off..dict_off + dict_size];
1092 let ctx = ZranContext::from(entry);
1093 Ok((ctx, dict))
1094 } else {
1095 Err(einval!(format!(
1096 "Invalid ZRan index, current: {}, max: {}",
1097 zran_index,
1098 self.zran_info_array.len()
1099 )))
1100 }
1101 }
1102
1103 pub(crate) fn is_separate(&self) -> bool {
1104 self.blob_features & BlobFeatures::SEPARATE.bits() != 0
1105 }
1106
1107 pub(crate) fn is_encrypted(&self) -> bool {
1108 self.blob_features & BlobFeatures::ENCRYPTED.bits() != 0
1109 }
1110}
1111
1112#[derive(Clone)]
1113pub enum BlobMetaChunkArray {
1115 V1(Vec<BlobChunkInfoV1Ondisk>),
1117 V2(Vec<BlobChunkInfoV2Ondisk>),
1119}
1120
1121impl Default for BlobMetaChunkArray {
1122 fn default() -> Self {
1123 BlobMetaChunkArray::new_v2()
1124 }
1125}
1126
1127impl BlobMetaChunkArray {
1129 pub fn new_v1() -> Self {
1131 BlobMetaChunkArray::V1(Vec::new())
1132 }
1133
1134 pub fn new_v2() -> Self {
1136 BlobMetaChunkArray::V2(Vec::new())
1137 }
1138
1139 pub fn len(&self) -> usize {
1141 match self {
1142 BlobMetaChunkArray::V1(v) => v.len(),
1143 BlobMetaChunkArray::V2(v) => v.len(),
1144 }
1145 }
1146
1147 pub fn is_empty(&self) -> bool {
1149 match self {
1150 BlobMetaChunkArray::V1(v) => v.is_empty(),
1151 BlobMetaChunkArray::V2(v) => v.is_empty(),
1152 }
1153 }
1154
1155 pub fn as_byte_slice(&self) -> &[u8] {
1157 match self {
1158 BlobMetaChunkArray::V1(v) => unsafe {
1159 std::slice::from_raw_parts(
1160 v.as_ptr() as *const u8,
1161 v.len() * size_of::<BlobChunkInfoV1Ondisk>(),
1162 )
1163 },
1164 BlobMetaChunkArray::V2(v) => unsafe {
1165 std::slice::from_raw_parts(
1166 v.as_ptr() as *const u8,
1167 v.len() * size_of::<BlobChunkInfoV2Ondisk>(),
1168 )
1169 },
1170 }
1171 }
1172
1173 pub fn add_v1(
1175 &mut self,
1176 compressed_offset: u64,
1177 compressed_size: u32,
1178 uncompressed_offset: u64,
1179 uncompressed_size: u32,
1180 ) {
1181 match self {
1182 BlobMetaChunkArray::V1(v) => {
1183 let mut meta = BlobChunkInfoV1Ondisk::default();
1184 meta.set_compressed_offset(compressed_offset);
1185 meta.set_compressed_size(compressed_size);
1186 meta.set_uncompressed_offset(uncompressed_offset);
1187 meta.set_uncompressed_size(uncompressed_size);
1188 v.push(meta);
1189 }
1190 BlobMetaChunkArray::V2(_v) => unimplemented!(),
1191 }
1192 }
1193
1194 #[allow(clippy::too_many_arguments)]
1196 pub fn add_v2(
1197 &mut self,
1198 compressed_offset: u64,
1199 compressed_size: u32,
1200 uncompressed_offset: u64,
1201 uncompressed_size: u32,
1202 compressed: bool,
1203 encrypted: bool,
1204 has_crc32: bool,
1205 is_batch: bool,
1206 data: u64,
1207 ) {
1208 match self {
1209 BlobMetaChunkArray::V2(v) => {
1210 let mut meta = BlobChunkInfoV2Ondisk::default();
1211 meta.set_compressed_offset(compressed_offset);
1212 meta.set_compressed_size(compressed_size);
1213 meta.set_uncompressed_offset(uncompressed_offset);
1214 meta.set_uncompressed_size(uncompressed_size);
1215 meta.set_compressed(compressed);
1216 meta.set_encrypted(encrypted);
1217 meta.set_has_crc32(has_crc32);
1218 meta.set_batch(is_batch);
1219 meta.set_data(data);
1220 v.push(meta);
1221 }
1222 BlobMetaChunkArray::V1(_v) => unimplemented!(),
1223 }
1224 }
1225
1226 pub fn add_v2_info(&mut self, chunk_info: BlobChunkInfoV2Ondisk) {
1228 match self {
1229 BlobMetaChunkArray::V2(v) => v.push(chunk_info),
1230 BlobMetaChunkArray::V1(_v) => unimplemented!(),
1231 }
1232 }
1233}
1234
1235impl BlobMetaChunkArray {
1236 fn from_file_map(filemap: &FileMapState, blob_info: &BlobInfo) -> Result<Self> {
1237 let chunk_count = blob_info.chunk_count();
1238 if blob_info.has_feature(BlobFeatures::CHUNK_INFO_V2) {
1239 let chunk_size = chunk_count as usize * size_of::<BlobChunkInfoV2Ondisk>();
1240 let base = filemap.validate_range(0, chunk_size)?;
1241 let v = unsafe {
1242 Vec::from_raw_parts(
1243 base as *mut u8 as *mut BlobChunkInfoV2Ondisk,
1244 chunk_count as usize,
1245 chunk_count as usize,
1246 )
1247 };
1248 Ok(BlobMetaChunkArray::V2(v))
1249 } else {
1250 let chunk_size = chunk_count as usize * size_of::<BlobChunkInfoV1Ondisk>();
1251 let base = filemap.validate_range(0, chunk_size)?;
1252 let v = unsafe {
1253 Vec::from_raw_parts(
1254 base as *mut u8 as *mut BlobChunkInfoV1Ondisk,
1255 chunk_count as usize,
1256 chunk_count as usize,
1257 )
1258 };
1259 Ok(BlobMetaChunkArray::V1(v))
1260 }
1261 }
1262
1263 fn get_chunk_index_nocheck(
1264 &self,
1265 state: &BlobCompressionContext,
1266 addr: u64,
1267 compressed: bool,
1268 ) -> Result<usize> {
1269 match self {
1270 BlobMetaChunkArray::V1(v) => {
1271 Self::_get_chunk_index_nocheck(state, v, addr, compressed, false)
1272 }
1273 BlobMetaChunkArray::V2(v) => {
1274 Self::_get_chunk_index_nocheck(state, v, addr, compressed, false)
1275 }
1276 }
1277 }
1278
1279 fn get_chunks_compressed(
1280 &self,
1281 state: &Arc<BlobCompressionContext>,
1282 start: u64,
1283 end: u64,
1284 batch_end: u64,
1285 batch_size: u64,
1286 prefetch: bool,
1287 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1288 match self {
1289 BlobMetaChunkArray::V1(v) => {
1290 Self::_get_chunks_compressed(state, v, start, end, batch_end, batch_size, prefetch)
1291 }
1292 BlobMetaChunkArray::V2(v) => {
1293 Self::_get_chunks_compressed(state, v, start, end, batch_end, batch_size, prefetch)
1294 }
1295 }
1296 }
1297
1298 fn get_chunks_uncompressed(
1299 &self,
1300 state: &Arc<BlobCompressionContext>,
1301 start: u64,
1302 end: u64,
1303 batch_end: u64,
1304 batch_size: u64,
1305 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1306 match self {
1307 BlobMetaChunkArray::V1(v) => {
1308 Self::_get_chunks_uncompressed(state, v, start, end, batch_end, batch_size)
1309 }
1310 BlobMetaChunkArray::V2(v) => {
1311 Self::_get_chunks_uncompressed(state, v, start, end, batch_end, batch_size)
1312 }
1313 }
1314 }
1315
1316 fn add_more_chunks(
1317 &self,
1318 state: &Arc<BlobCompressionContext>,
1319 chunks: &[Arc<dyn BlobChunkInfo>],
1320 max_size: u64,
1321 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1322 match self {
1323 BlobMetaChunkArray::V1(v) => Self::_add_more_chunks(state, v, chunks, max_size),
1324 BlobMetaChunkArray::V2(v) => Self::_add_more_chunks(state, v, chunks, max_size),
1325 }
1326 }
1327
1328 fn compressed_offset(&self, index: usize) -> u64 {
1329 match self {
1330 BlobMetaChunkArray::V1(v) => v[index].compressed_offset(),
1331 BlobMetaChunkArray::V2(v) => v[index].compressed_offset(),
1332 }
1333 }
1334
1335 fn compressed_size(&self, index: usize) -> u32 {
1336 match self {
1337 BlobMetaChunkArray::V1(v) => v[index].compressed_size(),
1338 BlobMetaChunkArray::V2(v) => v[index].compressed_size(),
1339 }
1340 }
1341
1342 fn uncompressed_offset(&self, index: usize) -> u64 {
1343 match self {
1344 BlobMetaChunkArray::V1(v) => v[index].uncompressed_offset(),
1345 BlobMetaChunkArray::V2(v) => v[index].uncompressed_offset(),
1346 }
1347 }
1348
1349 fn uncompressed_size(&self, index: usize) -> u32 {
1350 match self {
1351 BlobMetaChunkArray::V1(v) => v[index].uncompressed_size(),
1352 BlobMetaChunkArray::V2(v) => v[index].uncompressed_size(),
1353 }
1354 }
1355
1356 fn is_batch(&self, index: usize) -> bool {
1357 match self {
1358 BlobMetaChunkArray::V1(v) => v[index].is_batch(),
1359 BlobMetaChunkArray::V2(v) => v[index].is_batch(),
1360 }
1361 }
1362
1363 fn batch_index(&self, index: usize) -> Result<u32> {
1364 match self {
1365 BlobMetaChunkArray::V1(v) => v[index].get_batch_index(),
1366 BlobMetaChunkArray::V2(v) => v[index].get_batch_index(),
1367 }
1368 }
1369
1370 fn uncompressed_offset_in_batch_buf(&self, index: usize) -> Result<u32> {
1371 match self {
1372 BlobMetaChunkArray::V1(v) => v[index].get_uncompressed_offset_in_batch_buf(),
1373 BlobMetaChunkArray::V2(v) => v[index].get_uncompressed_offset_in_batch_buf(),
1374 }
1375 }
1376
1377 fn zran_index(&self, index: usize) -> Result<u32> {
1378 match self {
1379 BlobMetaChunkArray::V1(v) => v[index].get_zran_index(),
1380 BlobMetaChunkArray::V2(v) => v[index].get_zran_index(),
1381 }
1382 }
1383
1384 fn zran_offset(&self, index: usize) -> Result<u32> {
1385 match self {
1386 BlobMetaChunkArray::V1(v) => v[index].get_zran_offset(),
1387 BlobMetaChunkArray::V2(v) => v[index].get_zran_offset(),
1388 }
1389 }
1390
1391 fn is_compressed(&self, index: usize) -> bool {
1392 match self {
1393 BlobMetaChunkArray::V1(v) => v[index].is_compressed(),
1394 BlobMetaChunkArray::V2(v) => v[index].is_compressed(),
1395 }
1396 }
1397
1398 fn is_encrypted(&self, index: usize) -> bool {
1399 match self {
1400 BlobMetaChunkArray::V1(v) => v[index].is_encrypted(),
1401 BlobMetaChunkArray::V2(v) => v[index].is_encrypted(),
1402 }
1403 }
1404
1405 fn has_crc32(&self, index: usize) -> bool {
1406 match self {
1407 BlobMetaChunkArray::V1(v) => v[index].has_crc32(),
1408 BlobMetaChunkArray::V2(v) => v[index].has_crc32(),
1409 }
1410 }
1411
1412 fn crc32(&self, index: usize) -> u32 {
1413 match self {
1414 BlobMetaChunkArray::V1(v) => v[index].crc32(),
1415 BlobMetaChunkArray::V2(v) => v[index].crc32(),
1416 }
1417 }
1418
1419 fn _get_chunk_index_nocheck<T: BlobMetaChunkInfo>(
1420 state: &BlobCompressionContext,
1421 chunks: &[T],
1422 addr: u64,
1423 compressed: bool,
1424 prefetch: bool,
1425 ) -> Result<usize> {
1426 let mut size = chunks.len();
1427 let mut left = 0;
1428 let mut right = size;
1429 let mut start = 0;
1430 let mut end = 0;
1431
1432 while left < right {
1433 let mid = left + size / 2;
1434 let entry = &chunks[mid];
1438 if compressed {
1439 let c_offset = entry.compressed_offset();
1441 let c_size = state.get_compressed_size(mid)?;
1442 (start, end) = (c_offset, c_offset + c_size as u64);
1443 } else {
1444 start = entry.uncompressed_offset();
1445 end = entry.uncompressed_end();
1446 };
1447
1448 if start > addr {
1449 right = mid;
1450 } else if end <= addr {
1451 left = mid + 1;
1452 } else {
1453 if entry.is_batch() && entry.get_uncompressed_offset_in_batch_buf()? > 0 {
1455 right = mid;
1456 } else {
1457 return Ok(mid);
1458 }
1459 }
1460
1461 size = right - left;
1462 }
1463
1464 if prefetch {
1466 if right < chunks.len() {
1467 let entry = &chunks[right];
1468 if entry.compressed_offset() > addr {
1469 return Ok(right);
1470 }
1471 }
1472 if left < chunks.len() {
1473 let entry = &chunks[left];
1474 if entry.compressed_offset() > addr {
1475 return Ok(left);
1476 }
1477 }
1478 }
1479
1480 Err(einval!(format!(
1482 "failed to get chunk index, prefetch {}, left {}, right {}, start: {}, end: {}, addr: {}",
1483 prefetch, left, right, start, end, addr
1484 )))
1485 }
1486
1487 fn _get_chunks_uncompressed<T: BlobMetaChunkInfo>(
1488 state: &Arc<BlobCompressionContext>,
1489 chunk_info_array: &[T],
1490 start: u64,
1491 end: u64,
1492 batch_end: u64,
1493 batch_size: u64,
1494 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1495 let mut vec = Vec::with_capacity(512);
1496 let mut index =
1497 Self::_get_chunk_index_nocheck(state, chunk_info_array, start, false, false)?;
1498 let entry = Self::get_chunk_entry(state, chunk_info_array, index)?;
1499 trace!(
1500 "get_chunks_uncompressed: entry {} {}",
1501 entry.uncompressed_offset(),
1502 entry.uncompressed_end()
1503 );
1504
1505 if entry.is_zran() {
1507 let zran_index = entry.get_zran_index()?;
1508 let mut count = state.zran_info_array[zran_index as usize].out_size() as u64;
1509 let mut zran_last = zran_index;
1510 let mut zran_end = entry.aligned_uncompressed_end();
1511
1512 while index > 0 {
1513 let entry = Self::get_chunk_entry(state, chunk_info_array, index - 1)?;
1514 if !entry.is_zran() {
1515 return Err(einval!(
1516 "inconsistent ZRan and non-ZRan chunk compression information entries"
1517 ));
1518 } else if entry.get_zran_index()? != zran_index {
1519 break;
1521 } else {
1522 index -= 1;
1523 }
1524 }
1525
1526 for entry in &chunk_info_array[index..] {
1527 entry.validate(state)?;
1528 if !entry.is_zran() {
1529 return Err(einval!(
1530 "inconsistent ZRan and non-ZRan chunk compression information entries"
1531 ));
1532 }
1533 if entry.get_zran_index()? != zran_last {
1534 let ctx = &state.zran_info_array[entry.get_zran_index()? as usize];
1535 if count + ctx.out_size() as u64 >= batch_size
1536 && entry.uncompressed_offset() >= end
1537 {
1538 return Ok(vec);
1539 }
1540 count += ctx.out_size() as u64;
1541 zran_last = entry.get_zran_index()?;
1542 }
1543 zran_end = entry.aligned_uncompressed_end();
1544 vec.push(BlobMetaChunk::new(index, state));
1545 index += 1;
1546 }
1547
1548 if zran_end >= end {
1549 return Ok(vec);
1550 }
1551 return Err(einval!(format!(
1552 "entry not found index {} chunk_info_array.len {}, end 0x{:x}, range [0x{:x}-0x{:x}]",
1553 index,
1554 chunk_info_array.len(),
1555 vec.last().map(|v| v.uncompressed_end()).unwrap_or_default(),
1556 start,
1557 end,
1558 )));
1559 }
1560
1561 vec.push(BlobMetaChunk::new(index, state));
1562 let mut last_end = entry.aligned_uncompressed_end();
1563 if last_end >= batch_end {
1564 Ok(vec)
1565 } else {
1566 while index + 1 < chunk_info_array.len() {
1567 index += 1;
1568
1569 let entry = Self::get_chunk_entry(state, chunk_info_array, index)?;
1570 if entry.uncompressed_offset() != last_end {
1571 return Err(einval!(format!(
1572 "mismatch uncompressed {} size {} last_end {}",
1573 entry.uncompressed_offset(),
1574 entry.uncompressed_size(),
1575 last_end
1576 )));
1577 } else if last_end >= end && entry.aligned_uncompressed_end() >= batch_end {
1578 return Ok(vec);
1580 }
1581
1582 vec.push(BlobMetaChunk::new(index, state));
1583 last_end = entry.aligned_uncompressed_end();
1584 if last_end >= batch_end {
1585 return Ok(vec);
1586 }
1587 }
1588
1589 if last_end >= end {
1590 Ok(vec)
1591 } else {
1592 Err(einval!(format!(
1593 "entry not found index {} chunk_info_array.len {}, last_end 0x{:x}, end 0x{:x}, blob compressed size 0x{:x}",
1594 index,
1595 chunk_info_array.len(),
1596 last_end,
1597 end,
1598 state.uncompressed_size,
1599 )))
1600 }
1601 }
1602 }
1603
1604 fn _get_chunks_compressed<T: BlobMetaChunkInfo>(
1605 state: &Arc<BlobCompressionContext>,
1606 chunk_info_array: &[T],
1607 start: u64,
1608 end: u64,
1609 batch_end: u64,
1610 batch_size: u64,
1611 prefetch: bool,
1612 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1613 let mut vec = Vec::with_capacity(512);
1614 let mut index =
1615 Self::_get_chunk_index_nocheck(state, chunk_info_array, start, true, prefetch)?;
1616 let entry = Self::get_chunk_entry(state, chunk_info_array, index)?;
1617
1618 if entry.is_zran() {
1620 let zran_index = entry.get_zran_index()?;
1621 let pos = state.zran_info_array[zran_index as usize].in_offset();
1622 let mut zran_last = zran_index;
1623
1624 while index > 0 {
1625 let entry = Self::get_chunk_entry(state, chunk_info_array, index - 1)?;
1626 if !entry.is_zran() {
1627 return Err(einval!(
1628 "inconsistent ZRan and non-ZRan chunk compression information entries"
1629 ));
1630 } else if entry.get_zran_index()? != zran_index {
1631 break;
1633 } else {
1634 index -= 1;
1635 }
1636 }
1637
1638 for entry in &chunk_info_array[index..] {
1639 entry.validate(state)?;
1640 if !entry.is_zran() {
1641 return Err(einval!(
1642 "inconsistent ZRan and non-ZRan chunk compression information entries"
1643 ));
1644 }
1645 if entry.get_zran_index()? != zran_last {
1646 let ctx = &state.zran_info_array[entry.get_zran_index()? as usize];
1647 if ctx.in_offset() + ctx.in_size() as u64 - pos > batch_size
1648 && entry.compressed_offset() > end
1649 {
1650 return Ok(vec);
1651 }
1652 zran_last = entry.get_zran_index()?;
1653 }
1654 vec.push(BlobMetaChunk::new(index, state));
1655 index += 1;
1656 }
1657
1658 if let Some(c) = vec.last() {
1659 if c.uncompressed_end() >= end {
1660 return Ok(vec);
1661 }
1662 if prefetch && index >= chunk_info_array.len() {
1664 return Ok(vec);
1665 }
1666 }
1667 return Err(einval!(format!(
1668 "entry not found index {} chunk_info_array.len {}",
1669 index,
1670 chunk_info_array.len(),
1671 )));
1672 }
1673
1674 vec.push(BlobMetaChunk::new(index, state));
1675 let mut last_end = entry.compressed_end();
1676 if last_end >= batch_end {
1677 Ok(vec)
1678 } else {
1679 while index + 1 < chunk_info_array.len() {
1680 index += 1;
1681
1682 let entry = Self::get_chunk_entry(state, chunk_info_array, index)?;
1683 if last_end >= end && entry.compressed_end() > batch_end {
1685 return Ok(vec);
1686 }
1687
1688 vec.push(BlobMetaChunk::new(index, state));
1689 last_end = entry.compressed_end();
1690 if last_end >= batch_end {
1691 return Ok(vec);
1692 }
1693 }
1694
1695 if last_end >= end || (prefetch && !vec.is_empty()) {
1696 Ok(vec)
1697 } else {
1698 Err(einval!(format!(
1699 "entry not found index {} chunk_info_array.len {}, last_end 0x{:x}, end 0x{:x}, blob compressed size 0x{:x}",
1700 index,
1701 chunk_info_array.len(),
1702 last_end,
1703 end,
1704 state.compressed_size,
1705 )))
1706 }
1707 }
1708 }
1709
1710 fn _add_more_chunks<T: BlobMetaChunkInfo>(
1711 state: &Arc<BlobCompressionContext>,
1712 chunk_info_array: &[T],
1713 chunks: &[Arc<dyn BlobChunkInfo>],
1714 max_size: u64,
1715 ) -> Result<Vec<Arc<dyn BlobChunkInfo>>> {
1716 let first_idx = chunks[0].id() as usize;
1717 let first_entry = Self::get_chunk_entry(state, chunk_info_array, first_idx)?;
1718 let last_idx = chunks[chunks.len() - 1].id() as usize;
1719 let last_entry = Self::get_chunk_entry(state, chunk_info_array, last_idx)?;
1720
1721 let fetch_end = max_size + chunks[0].compressed_offset();
1723
1724 let mut vec = Vec::with_capacity(128);
1725
1726 if first_entry.is_zran() {
1728 let first_zran_idx = first_entry.get_zran_index()?;
1729 let mut last_zran_idx = last_entry.get_zran_index()?;
1730 let mut index = first_idx;
1731 while index > 0 {
1732 let entry = Self::get_chunk_entry(state, chunk_info_array, index - 1)?;
1733 if !entry.is_zran() {
1734 return Err(std::io::Error::other(
1736 "invalid ZRan compression information data",
1737 ));
1738 } else if entry.get_zran_index()? != first_zran_idx {
1739 break;
1741 } else {
1742 index -= 1;
1743 }
1744 }
1745
1746 for entry in &chunk_info_array[index..] {
1747 if entry.validate(state).is_err() || !entry.is_zran() {
1748 return Err(std::io::Error::other(
1749 "invalid ZRan compression information data",
1750 ));
1751 } else if entry.get_zran_index()? > last_zran_idx {
1752 if entry.compressed_end() + RAFS_MAX_CHUNK_SIZE <= fetch_end
1753 && entry.get_zran_index()? == last_zran_idx + 1
1754 {
1755 vec.push(BlobMetaChunk::new(index, state));
1756 last_zran_idx += 1;
1757 } else {
1758 return Ok(vec);
1759 }
1760 } else {
1761 vec.push(BlobMetaChunk::new(index, state));
1762 }
1763 index += 1;
1764 }
1765 } else {
1766 let mut entry_idx = first_idx;
1768 let mut curr_batch_idx = u32::MAX;
1769
1770 if first_entry.is_batch() {
1772 curr_batch_idx = first_entry.get_batch_index()?;
1773 while entry_idx > 0 {
1774 let entry = Self::get_chunk_entry(state, chunk_info_array, entry_idx - 1)?;
1775 if !entry.is_batch() || entry.get_batch_index()? != curr_batch_idx {
1776 break;
1778 } else {
1779 entry_idx -= 1;
1780 }
1781 }
1782 }
1783
1784 let mut idx_chunks = 0;
1786 for (idx, entry) in chunk_info_array.iter().enumerate().skip(entry_idx) {
1787 entry.validate(state)?;
1788
1789 if idx_chunks < chunks.len() && idx == chunks[idx_chunks].id() as usize {
1791 vec.push(chunks[idx_chunks].clone());
1792 idx_chunks += 1;
1793 if entry.is_batch() {
1794 curr_batch_idx = entry.get_batch_index()?;
1795 }
1796 continue;
1797 }
1798
1799 if entry.is_batch() {
1802 if curr_batch_idx == entry.get_batch_index()? {
1803 vec.push(BlobMetaChunk::new(idx, state));
1804 continue;
1805 }
1806
1807 let batch_ctx = state.get_batch_context(entry.get_batch_index()? as usize)?;
1808 if entry.compressed_offset() + batch_ctx.compressed_size() as u64 <= fetch_end {
1809 vec.push(BlobMetaChunk::new(idx, state));
1810 curr_batch_idx = entry.get_batch_index()?;
1811 } else {
1812 break;
1813 }
1814 continue;
1815 }
1816 if entry.compressed_end() <= fetch_end {
1817 vec.push(BlobMetaChunk::new(idx, state));
1818 } else {
1819 break;
1820 }
1821 }
1822 }
1823
1824 Ok(vec)
1825 }
1826
1827 fn get_chunk_entry<'a, T: BlobMetaChunkInfo>(
1828 state: &Arc<BlobCompressionContext>,
1829 chunk_info_array: &'a [T],
1830 index: usize,
1831 ) -> Result<&'a T> {
1832 assert!(index < chunk_info_array.len());
1833 let entry = &chunk_info_array[index];
1834 if state.blob_features & BlobFeatures::IS_CHUNKDICT_GENERATED.bits() == 0 {
1836 entry.validate(state)?;
1837 }
1838 Ok(entry)
1839 }
1840}
1841
1842#[derive(Clone)]
1844pub struct BlobMetaChunk {
1845 chunk_index: usize,
1846 meta: Arc<BlobCompressionContext>,
1847}
1848
1849impl BlobMetaChunk {
1850 #[allow(clippy::new_ret_no_self)]
1851 pub(crate) fn new(
1852 chunk_index: usize,
1853 meta: &Arc<BlobCompressionContext>,
1854 ) -> Arc<dyn BlobChunkInfo> {
1855 assert!(chunk_index <= RAFS_MAX_CHUNKS_PER_BLOB as usize);
1856 Arc::new(BlobMetaChunk {
1857 chunk_index,
1858 meta: meta.clone(),
1859 }) as Arc<dyn BlobChunkInfo>
1860 }
1861}
1862
1863impl BlobChunkInfo for BlobMetaChunk {
1864 fn chunk_id(&self) -> &RafsDigest {
1865 if self.chunk_index < self.meta.chunk_digest_array.len() {
1866 let digest = &self.meta.chunk_digest_array[self.chunk_index];
1867 digest.into()
1868 } else {
1869 &self.meta.chunk_digest_default
1870 }
1871 }
1872
1873 fn id(&self) -> u32 {
1874 self.chunk_index as u32
1875 }
1876
1877 fn blob_index(&self) -> u32 {
1878 self.meta.blob_index
1879 }
1880
1881 fn compressed_offset(&self) -> u64 {
1882 self.meta
1883 .chunk_info_array
1884 .compressed_offset(self.chunk_index)
1885 }
1886
1887 fn compressed_size(&self) -> u32 {
1888 self.meta.chunk_info_array.compressed_size(self.chunk_index)
1889 }
1890
1891 fn uncompressed_offset(&self) -> u64 {
1892 self.meta
1893 .chunk_info_array
1894 .uncompressed_offset(self.chunk_index)
1895 }
1896
1897 fn uncompressed_size(&self) -> u32 {
1898 self.meta
1899 .chunk_info_array
1900 .uncompressed_size(self.chunk_index)
1901 }
1902
1903 fn is_batch(&self) -> bool {
1904 self.meta.chunk_info_array.is_batch(self.chunk_index)
1905 }
1906
1907 fn is_compressed(&self) -> bool {
1908 self.meta.chunk_info_array.is_compressed(self.chunk_index)
1909 }
1910
1911 fn is_encrypted(&self) -> bool {
1912 self.meta.chunk_info_array.is_encrypted(self.chunk_index)
1913 }
1914
1915 fn has_crc32(&self) -> bool {
1916 self.meta.chunk_info_array.has_crc32(self.chunk_index)
1917 }
1918
1919 fn crc32(&self) -> u32 {
1920 self.meta.chunk_info_array.crc32(self.chunk_index)
1921 }
1922
1923 fn as_any(&self) -> &dyn Any {
1924 self
1925 }
1926}
1927
1928impl BlobV5ChunkInfo for BlobMetaChunk {
1929 fn index(&self) -> u32 {
1930 self.chunk_index as u32
1931 }
1932
1933 fn file_offset(&self) -> u64 {
1934 0
1936 }
1937
1938 fn flags(&self) -> BlobChunkFlags {
1939 let mut flags = BlobChunkFlags::empty();
1940 if self.is_compressed() {
1941 flags |= BlobChunkFlags::COMPRESSED;
1942 }
1943 flags
1944 }
1945
1946 fn as_base(&self) -> &dyn BlobChunkInfo {
1947 self
1948 }
1949}
1950
1951pub trait BlobMetaChunkInfo {
1953 fn compressed_offset(&self) -> u64;
1955
1956 fn set_compressed_offset(&mut self, offset: u64);
1958
1959 fn compressed_size(&self) -> u32;
1961
1962 fn set_compressed_size(&mut self, size: u32);
1964
1965 fn compressed_end(&self) -> u64 {
1967 self.compressed_offset() + self.compressed_size() as u64
1968 }
1969
1970 fn uncompressed_offset(&self) -> u64;
1972
1973 fn set_uncompressed_offset(&mut self, offset: u64);
1975
1976 fn uncompressed_size(&self) -> u32;
1978
1979 fn set_uncompressed_size(&mut self, size: u32);
1981
1982 fn uncompressed_end(&self) -> u64 {
1984 self.uncompressed_offset() + self.uncompressed_size() as u64
1985 }
1986
1987 fn aligned_uncompressed_end(&self) -> u64 {
1989 round_up_4k(self.uncompressed_end())
1990 }
1991
1992 fn is_encrypted(&self) -> bool;
1994
1995 fn has_crc32(&self) -> bool;
1997
1998 fn is_compressed(&self) -> bool;
2003
2004 fn is_batch(&self) -> bool;
2006
2007 fn is_zran(&self) -> bool;
2009
2010 fn get_zran_index(&self) -> Result<u32>;
2012
2013 fn get_zran_offset(&self) -> Result<u32>;
2015
2016 fn get_batch_index(&self) -> Result<u32>;
2018
2019 fn get_uncompressed_offset_in_batch_buf(&self) -> Result<u32>;
2021
2022 fn crc32(&self) -> u32;
2024
2025 fn get_data(&self) -> u64;
2027
2028 fn validate(&self, state: &BlobCompressionContext) -> Result<()>;
2030}
2031
2032pub fn format_blob_features(features: BlobFeatures) -> String {
2034 let mut output = String::new();
2035 if features.contains(BlobFeatures::ALIGNED) {
2036 output += "aligned ";
2037 }
2038 if features.contains(BlobFeatures::BATCH) {
2039 output += "batch ";
2040 }
2041 if features.contains(BlobFeatures::CAP_TAR_TOC) {
2042 output += "cap_toc ";
2043 }
2044 if features.contains(BlobFeatures::INLINED_CHUNK_DIGEST) {
2045 output += "chunk-digest ";
2046 }
2047 if features.contains(BlobFeatures::CHUNK_INFO_V2) {
2048 output += "chunk-v2 ";
2049 }
2050 if features.contains(BlobFeatures::INLINED_FS_META) {
2051 output += "fs-meta ";
2052 }
2053 if features.contains(BlobFeatures::SEPARATE) {
2054 output += "separate ";
2055 }
2056 if features.contains(BlobFeatures::HAS_TAR_HEADER) {
2057 output += "tar-header ";
2058 }
2059 if features.contains(BlobFeatures::HAS_TOC) {
2060 output += "toc ";
2061 }
2062 if features.contains(BlobFeatures::ZRAN) {
2063 output += "zran ";
2064 }
2065 if features.contains(BlobFeatures::ENCRYPTED) {
2066 output += "encrypted ";
2067 }
2068 if features.contains(BlobFeatures::IS_CHUNKDICT_GENERATED) {
2069 output += "is-chunkdict-generated ";
2070 }
2071 output.trim_end().to_string()
2072}
2073
2074fn round_up_4k<T: Add<Output = T> + BitAnd<Output = T> + Not<Output = T> + From<u16>>(val: T) -> T {
2075 (val + T::from(0xfff)) & !T::from(0xfff)
2076}
2077
2078#[cfg(test)]
2079pub(crate) mod tests {
2080 use super::*;
2081 use crate::backend::{BackendResult, BlobReader};
2082 use crate::device::BlobFeatures;
2083 use crate::RAFS_DEFAULT_CHUNK_SIZE;
2084 use nix::sys::uio;
2085 use nydus_utils::digest::{self, DigestHasher};
2086 use nydus_utils::metrics::BackendMetrics;
2087 use std::fs::File;
2088 use std::os::unix::io::AsRawFd;
2089 use std::path::PathBuf;
2090
2091 pub(crate) struct DummyBlobReader {
2092 pub metrics: Arc<BackendMetrics>,
2093 pub file: File,
2094 }
2095
2096 impl BlobReader for DummyBlobReader {
2097 fn blob_size(&self) -> BackendResult<u64> {
2098 Ok(0)
2099 }
2100
2101 fn try_read(&self, buf: &mut [u8], offset: u64) -> BackendResult<usize> {
2102 let ret = uio::pread(self.file.as_raw_fd(), buf, offset as i64).unwrap();
2103 Ok(ret)
2104 }
2105
2106 fn metrics(&self) -> &BackendMetrics {
2107 &self.metrics
2108 }
2109 }
2110
2111 #[test]
2112 fn test_round_up_4k() {
2113 assert_eq!(round_up_4k(0), 0x0u32);
2114 assert_eq!(round_up_4k(1), 0x1000u32);
2115 assert_eq!(round_up_4k(0xfff), 0x1000u32);
2116 assert_eq!(round_up_4k(0x1000), 0x1000u32);
2117 assert_eq!(round_up_4k(0x1001), 0x2000u32);
2118 assert_eq!(round_up_4k(0x1fff), 0x2000u64);
2119 }
2120
2121 #[test]
2122 fn test_load_meta_ci_zran_add_more_chunks() {
2123 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
2124 let path = PathBuf::from(root_dir).join("../tests/texture/zran/233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a");
2125
2126 let features = BlobFeatures::ALIGNED
2127 | BlobFeatures::INLINED_FS_META
2128 | BlobFeatures::CHUNK_INFO_V2
2129 | BlobFeatures::ZRAN;
2130 let mut blob_info = BlobInfo::new(
2131 0,
2132 "233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a".to_string(),
2133 0x16c6000,
2134 9839040,
2135 RAFS_DEFAULT_CHUNK_SIZE as u32,
2136 0xa3,
2137 features,
2138 );
2139 blob_info.set_blob_meta_info(0, 0xa1290, 0xa1290, compress::Algorithm::None as u32);
2140 let meta =
2141 BlobCompressionContextInfo::new(&path.display().to_string(), &blob_info, None, false)
2142 .unwrap();
2143 assert_eq!(meta.state.chunk_info_array.len(), 0xa3);
2144 assert_eq!(meta.state.zran_info_array.len(), 0x15);
2145 assert_eq!(meta.state.zran_dict_table.len(), 0xa0348 - 0x15 * 40);
2146
2147 let chunks = vec![BlobMetaChunk::new(0, &meta.state)];
2148 let chunks = meta.add_more_chunks(chunks.as_slice(), 0x30000).unwrap();
2149 assert_eq!(chunks.len(), 67);
2150
2151 let chunks = vec![BlobMetaChunk::new(0, &meta.state)];
2152 let chunks = meta
2153 .add_more_chunks(chunks.as_slice(), RAFS_DEFAULT_CHUNK_SIZE)
2154 .unwrap();
2155 assert_eq!(chunks.len(), 67);
2156
2157 let chunks = vec![BlobMetaChunk::new(66, &meta.state)];
2158 let chunks = meta
2159 .add_more_chunks(chunks.as_slice(), RAFS_DEFAULT_CHUNK_SIZE)
2160 .unwrap();
2161 assert_eq!(chunks.len(), 67);
2162
2163 let chunks = vec![BlobMetaChunk::new(116, &meta.state)];
2164 let chunks = meta
2165 .add_more_chunks(chunks.as_slice(), RAFS_DEFAULT_CHUNK_SIZE)
2166 .unwrap();
2167 assert_eq!(chunks.len(), 1);
2168
2169 let chunks = vec![BlobMetaChunk::new(162, &meta.state)];
2170 let chunks = meta
2171 .add_more_chunks(chunks.as_slice(), RAFS_DEFAULT_CHUNK_SIZE)
2172 .unwrap();
2173 assert_eq!(chunks.len(), 12);
2174 }
2175
2176 #[test]
2177 fn test_load_meta_ci_zran_get_chunks_uncompressed() {
2178 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
2179 let path = PathBuf::from(root_dir).join("../tests/texture/zran/233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a");
2180
2181 let features = BlobFeatures::ALIGNED
2182 | BlobFeatures::INLINED_FS_META
2183 | BlobFeatures::CHUNK_INFO_V2
2184 | BlobFeatures::ZRAN;
2185 let mut blob_info = BlobInfo::new(
2186 0,
2187 "233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a".to_string(),
2188 0x16c6000,
2189 9839040,
2190 RAFS_DEFAULT_CHUNK_SIZE as u32,
2191 0xa3,
2192 features,
2193 );
2194 blob_info.set_blob_meta_info(0, 0xa1290, 0xa1290, compress::Algorithm::None as u32);
2195 let meta =
2196 BlobCompressionContextInfo::new(&path.display().to_string(), &blob_info, None, false)
2197 .unwrap();
2198 assert_eq!(meta.state.chunk_info_array.len(), 0xa3);
2199 assert_eq!(meta.state.zran_info_array.len(), 0x15);
2200 assert_eq!(meta.state.zran_dict_table.len(), 0xa0348 - 0x15 * 40);
2201
2202 let chunks = meta.get_chunks_uncompressed(0, 1, 0x30000).unwrap();
2203 assert_eq!(chunks.len(), 67);
2204
2205 let chunks = meta
2206 .get_chunks_uncompressed(0, 1, RAFS_DEFAULT_CHUNK_SIZE)
2207 .unwrap();
2208 assert_eq!(chunks.len(), 67);
2209
2210 let chunks = meta
2211 .get_chunks_uncompressed(0x112000, 0x10000, RAFS_DEFAULT_CHUNK_SIZE)
2212 .unwrap();
2213 assert_eq!(chunks.len(), 116);
2214
2215 let chunks = meta
2216 .get_chunks_uncompressed(0xf9b000, 0x100, RAFS_DEFAULT_CHUNK_SIZE)
2217 .unwrap();
2218 assert_eq!(chunks.len(), 12);
2219
2220 let chunks = meta
2221 .get_chunks_uncompressed(0xf9b000, 0x100, 4 * RAFS_DEFAULT_CHUNK_SIZE)
2222 .unwrap();
2223 assert_eq!(chunks.len(), 13);
2224
2225 let chunks = meta
2226 .get_chunks_uncompressed(0x16c5000, 0x100, 4 * RAFS_DEFAULT_CHUNK_SIZE)
2227 .unwrap();
2228 assert_eq!(chunks.len(), 12);
2229
2230 assert!(meta
2231 .get_chunks_uncompressed(0x2000000, 0x100, 4 * RAFS_DEFAULT_CHUNK_SIZE)
2232 .is_err());
2233 }
2234
2235 #[test]
2236 fn test_load_meta_ci_zran_get_chunks_compressed() {
2237 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
2238 let path = PathBuf::from(root_dir).join("../tests/texture/zran/233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a");
2239
2240 let features = BlobFeatures::ALIGNED
2241 | BlobFeatures::INLINED_FS_META
2242 | BlobFeatures::CHUNK_INFO_V2
2243 | BlobFeatures::ZRAN;
2244 let mut blob_info = BlobInfo::new(
2245 0,
2246 "233c72f2b6b698c07021c4da367cfe2dff4f049efbaa885ca0ff760ea297865a".to_string(),
2247 0x16c6000,
2248 9839040,
2249 RAFS_DEFAULT_CHUNK_SIZE as u32,
2250 0xa3,
2251 features,
2252 );
2253 blob_info.set_blob_meta_info(0, 0xa1290, 0xa1290, compress::Algorithm::None as u32);
2254 let meta =
2255 BlobCompressionContextInfo::new(&path.display().to_string(), &blob_info, None, false)
2256 .unwrap();
2257 assert_eq!(meta.state.chunk_info_array.len(), 0xa3);
2258 assert_eq!(meta.state.zran_info_array.len(), 0x15);
2259 assert_eq!(meta.state.zran_dict_table.len(), 0xa0348 - 0x15 * 40);
2260
2261 let chunks = meta.get_chunks_compressed(0xb8, 1, 0x30000, false).unwrap();
2262 assert_eq!(chunks.len(), 67);
2263
2264 let chunks = meta
2265 .get_chunks_compressed(0xb8, 1, RAFS_DEFAULT_CHUNK_SIZE, false)
2266 .unwrap();
2267 assert_eq!(chunks.len(), 116);
2268
2269 let chunks = meta
2270 .get_chunks_compressed(0xb8, 1, 2 * RAFS_DEFAULT_CHUNK_SIZE, false)
2271 .unwrap();
2272 assert_eq!(chunks.len(), 120);
2273
2274 let chunks = meta
2275 .get_chunks_compressed(0x5fd41e, 1, RAFS_DEFAULT_CHUNK_SIZE / 2, false)
2276 .unwrap();
2277 assert_eq!(chunks.len(), 3);
2278
2279 let chunks = meta
2280 .get_chunks_compressed(0x95d55d, 0x20, RAFS_DEFAULT_CHUNK_SIZE, false)
2281 .unwrap();
2282 assert_eq!(chunks.len(), 12);
2283
2284 assert!(meta
2285 .get_chunks_compressed(0x0, 0x1, RAFS_DEFAULT_CHUNK_SIZE, false)
2286 .is_err());
2287 assert!(meta
2288 .get_chunks_compressed(0x1000000, 0x1, RAFS_DEFAULT_CHUNK_SIZE, false)
2289 .is_err());
2290 }
2291
2292 #[test]
2293 fn test_blob_compression_context_header_getters_and_setters() {
2294 let mut header = BlobCompressionContextHeader::default();
2295
2296 assert_eq!(header.features(), 0);
2297 header.set_aligned(true);
2298 assert!(header.is_4k_aligned());
2299 header.set_aligned(false);
2300
2301 header.set_inlined_fs_meta(true);
2302 assert!(header.has_feature(BlobFeatures::INLINED_FS_META));
2303 header.set_inlined_fs_meta(false);
2304
2305 header.set_chunk_info_v2(true);
2306 assert!(header.has_feature(BlobFeatures::CHUNK_INFO_V2));
2307 header.set_chunk_info_v2(false);
2308
2309 header.set_ci_zran(true);
2310 assert!(header.has_feature(BlobFeatures::ZRAN));
2311 header.set_ci_zran(false);
2312
2313 header.set_separate_blob(true);
2314 assert!(header.has_feature(BlobFeatures::SEPARATE));
2315 header.set_separate_blob(false);
2316
2317 header.set_ci_batch(true);
2318 assert!(header.has_feature(BlobFeatures::BATCH));
2319 header.set_ci_batch(false);
2320
2321 header.set_inlined_chunk_digest(true);
2322 assert!(header.has_feature(BlobFeatures::INLINED_CHUNK_DIGEST));
2323 header.set_inlined_chunk_digest(false);
2324
2325 header.set_has_tar_header(true);
2326 assert!(header.has_feature(BlobFeatures::HAS_TAR_HEADER));
2327 header.set_has_tar_header(false);
2328
2329 header.set_has_toc(true);
2330 assert!(header.has_feature(BlobFeatures::HAS_TOC));
2331 header.set_has_toc(false);
2332
2333 header.set_cap_tar_toc(true);
2334 assert!(header.has_feature(BlobFeatures::CAP_TAR_TOC));
2335 header.set_cap_tar_toc(false);
2336
2337 header.set_tarfs(true);
2338 assert!(header.has_feature(BlobFeatures::TARFS));
2339 header.set_tarfs(false);
2340
2341 header.set_encrypted(true);
2342 assert!(header.has_feature(BlobFeatures::ENCRYPTED));
2343 header.set_encrypted(false);
2344
2345 assert_eq!(header.features(), 0);
2346
2347 assert_eq!(header.ci_compressor(), compress::Algorithm::Lz4Block);
2348 header.set_ci_compressor(compress::Algorithm::GZip);
2349 assert_eq!(header.ci_compressor(), compress::Algorithm::GZip);
2350 header.set_ci_compressor(compress::Algorithm::Zstd);
2351 assert_eq!(header.ci_compressor(), compress::Algorithm::Zstd);
2352
2353 let mut hasher = RafsDigest::hasher(digest::Algorithm::Sha256);
2354 hasher.digest_update(header.as_bytes());
2355 let hash: String = hasher.digest_finalize().into();
2356 assert_eq!(
2357 hash,
2358 String::from("f56a1129d3df9fc7d60b26dbf495a60bda3dfc265f4f37854e4a36b826b660fc")
2359 );
2360
2361 assert_eq!(header.ci_entries(), 0);
2362 header.set_ci_entries(1);
2363 assert_eq!(header.ci_entries(), 1);
2364
2365 assert_eq!(header.ci_compressed_offset(), 0);
2366 header.set_ci_compressed_offset(1);
2367 assert_eq!(header.ci_compressed_offset(), 1);
2368
2369 assert_eq!(header.ci_compressed_size(), 0);
2370 header.set_ci_compressed_size(1);
2371 assert_eq!(header.ci_compressed_size(), 1);
2372
2373 assert_eq!(header.ci_uncompressed_size(), 0);
2374 header.set_ci_uncompressed_size(1);
2375 assert_eq!(header.ci_uncompressed_size(), 1);
2376
2377 assert_eq!(header.ci_zran_count(), 0);
2378 header.set_ci_zran_count(1);
2379 assert_eq!(header.ci_zran_count(), 1);
2380
2381 assert_eq!(header.ci_zran_offset(), 0);
2382 header.set_ci_zran_offset(1);
2383 assert_eq!(header.ci_zran_offset(), 1);
2384
2385 assert_eq!(header.ci_zran_size(), 0);
2386 header.set_ci_zran_size(1);
2387 assert_eq!(header.ci_zran_size(), 1);
2388 }
2389
2390 #[test]
2391 fn test_format_blob_features() {
2392 let features = !BlobFeatures::default();
2393 let content = format_blob_features(features);
2394 assert!(content.contains("aligned"));
2395 assert!(content.contains("fs-meta"));
2396 }
2397
2398 #[test]
2399 fn test_add_more_chunks() {
2400 let mut chunk0 = BlobChunkInfoV2Ondisk::default();
2402 chunk0.set_batch(true);
2403 chunk0.set_compressed(true);
2404 chunk0.set_batch_index(0);
2405 chunk0.set_uncompressed_offset_in_batch_buf(0);
2406 chunk0.set_uncompressed_offset(0);
2407 chunk0.set_uncompressed_size(0x2000);
2408 chunk0.set_compressed_offset(0);
2409
2410 let mut chunk1 = BlobChunkInfoV2Ondisk::default();
2411 chunk1.set_batch(true);
2412 chunk1.set_compressed(true);
2413 chunk1.set_batch_index(0);
2414 chunk1.set_uncompressed_offset_in_batch_buf(0x2000);
2415 chunk1.set_uncompressed_offset(0x2000);
2416 chunk1.set_uncompressed_size(0x1000);
2417 chunk1.set_compressed_offset(0);
2418
2419 let mut batch_ctx0 = BatchInflateContext::default();
2420 batch_ctx0.set_uncompressed_batch_size(0x3000);
2421 batch_ctx0.set_compressed_size(0x2000);
2422
2423 let mut chunk2 = BlobChunkInfoV2Ondisk::default();
2424 chunk2.set_batch(false);
2425 chunk2.set_compressed(true);
2426 chunk2.set_uncompressed_offset(0x3000);
2427 chunk2.set_compressed_offset(0x2000);
2428 chunk2.set_uncompressed_size(0x4000);
2429 chunk2.set_compressed_size(0x3000);
2430
2431 let mut chunk3 = BlobChunkInfoV2Ondisk::default();
2432 chunk3.set_batch(true);
2433 chunk3.set_compressed(true);
2434 chunk3.set_batch_index(1);
2435 chunk3.set_uncompressed_offset_in_batch_buf(0);
2436 chunk3.set_uncompressed_offset(0x7000);
2437 chunk3.set_uncompressed_size(0x2000);
2438 chunk3.set_compressed_offset(0x5000);
2439
2440 let mut chunk4 = BlobChunkInfoV2Ondisk::default();
2441 chunk4.set_batch(true);
2442 chunk4.set_compressed(true);
2443 chunk4.set_batch_index(1);
2444 chunk4.set_uncompressed_offset_in_batch_buf(0x2000);
2445 chunk4.set_uncompressed_offset(0x9000);
2446 chunk4.set_uncompressed_size(0x2000);
2447 chunk4.set_compressed_offset(0x5000);
2448
2449 let mut batch_ctx1 = BatchInflateContext::default();
2450 batch_ctx1.set_compressed_size(0x3000);
2451 batch_ctx1.set_uncompressed_batch_size(0x4000);
2452
2453 let chunk_info_array = vec![chunk0, chunk1, chunk2, chunk3, chunk4];
2454 let chunk_infos = BlobMetaChunkArray::V2(chunk_info_array);
2455 let chunk_infos = ManuallyDrop::new(chunk_infos);
2456
2457 let batch_ctx_array = vec![batch_ctx0, batch_ctx1];
2458 let batch_ctxes = ManuallyDrop::new(batch_ctx_array);
2459
2460 let state = BlobCompressionContext {
2461 chunk_info_array: chunk_infos,
2462 batch_info_array: batch_ctxes,
2463 compressed_size: 0x8000,
2464 uncompressed_size: 0xB000,
2465 blob_features: (BlobFeatures::BATCH
2466 | BlobFeatures::ALIGNED
2467 | BlobFeatures::INLINED_FS_META
2468 | BlobFeatures::CHUNK_INFO_V2)
2469 .bits(),
2470 ..Default::default()
2471 };
2472
2473 let state = Arc::new(state);
2474 let meta = BlobCompressionContextInfo { state };
2475
2476 let chunks = vec![BlobMetaChunk::new(0, &meta.state)];
2478 let chunks = meta
2479 .add_more_chunks(&chunks, RAFS_DEFAULT_CHUNK_SIZE)
2480 .unwrap();
2481 let chunk_ids: Vec<_> = chunks.iter().map(|c| c.id()).collect();
2482 assert_eq!(chunk_ids, vec![0, 1, 2, 3, 4]);
2483
2484 let chunks = vec![BlobMetaChunk::new(1, &meta.state)];
2486 let chunks = meta
2487 .add_more_chunks(&chunks, RAFS_DEFAULT_CHUNK_SIZE)
2488 .unwrap();
2489 let chunk_ids: Vec<_> = chunks.iter().map(|c| c.id()).collect();
2490 assert_eq!(chunk_ids, vec![0, 1, 2, 3, 4]);
2491
2492 let chunks = vec![BlobMetaChunk::new(1, &meta.state)];
2494 let chunks = meta.add_more_chunks(&chunks, 0).unwrap();
2495 let chunk_ids: Vec<_> = chunks.iter().map(|c| c.id()).collect();
2496 assert_eq!(chunk_ids, vec![0, 1]);
2497
2498 let chunks = vec![BlobMetaChunk::new(2, &meta.state)];
2500 let chunks = meta.add_more_chunks(&chunks, 0).unwrap();
2501 let chunk_ids: Vec<_> = chunks.iter().map(|c| c.id()).collect();
2502 assert_eq!(chunk_ids, vec![2]);
2503
2504 let chunks = vec![BlobMetaChunk::new(1, &meta.state)];
2506 let chunks = meta.add_more_chunks(&chunks, 0x6000).unwrap();
2507 let chunk_ids: Vec<_> = chunks.iter().map(|c| c.id()).collect();
2508 assert_eq!(chunk_ids, vec![0, 1, 2]);
2509 }
2510}