1use std::ffi::{OsStr, OsString};
7use std::fmt::{self, Display, Formatter, Result as FmtResult};
8use std::fs::{self, File};
9use std::io::Read;
10use std::ops::Deref;
11#[cfg(target_os = "linux")]
12use std::os::linux::fs::MetadataExt;
13#[cfg(target_os = "macos")]
14use std::os::macos::fs::MetadataExt;
15use std::os::unix::ffi::OsStrExt;
16use std::path::{Component, Path, PathBuf};
17use std::sync::Arc;
18
19use anyhow::{anyhow, bail, Context, Error, Result};
20use nydus_rafs::metadata::chunk::ChunkWrapper;
21use nydus_rafs::metadata::inode::InodeWrapper;
22use nydus_rafs::metadata::layout::v6::EROFS_INODE_FLAT_PLAIN;
23use nydus_rafs::metadata::layout::RafsXAttrs;
24use nydus_rafs::metadata::{Inode, RafsVersion};
25use nydus_storage::device::BlobFeatures;
26use nydus_storage::meta::{BlobChunkInfoV2Ondisk, BlobMetaChunkInfo};
27use nydus_utils::digest::{DigestHasher, RafsDigest};
28use nydus_utils::{compress, crc32, crypt};
29use nydus_utils::{div_round_up, event_tracer, root_tracer, try_round_up_4k, ByteSize};
30use parse_size::parse_size;
31use sha2::digest::Digest;
32
33use crate::{BlobContext, BlobManager, BuildContext, ChunkDict, ConversionType, Overlay};
34
35use super::context::Artifact;
36
37const ROOT_PATH_NAME: &[u8] = b"/";
39
40#[derive(Clone, Hash, PartialEq, Eq)]
42pub enum ChunkSource {
43 Build,
45 Dict,
47 Parent,
49}
50
51impl Display for ChunkSource {
52 fn fmt(&self, f: &mut Formatter) -> FmtResult {
53 match self {
54 Self::Build => write!(f, "build"),
55 Self::Dict => write!(f, "dict"),
56 Self::Parent => write!(f, "parent"),
57 }
58 }
59}
60
61#[derive(Clone)]
63pub struct NodeChunk {
64 pub source: ChunkSource,
65 pub inner: Arc<ChunkWrapper>,
66}
67
68impl Display for NodeChunk {
69 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70 write!(f, "{}", self.inner,)
71 }
72}
73
74impl NodeChunk {
75 pub fn copy_from(&mut self, other: &ChunkWrapper) {
77 let mut chunk = self.inner.deref().clone();
78 chunk.copy_from(other);
79 self.inner = Arc::new(chunk);
80 }
81
82 pub fn set_index(&mut self, index: u32) {
84 let mut chunk = self.inner.deref().clone();
85 chunk.set_index(index);
86 self.inner = Arc::new(chunk);
87 }
88
89 pub fn set_blob_index(&mut self, index: u32) {
91 let mut chunk = self.inner.deref().clone();
92 chunk.set_blob_index(index);
93 self.inner = Arc::new(chunk);
94 }
95
96 pub fn set_compressed_size(&mut self, size: u32) {
98 let mut chunk = self.inner.deref().clone();
99 chunk.set_compressed_size(size);
100 self.inner = Arc::new(chunk);
101 }
102
103 pub fn set_file_offset(&mut self, offset: u64) {
105 let mut chunk = self.inner.deref().clone();
106 chunk.set_file_offset(offset);
107 self.inner = Arc::new(chunk);
108 }
109}
110
111#[derive(Clone, Default, Debug)]
113pub struct NodeInfo {
114 pub explicit_uidgid: bool,
116
117 pub src_dev: u64,
122 pub src_ino: Inode,
124 pub rdev: u64,
126 pub source: PathBuf,
128 pub path: PathBuf,
130 pub target: PathBuf,
132 pub target_vec: Vec<OsString>,
134 pub symlink: Option<OsString>,
136 pub xattrs: RafsXAttrs,
138
139 pub v6_force_extended_inode: bool,
141}
142
143#[derive(Clone)]
145pub struct Node {
146 pub info: Arc<NodeInfo>,
148 pub index: u64,
150 pub inode: InodeWrapper,
152 pub chunks: Vec<NodeChunk>,
154 pub layer_idx: u16,
156 pub overlay: Overlay,
158
159 pub v6_compact_inode: bool,
161 pub v6_datalayout: u16,
163 pub v6_offset: u64,
165 pub v6_dirents_offset: u64,
167 pub v6_dirents: Vec<(u64, OsString, u32)>,
169}
170
171impl Display for Node {
172 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
173 write!(
174 f,
175 "{} {:?}: index {} ino {} real_ino {} child_index {} child_count {} i_nlink {} i_size {} i_blocks {} i_name_size {} i_symlink_size {} has_xattr {} link {:?} i_mtime {} i_mtime_nsec {}",
176 self.file_type(),
177 self.target(),
178 self.index,
179 self.inode.ino(),
180 self.info.src_ino,
181 self.inode.child_index(),
182 self.inode.child_count(),
183 self.inode.nlink(),
184 self.inode.size(),
185 self.inode.blocks(),
186 self.inode.name_size(),
187 self.inode.symlink_size(),
188 self.inode.has_xattr(),
189 self.info.symlink,
190 self.inode.mtime(),
191 self.inode.mtime_nsec(),
192 )
193 }
194}
195
196impl Node {
197 pub fn new(inode: InodeWrapper, info: NodeInfo, layer_idx: u16) -> Self {
199 Node {
200 info: Arc::new(info),
201 index: 0,
202 overlay: Overlay::UpperAddition,
203 inode,
204 chunks: Vec::new(),
205 layer_idx,
206 v6_offset: 0,
207 v6_dirents: Vec::<(u64, OsString, u32)>::new(),
208 v6_datalayout: 0,
209 v6_compact_inode: false,
210 v6_dirents_offset: 0,
211 }
212 }
213
214 pub fn dump_node_data(
220 self: &mut Node,
221 ctx: &BuildContext,
222 blob_mgr: &mut BlobManager,
223 blob_writer: &mut dyn Artifact,
224 chunk_data_buf: &mut [u8],
225 ) -> Result<u64> {
226 let mut reader = if self.is_reg() {
227 let file = File::open(self.path())
228 .with_context(|| format!("failed to open node file {:?}", self.path()))?;
229 Some(file)
230 } else {
231 None
232 };
233
234 self.dump_node_data_with_reader(ctx, blob_mgr, blob_writer, reader.as_mut(), chunk_data_buf)
235 }
236
237 pub fn dump_node_data_with_reader<R: Read>(
244 &mut self,
245 ctx: &BuildContext,
246 blob_mgr: &mut BlobManager,
247 blob_writer: &mut dyn Artifact,
248 reader: Option<&mut R>,
249 data_buf: &mut [u8],
250 ) -> Result<u64> {
251 if self.is_dir() {
252 return Ok(0);
253 } else if self.is_symlink() {
254 if let Some(symlink) = self.info.symlink.as_ref() {
255 if self.inode.is_v5() {
256 self.inode
257 .set_digest(RafsDigest::from_buf(symlink.as_bytes(), ctx.digester));
258 }
259 return Ok(0);
260 } else {
261 return Err(Error::msg("inode's symblink is invalid."));
262 }
263 } else if self.is_special() {
264 if self.inode.is_v5() {
265 self.inode
266 .set_digest(RafsDigest::hasher(ctx.digester).digest_finalize());
267 }
268 return Ok(0);
269 }
270
271 let mut blob_size = 0u64;
272 let reader = reader.ok_or_else(|| anyhow!("missing reader to read file data"))?;
273 let mut inode_hasher = if self.inode.is_v5() {
274 Some(RafsDigest::hasher(ctx.digester))
275 } else {
276 None
277 };
278
279 if blob_mgr.external {
280 let external_values = ctx.attributes.get_values(self.target()).unwrap();
281 let external_blob_index = external_values
282 .get("blob_index")
283 .and_then(|v| v.parse::<u32>().ok())
284 .ok_or_else(|| anyhow!("failed to parse blob_index"))?;
285 let external_blob_id = external_values
286 .get("blob_id")
287 .ok_or_else(|| anyhow!("failed to parse blob_id"))?;
288 let external_chunk_size = external_values
289 .get("chunk_size")
290 .and_then(|v| parse_size(v).ok())
291 .ok_or_else(|| anyhow!("failed to parse chunk_size"))?;
292 let mut external_compressed_offset = external_values
293 .get("chunk_0_compressed_offset")
294 .and_then(|v| v.parse::<u64>().ok())
295 .ok_or_else(|| anyhow!("failed to parse chunk_0_compressed_offset"))?;
296 let external_compressed_size = external_values
297 .get("compressed_size")
298 .and_then(|v| v.parse::<u64>().ok())
299 .ok_or_else(|| anyhow!("failed to parse compressed_size"))?;
300 let (_, external_blob_ctx) =
301 blob_mgr.get_or_create_blob_by_idx(ctx, external_blob_index)?;
302 external_blob_ctx.blob_id = external_blob_id.to_string();
303 external_blob_ctx.compressed_blob_size = external_compressed_size;
304 external_blob_ctx.uncompressed_blob_size = external_compressed_size;
305 let chunk_count = self
306 .chunk_count(external_chunk_size as u64)
307 .with_context(|| {
308 format!("failed to get chunk count for {}", self.path().display())
309 })?;
310 self.inode.set_child_count(chunk_count);
311
312 info!(
313 "target {:?}, file_size {}, blob_index {}, blob_id {}, chunk_size {}, chunk_count {}",
314 self.target(),
315 self.inode.size(),
316 external_blob_index,
317 external_blob_id,
318 external_chunk_size,
319 chunk_count
320 );
321 for i in 0..self.inode.child_count() {
322 let mut chunk = self.inode.create_chunk();
323 let file_offset = i as u64 * external_chunk_size as u64;
324 let compressed_size = if i == self.inode.child_count() - 1 {
325 self.inode.size() - (external_chunk_size * i as u64)
326 } else {
327 external_chunk_size
328 } as u32;
329 chunk.set_blob_index(external_blob_index);
330 chunk.set_index(external_blob_ctx.alloc_chunk_index()?);
331 chunk.set_compressed_offset(external_compressed_offset);
332 chunk.set_compressed_size(compressed_size);
333 chunk.set_uncompressed_offset(external_compressed_offset);
334 chunk.set_uncompressed_size(compressed_size);
335 chunk.set_compressed(false);
336 chunk.set_file_offset(file_offset);
337 external_compressed_offset += compressed_size as u64;
338 external_blob_ctx.chunk_size = external_chunk_size as u32;
339
340 if ctx.crc32_algorithm != crc32::Algorithm::None {
341 self.set_external_chunk_crc32(ctx, &mut chunk, i)?
342 }
343
344 if let Some(h) = inode_hasher.as_mut() {
345 h.digest_update(chunk.id().as_ref());
346 }
347
348 self.chunks.push(NodeChunk {
349 source: ChunkSource::Build,
350 inner: Arc::new(chunk),
351 });
352 }
353
354 if let Some(h) = inode_hasher {
355 self.inode.set_digest(h.digest_finalize());
356 }
357
358 return Ok(0);
359 }
360
361 for i in 0..self.inode.child_count() {
363 let chunk_size = ctx.chunk_size;
364 let file_offset = i as u64 * chunk_size as u64;
365 let uncompressed_size = if i == self.inode.child_count() - 1 {
366 (self.inode.size() - chunk_size as u64 * i as u64) as u32
367 } else {
368 chunk_size
369 };
370
371 let chunk_data = &mut data_buf[0..uncompressed_size as usize];
372 let (mut chunk, mut chunk_info) =
373 self.read_file_chunk(ctx, reader, chunk_data, blob_mgr.external)?;
374 if let Some(h) = inode_hasher.as_mut() {
375 h.digest_update(chunk.id().as_ref());
376 }
377
378 if ctx.conversion_type != ConversionType::TarToTarfs && !blob_mgr.external {
380 chunk = match self.deduplicate_chunk(
381 ctx,
382 blob_mgr,
383 file_offset,
384 uncompressed_size,
385 chunk,
386 )? {
387 None => continue,
388 Some(c) => c,
389 };
390 }
391
392 let (blob_index, blob_ctx) = blob_mgr.get_or_create_current_blob(ctx)?;
393 let chunk_index = blob_ctx.alloc_chunk_index()?;
394 chunk.set_blob_index(blob_index);
395 chunk.set_index(chunk_index);
396 chunk.set_file_offset(file_offset);
397 let mut dumped_size = chunk.compressed_size();
398 if ctx.conversion_type == ConversionType::TarToTarfs {
399 chunk.set_uncompressed_offset(chunk.compressed_offset());
400 chunk.set_uncompressed_size(chunk.compressed_size());
401 } else {
402 let (info, d_size) =
403 self.dump_file_chunk(ctx, blob_ctx, blob_writer, chunk_data, &mut chunk)?;
404 if info.is_some() {
405 chunk_info = info;
406 }
407 if let Some(d_size) = d_size {
408 dumped_size = d_size;
409 }
410 }
411
412 let chunk = Arc::new(chunk);
413 blob_size += dumped_size as u64;
414 if ctx.conversion_type != ConversionType::TarToTarfs {
415 blob_ctx.add_chunk_meta_info(&chunk, chunk_info)?;
416 blob_mgr
417 .layered_chunk_dict
418 .add_chunk(chunk.clone(), ctx.digester);
419 }
420 self.chunks.push(NodeChunk {
421 source: ChunkSource::Build,
422 inner: chunk,
423 });
424 }
425
426 if let Some(h) = inode_hasher {
428 self.inode.set_digest(h.digest_finalize());
429 }
430
431 Ok(blob_size)
432 }
433
434 fn set_external_chunk_crc32(
435 &self,
436 ctx: &BuildContext,
437 chunk: &mut ChunkWrapper,
438 i: u32,
439 ) -> Result<()> {
440 if let Some(crcs) = ctx.attributes.get_crcs(self.target()) {
441 if (i as usize) >= crcs.len() {
442 return Err(anyhow!(
443 "invalid crc index {} for file {}",
444 i,
445 self.target().display()
446 ));
447 }
448 chunk.set_has_crc32(true);
449 chunk.set_crc32(crcs[i as usize]);
450 }
451 Ok(())
452 }
453
454 fn read_file_chunk<R: Read>(
455 &self,
456 ctx: &BuildContext,
457 reader: &mut R,
458 buf: &mut [u8],
459 external: bool,
460 ) -> Result<(ChunkWrapper, Option<BlobChunkInfoV2Ondisk>)> {
461 let mut chunk = self.inode.create_chunk();
462 let mut chunk_info = None;
463 if let Some(ref zran) = ctx.blob_zran_generator {
464 let mut zran = zran.lock().unwrap();
465 zran.start_chunk(ctx.chunk_size as u64)?;
466 if !external {
467 reader
468 .read_exact(buf)
469 .with_context(|| format!("failed to read node file {:?}", self.path()))?;
470 }
471 let info = zran.finish_chunk()?;
472 chunk.set_compressed_offset(info.compressed_offset());
473 chunk.set_compressed_size(info.compressed_size());
474 chunk.set_compressed(true);
475 chunk_info = Some(info);
476 } else if let Some(ref tar_reader) = ctx.blob_tar_reader {
477 let pos = tar_reader.position();
479 chunk.set_compressed_offset(pos);
480 chunk.set_compressed_size(buf.len() as u32);
481 chunk.set_compressed(false);
482 if !external {
483 reader
484 .read_exact(buf)
485 .with_context(|| format!("failed to read node file {:?}", self.path()))?;
486 }
487 } else if !external {
488 reader
489 .read_exact(buf)
490 .with_context(|| format!("failed to read node file {:?}", self.path()))?;
491 }
492
493 if ctx.conversion_type != ConversionType::TarToTarfs && !external {
495 chunk.set_id(RafsDigest::from_buf(buf, ctx.digester));
496 if ctx.crc32_algorithm != crc32::Algorithm::None {
497 chunk.set_has_crc32(true);
498 chunk.set_crc32(crc32::Crc32::new(ctx.crc32_algorithm).from_buf(buf));
499 }
500 }
501
502 if ctx.cipher != crypt::Algorithm::None && !external {
503 chunk.set_encrypted(true);
504 }
505
506 Ok((chunk, chunk_info))
507 }
508
509 fn dump_file_chunk(
515 &self,
516 ctx: &BuildContext,
517 blob_ctx: &mut BlobContext,
518 blob_writer: &mut dyn Artifact,
519 chunk_data: &[u8],
520 chunk: &mut ChunkWrapper,
521 ) -> Result<(Option<BlobChunkInfoV2Ondisk>, Option<u32>)> {
522 let d_size = chunk_data.len() as u32;
523 let aligned_d_size = if ctx.aligned_chunk {
524 try_round_up_4k(d_size).unwrap()
526 } else {
527 d_size
528 };
529 let pre_d_offset = blob_ctx.current_uncompressed_offset;
530 blob_ctx.uncompressed_blob_size = pre_d_offset + aligned_d_size as u64;
531 blob_ctx.current_uncompressed_offset += aligned_d_size as u64;
532 chunk.set_uncompressed_offset(pre_d_offset);
533 chunk.set_uncompressed_size(d_size);
534
535 let mut chunk_info = None;
536 let encrypted = blob_ctx.blob_cipher != crypt::Algorithm::None;
537 let mut dumped_size = None;
538
539 if ctx.blob_batch_generator.is_some()
540 && self.inode.child_count() == 1
541 && d_size < ctx.batch_size / 2
542 {
543 let mut batch = ctx.blob_batch_generator.as_ref().unwrap().lock().unwrap();
545
546 if batch.chunk_data_buf_len() as u32 + d_size < ctx.batch_size {
547 chunk_info = Some(batch.generate_chunk_info(
549 blob_ctx.current_compressed_offset,
550 pre_d_offset,
551 d_size,
552 encrypted,
553 )?);
554 batch.append_chunk_data_buf(chunk_data);
555 } else {
556 if !batch.chunk_data_buf_is_empty() {
558 let (_, c_size, _) =
560 Self::write_chunk_data(ctx, blob_ctx, blob_writer, batch.chunk_data_buf())?;
561 dumped_size = Some(c_size);
562 batch.add_context(c_size);
563 batch.clear_chunk_data_buf();
564 }
565
566 chunk_info = Some(batch.generate_chunk_info(
568 blob_ctx.current_compressed_offset,
569 pre_d_offset,
570 d_size,
571 encrypted,
572 )?);
573 batch.append_chunk_data_buf(chunk_data);
574 }
575 } else if !ctx.blob_features.contains(BlobFeatures::SEPARATE) {
576 if let Some(batch) = &ctx.blob_batch_generator {
582 let mut batch = batch.lock().unwrap();
583 if !batch.chunk_data_buf_is_empty() {
584 let (_, c_size, _) =
586 Self::write_chunk_data(ctx, blob_ctx, blob_writer, batch.chunk_data_buf())?;
587 dumped_size = Some(c_size);
588 batch.add_context(c_size);
589 batch.clear_chunk_data_buf();
590 }
591 }
592
593 let (pre_c_offset, c_size, is_compressed) =
594 Self::write_chunk_data(ctx, blob_ctx, blob_writer, chunk_data)
595 .with_context(|| format!("failed to write chunk data {:?}", self.path()))?;
596 dumped_size = Some(dumped_size.unwrap_or(0) + c_size);
597 chunk.set_compressed_offset(pre_c_offset);
598 chunk.set_compressed_size(c_size);
599 chunk.set_compressed(is_compressed);
600 }
601
602 if let Some(blob_cache) = ctx.blob_cache_generator.as_ref() {
603 blob_cache.write_blob_data(chunk_data, chunk, aligned_d_size)?;
604 }
605 event_tracer!("blob_uncompressed_size", +d_size);
606
607 Ok((chunk_info, dumped_size))
608 }
609
610 pub fn write_chunk_data(
611 _ctx: &BuildContext,
612 blob_ctx: &mut BlobContext,
613 blob_writer: &mut dyn Artifact,
614 chunk_data: &[u8],
615 ) -> Result<(u64, u32, bool)> {
616 let (compressed, is_compressed) = compress::compress(chunk_data, blob_ctx.blob_compressor)
617 .with_context(|| "failed to compress node file".to_string())?;
618 let encrypted = crypt::encrypt_with_context(
619 &compressed,
620 &blob_ctx.cipher_object,
621 &blob_ctx.cipher_ctx,
622 blob_ctx.blob_cipher != crypt::Algorithm::None,
623 )?;
624 let compressed_size = encrypted.len() as u32;
625 let pre_compressed_offset = blob_ctx.current_compressed_offset;
626 if !blob_ctx.external {
627 blob_writer
630 .write_all(&encrypted)
631 .context("failed to write blob")?;
632 blob_ctx.blob_hash.update(&encrypted);
633 }
634 blob_ctx.current_compressed_offset += compressed_size as u64;
635 blob_ctx.compressed_blob_size += compressed_size as u64;
636
637 Ok((pre_compressed_offset, compressed_size, is_compressed))
638 }
639
640 fn deduplicate_chunk(
641 &mut self,
642 ctx: &BuildContext,
643 blob_mgr: &mut BlobManager,
644 file_offset: u64,
645 uncompressed_size: u32,
646 mut chunk: ChunkWrapper,
647 ) -> Result<Option<ChunkWrapper>> {
648 let dict = &blob_mgr.global_chunk_dict;
649 let mut cached_chunk = dict.get_chunk(chunk.id(), uncompressed_size);
650 let from_dict = cached_chunk.is_some();
651 if cached_chunk.is_none() {
652 cached_chunk = blob_mgr
653 .layered_chunk_dict
654 .get_chunk(chunk.id(), uncompressed_size);
655 }
656 let cached_chunk = match cached_chunk {
657 Some(v) => v,
658 None => return Ok(Some(chunk)),
659 };
660
661 if !self.is_hardlink() {
663 event_tracer!("dedup_uncompressed_size", +uncompressed_size);
664 event_tracer!("dedup_chunks", +1);
665 }
666 chunk.copy_from(cached_chunk);
667 chunk.set_file_offset(file_offset);
668
669 if from_dict {
671 let blob_index = if let Some(blob_idx) = dict.get_real_blob_idx(chunk.blob_index()) {
672 blob_idx
673 } else {
674 let blob_idx = blob_mgr.alloc_index()?;
675 dict.set_real_blob_idx(chunk.blob_index(), blob_idx);
676 if let Some(blob) = dict.get_blob_by_inner_idx(chunk.blob_index()) {
677 let ctx = BlobContext::from(ctx, blob, ChunkSource::Dict)?;
678 blob_mgr.add_blob(ctx);
679 }
680 blob_idx
681 };
682 chunk.set_blob_index(blob_index);
683 }
684
685 trace!(
686 "\t\tfound duplicated chunk: {} compressor {}",
687 chunk,
688 ctx.compressor
689 );
690 let source = if from_dict {
691 ChunkSource::Dict
692 } else if Some(chunk.blob_index()) != blob_mgr.get_current_blob().map(|(u, _)| u) {
693 ChunkSource::Parent
694 } else {
695 ChunkSource::Build
696 };
697 self.chunks.push(NodeChunk {
698 source,
699 inner: Arc::new(chunk),
700 });
701
702 Ok(None)
703 }
704}
705
706impl Node {
708 #[allow(clippy::too_many_arguments)]
709 pub fn from_fs_object(
711 version: RafsVersion,
712 source: PathBuf,
713 path: PathBuf,
714 overlay: Overlay,
715 chunk_size: u32,
716 file_size: u64,
717 explicit_uidgid: bool,
718 v6_force_extended_inode: bool,
719 ) -> Result<Node> {
720 let target = Self::generate_target(&path, &source);
721 let target_vec = Self::generate_target_vec(&target);
722 let info = NodeInfo {
723 explicit_uidgid,
724 src_ino: 0,
725 src_dev: u64::MAX,
726 rdev: u64::MAX,
727 source,
728 target,
729 path,
730 target_vec,
731 symlink: None,
732 xattrs: RafsXAttrs::default(),
733 v6_force_extended_inode,
734 };
735 let mut node = Node {
736 info: Arc::new(info),
737 index: 0,
738 layer_idx: 0,
739 overlay,
740 inode: InodeWrapper::new(version),
741 chunks: Vec::new(),
742 v6_datalayout: EROFS_INODE_FLAT_PLAIN,
743 v6_compact_inode: false,
744 v6_offset: 0,
745 v6_dirents_offset: 0,
746 v6_dirents: Vec::new(),
747 };
748
749 node.build_inode(chunk_size, file_size)
750 .context("failed to build Node from fs object")?;
751 if version.is_v6() {
752 node.v6_set_inode_compact();
753 }
754
755 Ok(node)
756 }
757
758 fn build_inode_xattr(&mut self) -> Result<()> {
759 let file_xattrs = match xattr::list(self.path()) {
760 Ok(x) => x,
761 Err(e) => {
762 if e.raw_os_error() == Some(libc::EOPNOTSUPP) {
763 return Ok(());
764 } else {
765 return Err(anyhow!(
766 "failed to list xattr of {}, {}",
767 self.path().display(),
768 e
769 ));
770 }
771 }
772 };
773
774 let mut info = self.info.deref().clone();
775 for key in file_xattrs {
776 let value = xattr::get(self.path(), &key).with_context(|| {
777 format!("failed to get xattr {:?} of {}", key, self.path().display())
778 })?;
779 info.xattrs.add(key, value.unwrap_or_default())?;
780 }
781 if !info.xattrs.is_empty() {
782 self.inode.set_has_xattr(true);
783 }
784 self.info = Arc::new(info);
785
786 Ok(())
787 }
788
789 fn build_inode_stat(&mut self, file_size: u64) -> Result<()> {
790 let meta = self
791 .meta()
792 .with_context(|| format!("failed to get metadata of {}", self.path().display()))?;
793 let mut info = self.info.deref().clone();
794
795 info.src_ino = meta.st_ino();
796 info.src_dev = meta.st_dev();
797 info.rdev = meta.st_rdev();
798
799 self.inode.set_mode(meta.st_mode());
800 if info.explicit_uidgid {
801 self.inode.set_uid(meta.st_uid());
802 self.inode.set_gid(meta.st_gid());
803 }
804
805 let ignore_mtime = self.is_root();
811 if !ignore_mtime {
812 self.inode.set_mtime(meta.st_mtime() as u64);
813 self.inode.set_mtime_nsec(meta.st_mtime_nsec() as u32);
814 }
815 self.inode.set_projid(0);
816 self.inode.set_rdev(meta.st_rdev() as u32);
817 self.inode.set_nlink(1);
819
820 if !self.is_dir() {
824 if file_size != 0 && meta.st_size() == 0 {
827 self.inode.set_size(file_size);
828 } else {
829 self.inode.set_size(meta.st_size());
830 }
831 self.v5_set_inode_blocks();
832 }
833 self.info = Arc::new(info);
834
835 Ok(())
836 }
837
838 fn build_inode(&mut self, chunk_size: u32, file_size: u64) -> Result<()> {
839 let size = self.name().byte_size();
840 if size > u16::MAX as usize {
841 bail!("file name length 0x{:x} is too big", size,);
842 }
843 self.inode.set_name_size(size);
844
845 self.build_inode_xattr()
847 .with_context(|| format!("failed to get xattr for {}", self.path().display()))?;
848 self.build_inode_stat(file_size)
849 .with_context(|| format!("failed to build inode {}", self.path().display()))?;
850
851 if self.is_reg() {
852 let chunk_count = self.chunk_count(chunk_size as u64).with_context(|| {
853 format!("failed to get chunk count for {}", self.path().display())
854 })?;
855 self.inode.set_child_count(chunk_count);
856 } else if self.is_symlink() {
857 let target_path = fs::read_link(self.path()).with_context(|| {
858 format!(
859 "failed to read symlink target for {}",
860 self.path().display()
861 )
862 })?;
863 let symlink: OsString = target_path.into();
864 let size = symlink.byte_size();
865 if size > u16::MAX as usize {
866 bail!("symlink content size 0x{:x} is too big", size);
867 }
868 self.inode.set_symlink_size(size);
869 self.set_symlink(symlink);
870 }
871
872 Ok(())
873 }
874
875 fn meta(&self) -> Result<impl MetadataExt> {
876 self.path()
877 .symlink_metadata()
878 .with_context(|| format!("failed to get metadata of {}", self.path().display()))
879 }
880}
881
882impl Node {
884 pub fn is_root(&self) -> bool {
885 self.target() == OsStr::from_bytes(ROOT_PATH_NAME)
886 }
887
888 pub fn is_dir(&self) -> bool {
889 self.inode.is_dir()
890 }
891
892 pub fn is_symlink(&self) -> bool {
893 self.inode.is_symlink()
894 }
895
896 pub fn is_reg(&self) -> bool {
897 self.inode.is_reg()
898 }
899
900 pub fn is_hardlink(&self) -> bool {
901 self.inode.is_hardlink()
902 }
903
904 pub fn is_special(&self) -> bool {
905 self.inode.is_special()
906 }
907
908 pub fn chunk_count(&self, chunk_size: u64) -> Result<u32> {
909 if self.is_reg() {
910 let chunks = div_round_up(self.inode.size(), chunk_size);
911 if chunks > u32::MAX as u64 {
912 bail!("file size 0x{:x} is too big", self.inode.size())
913 } else {
914 Ok(chunks as u32)
915 }
916 } else {
917 Ok(0)
918 }
919 }
920
921 pub fn file_type(&self) -> &str {
923 let mut file_type = "";
924
925 if self.is_symlink() {
926 file_type = "symlink";
927 } else if self.is_dir() {
928 file_type = "dir"
929 } else if self.is_reg() {
930 if self.is_hardlink() {
931 file_type = "hardlink";
932 } else {
933 file_type = "file";
934 }
935 }
936
937 file_type
938 }
939
940 pub fn name(&self) -> &OsStr {
942 let len = self.info.target_vec.len();
943 if len != 0 {
944 &self.info.target_vec[len - 1]
945 } else if self.path() == &self.info.source {
946 OsStr::from_bytes(ROOT_PATH_NAME)
947 } else {
948 self.path().file_name().unwrap()
950 }
951 }
952
953 pub fn path(&self) -> &PathBuf {
955 &self.info.path
956 }
957
958 pub fn generate_target_vec(target: &Path) -> Vec<OsString> {
960 target
961 .components()
962 .map(|comp| match comp {
963 Component::RootDir => OsString::from("/"),
964 Component::Normal(name) => name.to_os_string(),
965 _ => panic!("invalid file component pattern!"),
966 })
967 .collect::<Vec<_>>()
968 }
969
970 pub fn target_vec(&self) -> &[OsString] {
972 &self.info.target_vec
973 }
974
975 pub fn generate_target(path: &Path, root: &Path) -> PathBuf {
983 if let Ok(p) = path.strip_prefix(root) {
984 Path::new("/").join(p)
985 } else {
986 path.to_path_buf()
988 }
989 }
990
991 pub fn target(&self) -> &PathBuf {
993 &self.info.target
994 }
995
996 pub fn set_symlink(&mut self, symlink: OsString) {
998 let mut info = self.info.deref().clone();
999 info.symlink = Some(symlink);
1000 self.info = Arc::new(info);
1001 }
1002
1003 pub fn set_xattr(&mut self, xattr: RafsXAttrs) {
1005 let mut info = self.info.deref().clone();
1006 info.xattrs = xattr;
1007 self.info = Arc::new(info);
1008 }
1009
1010 pub fn remove_xattr(&mut self, key: &OsStr) {
1012 let mut info = self.info.deref().clone();
1013 info.xattrs.remove(key);
1014 if info.xattrs.is_empty() {
1015 self.inode.set_has_xattr(false);
1016 }
1017 self.info = Arc::new(info);
1018 }
1019}
1020
1021#[cfg(test)]
1022mod tests {
1023 use std::{collections::HashMap, io::BufReader};
1024
1025 use nydus_utils::{digest, BufReaderInfo};
1026 use vmm_sys_util::tempfile::TempFile;
1027
1028 use crate::{attributes::Attributes, ArtifactWriter, BlobCacheGenerator, HashChunkDict};
1029
1030 use super::*;
1031
1032 #[test]
1033 fn test_node_chunk() {
1034 let chunk_wrapper1 = ChunkWrapper::new(RafsVersion::V5);
1035 let mut chunk = NodeChunk {
1036 source: ChunkSource::Build,
1037 inner: Arc::new(chunk_wrapper1),
1038 };
1039 println!("NodeChunk: {}", chunk);
1040 matches!(chunk.inner.deref().clone(), ChunkWrapper::V5(_));
1041
1042 let chunk_wrapper2 = ChunkWrapper::new(RafsVersion::V6);
1043 chunk.copy_from(&chunk_wrapper2);
1044 matches!(chunk.inner.deref().clone(), ChunkWrapper::V6(_));
1045
1046 chunk.set_index(0x10);
1047 assert_eq!(chunk.inner.index(), 0x10);
1048 chunk.set_blob_index(0x20);
1049 assert_eq!(chunk.inner.blob_index(), 0x20);
1050 chunk.set_compressed_size(0x30);
1051 assert_eq!(chunk.inner.compressed_size(), 0x30);
1052 chunk.set_file_offset(0x40);
1053 assert_eq!(chunk.inner.file_offset(), 0x40);
1054 }
1055
1056 #[test]
1057 fn test_node_dump_node_data() {
1058 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
1059 let mut source_path = PathBuf::from(root_dir);
1060 source_path.push("../tests/texture/blobs/be7d77eeb719f70884758d1aa800ed0fb09d701aaec469964e9d54325f0d5fef");
1061
1062 let mut inode = InodeWrapper::new(RafsVersion::V5);
1063 inode.set_child_count(2);
1064 inode.set_size(20);
1065 let info = NodeInfo {
1066 explicit_uidgid: true,
1067 src_ino: 1,
1068 src_dev: u64::MAX,
1069 rdev: u64::MAX,
1070 path: source_path.clone(),
1071 source: PathBuf::from("/"),
1072 target: source_path.clone(),
1073 target_vec: vec![OsString::from(source_path)],
1074 symlink: Some(OsString::from("symlink")),
1075 xattrs: RafsXAttrs::new(),
1076 v6_force_extended_inode: false,
1077 };
1078 let mut node = Node::new(inode, info, 1);
1079
1080 let mut ctx = BuildContext::default();
1081 ctx.set_chunk_size(2);
1082 ctx.conversion_type = ConversionType::TarToRef;
1083 ctx.cipher = crypt::Algorithm::Aes128Xts;
1084 let tmp_file1 = TempFile::new().unwrap();
1085 std::fs::write(
1086 tmp_file1.as_path(),
1087 "This is a test!\n".repeat(32).as_bytes(),
1088 )
1089 .unwrap();
1090 let buf_reader = BufReader::new(tmp_file1.into_file());
1091 ctx.blob_tar_reader = Some(BufReaderInfo::from_buf_reader(buf_reader));
1092 let tmp_file2 = TempFile::new().unwrap();
1093 ctx.blob_cache_generator = Some(
1094 BlobCacheGenerator::new(crate::ArtifactStorage::SingleFile(PathBuf::from(
1095 tmp_file2.as_path(),
1096 )))
1097 .unwrap(),
1098 );
1099
1100 let mut blob_mgr = BlobManager::new(digest::Algorithm::Sha256, false);
1101 let mut chunk_dict = HashChunkDict::new(digest::Algorithm::Sha256);
1102 let mut chunk_wrapper = ChunkWrapper::new(RafsVersion::V5);
1103 chunk_wrapper.set_id(RafsDigest {
1104 data: [
1105 209, 217, 144, 116, 135, 113, 3, 121, 133, 92, 96, 25, 219, 145, 151, 219, 119, 47,
1106 96, 147, 90, 51, 78, 44, 193, 149, 6, 102, 13, 173, 138, 191,
1107 ],
1108 });
1109 chunk_wrapper.set_uncompressed_size(2);
1110 chunk_dict.add_chunk(Arc::new(chunk_wrapper), digest::Algorithm::Sha256);
1111 blob_mgr.set_chunk_dict(Arc::new(chunk_dict));
1112
1113 let tmp_file3 = TempFile::new().unwrap();
1114 let mut blob_writer = ArtifactWriter::new(crate::ArtifactStorage::SingleFile(
1115 PathBuf::from(tmp_file3.as_path()),
1116 ))
1117 .unwrap();
1118
1119 let mut chunk_data_buf = [1u8; 32];
1120
1121 node.inode.set_mode(0o755 | libc::S_IFDIR as u32);
1122 let data_size =
1123 node.dump_node_data(&ctx, &mut blob_mgr, &mut blob_writer, &mut chunk_data_buf);
1124 assert!(data_size.is_ok());
1125 assert_eq!(data_size.unwrap(), 0);
1126
1127 node.inode.set_mode(0o755 | libc::S_IFLNK as u32);
1128 let data_size =
1129 node.dump_node_data(&ctx, &mut blob_mgr, &mut blob_writer, &mut chunk_data_buf);
1130 assert!(data_size.is_ok());
1131 assert_eq!(data_size.unwrap(), 0);
1132
1133 node.inode.set_mode(0o755 | libc::S_IFBLK as u32);
1134 let data_size =
1135 node.dump_node_data(&ctx, &mut blob_mgr, &mut blob_writer, &mut chunk_data_buf);
1136 assert!(data_size.is_ok());
1137 assert_eq!(data_size.unwrap(), 0);
1138
1139 node.inode.set_mode(0o755 | libc::S_IFREG as u32);
1140 let data_size =
1141 node.dump_node_data(&ctx, &mut blob_mgr, &mut blob_writer, &mut chunk_data_buf);
1142 assert!(data_size.is_ok());
1143 assert_eq!(data_size.unwrap(), 18);
1144 }
1145
1146 #[test]
1147 fn test_node() {
1148 let inode = InodeWrapper::new(RafsVersion::V5);
1149 let info = NodeInfo {
1150 explicit_uidgid: true,
1151 src_ino: 1,
1152 src_dev: u64::MAX,
1153 rdev: u64::MAX,
1154 path: PathBuf::new(),
1155 source: PathBuf::new(),
1156 target: PathBuf::new(),
1157 target_vec: vec![OsString::new()],
1158 symlink: None,
1159 xattrs: RafsXAttrs::new(),
1160 v6_force_extended_inode: false,
1161 };
1162
1163 let mut inode1 = inode.clone();
1164 inode1.set_size(1 << 60);
1165 inode1.set_mode(0o755 | libc::S_IFREG as u32);
1166 let node = Node::new(inode1, info.clone(), 1);
1167 assert!(node.chunk_count(2).is_err());
1168
1169 let mut inode2 = inode.clone();
1170 inode2.set_mode(0o755 | libc::S_IFCHR as u32);
1171 let node = Node::new(inode2, info.clone(), 1);
1172 assert!(node.chunk_count(2).is_ok());
1173 assert_eq!(node.chunk_count(2).unwrap(), 0);
1174
1175 let mut inode3 = inode.clone();
1176 inode3.set_mode(0o755 | libc::S_IFLNK as u32);
1177 let node = Node::new(inode3, info.clone(), 1);
1178 assert_eq!(node.file_type(), "symlink");
1179 let mut inode4 = inode.clone();
1180 inode4.set_mode(0o755 | libc::S_IFDIR as u32);
1181 let node = Node::new(inode4, info.clone(), 1);
1182 assert_eq!(node.file_type(), "dir");
1183 let mut inode5 = inode.clone();
1184 inode5.set_mode(0o755 | libc::S_IFREG as u32);
1185 let node = Node::new(inode5, info.clone(), 1);
1186 assert_eq!(node.file_type(), "file");
1187
1188 let mut info1 = info.clone();
1189 info1.target_vec = vec![OsString::from("1"), OsString::from("2")];
1190 let node = Node::new(inode.clone(), info1, 1);
1191 assert_eq!(node.name(), OsString::from("2").as_os_str());
1192 let mut info2 = info.clone();
1193 info2.target_vec = vec![];
1194 info2.path = PathBuf::from("/");
1195 info2.source = PathBuf::from("/");
1196 let node = Node::new(inode.clone(), info2, 1);
1197 assert_eq!(node.name(), OsStr::from_bytes(ROOT_PATH_NAME));
1198 let mut info3 = info.clone();
1199 info3.target_vec = vec![];
1200 info3.path = PathBuf::from("/1");
1201 info3.source = PathBuf::from("/11");
1202 let node = Node::new(inode.clone(), info3, 1);
1203 assert_eq!(node.name(), OsStr::new("1"));
1204
1205 let target = PathBuf::from("/root/child");
1206 assert_eq!(
1207 Node::generate_target_vec(&target),
1208 vec![
1209 OsString::from("/"),
1210 OsString::from("root"),
1211 OsString::from("child")
1212 ]
1213 );
1214
1215 let mut node = Node::new(inode, info, 1);
1216 node.set_symlink(OsString::from("symlink"));
1217 assert_eq!(node.info.deref().symlink, Some(OsString::from("symlink")));
1218
1219 let mut xatter = RafsXAttrs::new();
1220 assert!(xatter
1221 .add(OsString::from("user.key"), [1u8; 16].to_vec())
1222 .is_ok());
1223 assert!(xatter
1224 .add(
1225 OsString::from("system.posix_acl_default.key"),
1226 [2u8; 8].to_vec()
1227 )
1228 .is_ok());
1229 node.set_xattr(xatter);
1230 node.inode.set_has_xattr(true);
1231 node.remove_xattr(OsStr::new("user.key"));
1232 assert!(node.inode.has_xattr());
1233 node.remove_xattr(OsStr::new("system.posix_acl_default.key"));
1234 assert!(!node.inode.has_xattr());
1235 }
1236
1237 #[test]
1238 fn test_set_external_chunk_crc32() {
1239 let mut ctx = BuildContext {
1240 crc32_algorithm: crc32::Algorithm::Crc32Iscsi,
1241 attributes: Attributes {
1242 crcs: HashMap::new(),
1243 ..Default::default()
1244 },
1245 ..Default::default()
1246 };
1247 let target = PathBuf::from("/test_file");
1248 ctx.attributes
1249 .crcs
1250 .insert(target.clone(), vec![0x12345678, 0x87654321]);
1251
1252 let node = Node::new(
1253 InodeWrapper::new(RafsVersion::V5),
1254 NodeInfo {
1255 path: target.clone(),
1256 target: target.clone(),
1257 ..Default::default()
1258 },
1259 1,
1260 );
1261
1262 let mut chunk = node.inode.create_chunk();
1263 print!("target: {}", node.target().display());
1264 let result = node.set_external_chunk_crc32(&ctx, &mut chunk, 1);
1265 assert!(result.is_ok());
1266 assert_eq!(chunk.crc32(), 0x87654321);
1267 assert!(chunk.has_crc32());
1268
1269 let result = node.set_external_chunk_crc32(&ctx, &mut chunk, 2);
1271 assert!(result.is_err());
1272 let err = result.unwrap_err().to_string();
1273 assert!(err.contains("invalid crc index 2 for file /test_file"));
1274 }
1275}