use std::ffi::OsStr;
use std::ffi::OsString;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use anyhow::Result;
use nydus_rafs::metadata::chunk::ChunkWrapper;
use nydus_rafs::metadata::inode::InodeWrapper;
use nydus_rafs::metadata::layout::{bytes_to_os_str, RafsXAttrs};
use nydus_rafs::metadata::{Inode, RafsInodeExt, RafsSuper};
use super::chunk_dict::ChunkDict;
use super::node::{ChunkSource, Node, NodeChunk, Overlay, WhiteoutSpec, WhiteoutType};
#[derive(Clone)]
pub struct Tree {
pub node: Node,
pub children: Vec<Tree>,
}
impl Tree {
pub fn new(node: Node) -> Self {
Tree {
node,
children: Vec::new(),
}
}
pub fn from_bootstrap<T: ChunkDict>(rs: &RafsSuper, chunk_dict: &mut T) -> Result<Self> {
let tree_builder = MetadataTreeBuilder::new(rs);
let root_inode = rs.get_extended_inode(rs.superblock.root_ino(), true)?;
let root_node =
MetadataTreeBuilder::parse_node(rs, root_inode.deref(), PathBuf::from("/"))?;
let mut tree = Tree::new(root_node);
tree.children = timing_tracer!(
{ tree_builder.load_children(rs.superblock.root_ino(), None, chunk_dict, true) },
"load_tree_from_bootstrap"
)?;
Ok(tree)
}
pub fn iterate<F>(&self, cb: &mut F) -> Result<()>
where
F: FnMut(&Node) -> bool,
{
if !cb(&self.node) {
return Ok(());
}
for child in &self.children {
child.iterate(cb)?;
}
Ok(())
}
pub fn apply(
&mut self,
target: &Node,
handle_whiteout: bool,
whiteout_spec: WhiteoutSpec,
) -> Result<bool> {
if handle_whiteout {
if let Some(whiteout_type) = target.whiteout_type(whiteout_spec) {
let origin_name = target.origin_name(whiteout_type);
let parent_name = if let Some(parent_path) = target.path().parent() {
parent_path.file_name()
} else {
None
};
event_tracer!("whiteout_files", +1);
if whiteout_type == WhiteoutType::OverlayFsOpaque {
self.remove(target, whiteout_type, origin_name, parent_name)?;
return self.apply(target, false, whiteout_spec);
}
return self.remove(target, whiteout_type, origin_name, parent_name);
}
}
let target_paths = target.target_vec();
let target_paths_len = target_paths.len();
let depth = self.node.target_vec().len();
if target.path() == Path::new("/") {
let mut node = target.clone();
node.overlay = Overlay::UpperModification;
self.node = node;
return Ok(true);
}
if depth < target_paths_len {
for child in self.children.iter_mut() {
if target_paths[depth] != child.node.name() {
continue;
}
if depth == target_paths_len - 1 {
let mut node = target.clone();
node.overlay = Overlay::UpperModification;
child.node = node;
return Ok(true);
}
if child.node.is_dir() {
let found = child.apply(target, handle_whiteout, whiteout_spec)?;
if found {
return Ok(true);
}
}
}
}
if depth == target_paths_len - 1 && target_paths[depth - 1] == self.node.name() {
let mut node = target.clone();
node.overlay = Overlay::UpperAddition;
self.children.push(Tree {
node,
children: Vec::new(),
});
return Ok(true);
}
Ok(false)
}
fn remove(
&mut self,
target: &Node,
whiteout_type: WhiteoutType,
origin_name: Option<&OsStr>,
parent_name: Option<&OsStr>,
) -> Result<bool> {
let target_paths = target.target_vec();
let target_paths_len = target_paths.len();
let node_paths = self.node.target_vec();
let depth = node_paths.len();
if depth >= target_paths_len || node_paths[depth - 1] != target_paths[depth - 1] {
return Ok(false);
}
if depth == 1
&& (whiteout_type == WhiteoutType::OciOpaque && target_paths_len == 2
|| whiteout_type == WhiteoutType::OverlayFsOpaque && target_paths_len == 1)
{
self.node.overlay = Overlay::UpperOpaque;
self.children.clear();
return Ok(true);
}
for idx in 0..self.children.len() {
let child = &mut self.children[idx];
if depth == target_paths_len - 1
&& whiteout_type.is_removal()
&& origin_name == Some(child.node.name())
{
self.children.remove(idx);
return Ok(true);
}
if whiteout_type == WhiteoutType::OciOpaque
&& target_paths_len >= 2
&& depth == target_paths_len - 2
{
if let Some(parent_name) = parent_name {
if parent_name == child.node.name() {
child.node.overlay = Overlay::UpperOpaque;
child.children.clear();
return Ok(true);
}
}
} else if whiteout_type == WhiteoutType::OverlayFsOpaque
&& depth == target_paths_len - 1
&& target.name() == child.node.name()
{
child.node.overlay = Overlay::UpperOpaque;
child.children.clear();
return Ok(true);
}
if child.node.is_dir() {
let found = child.remove(target, whiteout_type, origin_name, parent_name)?;
if found {
return Ok(true);
}
}
}
Ok(false)
}
}
pub struct MetadataTreeBuilder<'a> {
rs: &'a RafsSuper,
}
impl<'a> MetadataTreeBuilder<'a> {
fn new(rs: &'a RafsSuper) -> Self {
Self { rs }
}
fn load_children<T: ChunkDict>(
&self,
ino: Inode,
parent: Option<&PathBuf>,
chunk_dict: &mut T,
validate_digest: bool,
) -> Result<Vec<Tree>> {
let inode = self.rs.get_extended_inode(ino, validate_digest)?;
if !inode.is_dir() {
return Ok(Vec::new());
}
let parent_path = if let Some(parent) = parent {
parent.join(inode.name())
} else {
PathBuf::from("/")
};
let blobs = self.rs.superblock.get_blob_infos();
let child_count = inode.get_child_count();
let mut children = Vec::with_capacity(child_count as usize);
event_tracer!("load_from_parent_bootstrap", +child_count);
for idx in 0..child_count {
let child = inode.get_child_by_index(idx)?;
let child_ino = child.ino();
let child_path = parent_path.join(child.name());
let child = Self::parse_node(self.rs, child.deref(), child_path)?;
if child.is_reg() {
for chunk in &child.chunks {
let blob_idx = chunk.inner.blob_index();
if let Some(blob) = blobs.get(blob_idx as usize) {
chunk_dict.add_chunk(chunk.inner.clone(), blob.digester());
}
}
}
let mut child = Tree::new(child);
if child.node.is_dir() {
child.children =
self.load_children(child_ino, Some(&parent_path), chunk_dict, validate_digest)?;
}
children.push(child);
}
Ok(children)
}
pub fn parse_node(rs: &RafsSuper, inode: &dyn RafsInodeExt, path: PathBuf) -> Result<Node> {
let chunks = if inode.is_reg() {
let chunk_count = inode.get_chunk_count();
let mut chunks = Vec::with_capacity(chunk_count as usize);
for i in 0..chunk_count {
let cki = inode.get_chunk_info(i)?;
chunks.push(NodeChunk {
source: ChunkSource::Parent,
inner: ChunkWrapper::from_chunk_info(cki.as_ref()),
});
}
chunks
} else {
Vec::new()
};
let symlink = if inode.is_symlink() {
Some(inode.get_symlink()?)
} else {
None
};
let mut xattrs = RafsXAttrs::new();
for name in inode.get_xattrs()? {
let name = bytes_to_os_str(&name);
let value = inode.get_xattr(name)?;
xattrs.add(name.to_os_string(), value.unwrap_or_default())?;
}
let src_dev = u64::MAX;
let inode_wrapper = InodeWrapper::from_inode_info(inode);
let source = PathBuf::from("/");
let target = Node::generate_target(&path, &source);
let target_vec = Node::generate_target_vec(&target);
Ok(Node {
index: 0,
src_ino: inode_wrapper.ino(),
src_dev,
rdev: inode.rdev() as u64,
overlay: Overlay::Lower,
explicit_uidgid: rs.meta.explicit_uidgid(),
source,
target,
path,
target_vec,
inode: inode_wrapper,
chunks,
symlink,
xattrs,
layer_idx: 0,
ctime: 0,
v6_offset: 0,
v6_dirents: Vec::<(u64, OsString, u32)>::new(),
v6_datalayout: 0,
v6_compact_inode: false,
v6_force_extended_inode: false,
v6_dirents_offset: 0,
})
}
}