use std::any::Any;
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::fs::OpenOptions;
use std::io::{Error, Result};
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use anyhow::bail;
use fuse_backend_rs::abi::fuse_abi::Attr;
use fuse_backend_rs::api::filesystem::{Entry, ROOT_ID};
use nydus_utils::compress;
use nydus_utils::digest::{self, RafsDigest};
use serde::Serialize;
use serde_with::{serde_as, DisplayFromStr};
use storage::device::{BlobChunkInfo, BlobInfo, BlobIoVec};
use self::layout::{XattrName, XattrValue, RAFS_SUPER_VERSION_V5, RAFS_SUPER_VERSION_V6};
use self::noop::NoopSuperBlock;
use crate::fs::{RafsConfig, RAFS_DEFAULT_ATTR_TIMEOUT, RAFS_DEFAULT_ENTRY_TIMEOUT};
use crate::{RafsError, RafsIoReader, RafsIoWrite, RafsResult};
pub mod cached_v5;
pub mod direct_v5;
pub mod direct_v6;
pub mod layout;
mod md_v5;
mod md_v6;
mod noop;
pub use storage::{RAFS_DEFAULT_CHUNK_SIZE, RAFS_MAX_CHUNK_SIZE};
pub const RAFS_BLOB_ID_MAX_LENGTH: usize = 64;
pub const RAFS_ATTR_BLOCK_SIZE: u32 = 4096;
pub const RAFS_MAX_NAME: usize = 255;
pub const RAFS_MAX_METADATA_SIZE: usize = 0x8000_0000;
pub const DOT: &str = ".";
pub const DOTDOT: &str = "..";
pub type Inode = u64;
pub trait RafsSuperInodes {
fn get_max_ino(&self) -> Inode;
fn get_inode(&self, ino: Inode, digest_validate: bool) -> Result<Arc<dyn RafsInode>>;
fn validate_digest(
&self,
inode: Arc<dyn RafsInode>,
recursive: bool,
digester: digest::Algorithm,
) -> Result<bool>;
}
pub trait RafsSuperBlock: RafsSuperInodes + Send + Sync {
fn load(&mut self, r: &mut RafsIoReader) -> Result<()>;
fn update(&self, r: &mut RafsIoReader) -> RafsResult<()>;
fn destroy(&mut self);
fn get_blob_infos(&self) -> Vec<Arc<BlobInfo>>;
fn root_ino(&self) -> u64;
fn get_chunk_info(&self, _idx: usize) -> Result<Arc<dyn BlobChunkInfo>> {
unimplemented!()
}
}
pub enum PostWalkAction {
Continue,
Break,
}
pub type ChildInodeHandler<'a> =
&'a mut dyn FnMut(Option<Arc<dyn RafsInode>>, OsString, u64, u64) -> Result<PostWalkAction>;
pub trait RafsInode: Any {
fn validate(&self, max_inode: Inode, chunk_size: u64) -> Result<()>;
fn get_entry(&self) -> Entry;
fn get_attr(&self) -> Attr;
fn get_name_size(&self) -> u16;
fn get_symlink(&self) -> Result<OsString>;
fn get_symlink_size(&self) -> u16;
fn get_child_by_name(&self, name: &OsStr) -> Result<Arc<dyn RafsInode>>;
fn walk_children_inodes(&self, entry_offset: u64, handler: ChildInodeHandler) -> Result<()>;
fn get_child_by_index(&self, idx: u32) -> Result<Arc<dyn RafsInode>>;
fn get_child_count(&self) -> u32;
fn get_child_index(&self) -> Result<u32>;
fn get_chunk_count(&self) -> u32;
fn get_chunk_info(&self, idx: u32) -> Result<Arc<dyn BlobChunkInfo>>;
fn has_xattr(&self) -> bool;
fn get_xattr(&self, name: &OsStr) -> Result<Option<XattrValue>>;
fn get_xattrs(&self) -> Result<Vec<XattrName>>;
fn is_dir(&self) -> bool;
fn is_symlink(&self) -> bool;
fn is_reg(&self) -> bool;
fn is_hardlink(&self) -> bool;
fn ino(&self) -> u64;
fn name(&self) -> OsString;
fn parent(&self) -> u64;
fn rdev(&self) -> u32;
fn flags(&self) -> u64;
fn projid(&self) -> u32;
fn size(&self) -> u64;
fn is_empty_size(&self) -> bool {
self.size() == 0
}
fn get_digest(&self) -> RafsDigest;
fn collect_descendants_inodes(
&self,
descendants: &mut Vec<Arc<dyn RafsInode>>,
) -> Result<usize>;
fn alloc_bio_vecs(&self, offset: u64, size: usize, user_io: bool) -> Result<Vec<BlobIoVec>>;
fn as_any(&self) -> &dyn Any;
fn walk_chunks(
&self,
cb: &mut dyn FnMut(&dyn BlobChunkInfo) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
let chunk_count = self.get_chunk_count();
for i in 0..chunk_count {
cb(self.get_chunk_info(i)?.as_ref())?;
}
Ok(())
}
}
pub trait RafsStore {
fn store(&self, w: &mut dyn RafsIoWrite) -> Result<usize>;
}
bitflags! {
#[derive(Serialize)]
pub struct RafsSuperFlags: u64 {
const COMPRESS_NONE = 0x0000_0001;
const COMPRESS_LZ4_BLOCK = 0x0000_0002;
const DIGESTER_BLAKE3 = 0x0000_0004;
const DIGESTER_SHA256 = 0x0000_0008;
const EXPLICIT_UID_GID = 0x0000_0010;
const HAS_XATTR = 0x0000_0020;
const COMPRESS_GZIP = 0x0000_0040;
const COMPRESS_ZSTD = 0x0000_0080;
}
}
impl Default for RafsSuperFlags {
fn default() -> Self {
RafsSuperFlags::empty()
}
}
impl Display for RafsSuperFlags {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{:?}", self)?;
Ok(())
}
}
#[serde_as]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct RafsSuperMeta {
pub magic: u32,
pub version: u32,
pub sb_size: u32,
pub root_inode: Inode,
pub chunk_size: u32,
pub inodes_count: u64,
#[serde_as(as = "DisplayFromStr")]
pub flags: RafsSuperFlags,
pub inode_table_entries: u32,
pub inode_table_offset: u64,
pub blob_table_size: u32,
pub blob_table_offset: u64,
pub extended_blob_table_offset: u64,
pub extended_blob_table_entries: u32,
pub blob_readahead_offset: u32,
pub blob_readahead_size: u32,
pub prefetch_table_offset: u64,
pub prefetch_table_entries: u32,
pub attr_timeout: Duration,
pub entry_timeout: Duration,
pub meta_blkaddr: u32,
pub root_nid: u16,
pub is_chunk_dict: bool,
pub chunk_table_offset: u64,
pub chunk_table_size: u64,
}
impl RafsSuperMeta {
pub fn is_v5(&self) -> bool {
self.version == RAFS_SUPER_VERSION_V5
}
pub fn is_v6(&self) -> bool {
self.version == RAFS_SUPER_VERSION_V6
}
pub fn is_chunk_dict(&self) -> bool {
self.is_chunk_dict
}
pub fn explicit_uidgid(&self) -> bool {
self.flags.contains(RafsSuperFlags::EXPLICIT_UID_GID)
}
pub fn has_xattr(&self) -> bool {
self.flags.contains(RafsSuperFlags::HAS_XATTR)
}
pub fn get_compressor(&self) -> compress::Algorithm {
if self.is_v5() || self.is_v6() {
self.flags.into()
} else {
compress::Algorithm::None
}
}
pub fn get_digester(&self) -> digest::Algorithm {
if self.is_v5() || self.is_v6() {
self.flags.into()
} else {
digest::Algorithm::Blake3
}
}
}
impl Default for RafsSuperMeta {
fn default() -> Self {
RafsSuperMeta {
magic: 0,
version: 0,
sb_size: 0,
inodes_count: 0,
root_inode: 0,
chunk_size: 0,
flags: RafsSuperFlags::empty(),
inode_table_entries: 0,
inode_table_offset: 0,
blob_table_size: 0,
blob_table_offset: 0,
extended_blob_table_offset: 0,
extended_blob_table_entries: 0,
blob_readahead_offset: 0,
blob_readahead_size: 0,
prefetch_table_offset: 0,
prefetch_table_entries: 0,
attr_timeout: Duration::from_secs(RAFS_DEFAULT_ATTR_TIMEOUT),
entry_timeout: Duration::from_secs(RAFS_DEFAULT_ENTRY_TIMEOUT),
meta_blkaddr: 0,
root_nid: 0,
is_chunk_dict: false,
chunk_table_offset: 0,
chunk_table_size: 0,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RafsMode {
Direct,
Cached,
}
impl FromStr for RafsMode {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"direct" => Ok(Self::Direct),
"cached" => Ok(Self::Cached),
_ => Err(einval!("rafs mode should be direct or cached")),
}
}
}
impl Display for RafsMode {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Self::Direct => write!(f, "direct"),
Self::Cached => write!(f, "cached"),
}
}
}
pub struct RafsSuper {
pub mode: RafsMode,
pub validate_digest: bool,
pub meta: RafsSuperMeta,
pub superblock: Arc<dyn RafsSuperBlock>,
}
impl Default for RafsSuper {
fn default() -> Self {
Self {
mode: RafsMode::Direct,
validate_digest: false,
meta: RafsSuperMeta::default(),
superblock: Arc::new(NoopSuperBlock::new()),
}
}
}
impl RafsSuper {
pub fn new(conf: &RafsConfig) -> Result<Self> {
Ok(Self {
mode: RafsMode::from_str(conf.mode.as_str())?,
validate_digest: conf.digest_validate,
..Default::default()
})
}
pub fn destroy(&mut self) {
Arc::get_mut(&mut self.superblock)
.expect("Inodes are no longer used.")
.destroy();
}
pub fn load_from_metadata<P: AsRef<Path>>(
path: P,
mode: RafsMode,
validate_digest: bool,
) -> Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(false)
.open(path.as_ref())?;
let mut rs = RafsSuper {
mode,
validate_digest,
..Default::default()
};
let mut reader = Box::new(file) as RafsIoReader;
rs.load(&mut reader)?;
Ok(rs)
}
pub fn load_chunk_dict_from_metadata(path: &Path) -> Result<Self> {
let file = OpenOptions::new().read(true).write(false).open(path)?;
let mut rs = RafsSuper {
mode: RafsMode::Direct,
validate_digest: true,
..Default::default()
};
let mut reader = Box::new(file) as RafsIoReader;
rs.meta.is_chunk_dict = true;
rs.load(&mut reader)?;
Ok(rs)
}
pub fn load(&mut self, r: &mut RafsIoReader) -> Result<()> {
if self.try_load_v5(r)? {
return Ok(());
}
if self.try_load_v6(r)? {
return Ok(());
}
Err(einval!("invalid superblock version number"))
}
pub fn update(&self, r: &mut RafsIoReader) -> RafsResult<()> {
if self.meta.is_v5() {
self.skip_v5_superblock(r)
.map_err(RafsError::FillSuperblock)?;
}
self.superblock.update(r)
}
pub fn store(&self, w: &mut dyn RafsIoWrite) -> Result<usize> {
if self.meta.is_v5() {
return self.store_v5(w);
}
Err(einval!("invalid superblock version number"))
}
pub fn get_inode(&self, ino: Inode, digest_validate: bool) -> Result<Arc<dyn RafsInode>> {
self.superblock.get_inode(ino, digest_validate)
}
pub fn get_max_ino(&self) -> Inode {
self.superblock.get_max_ino()
}
pub fn path_from_ino(&self, ino: Inode) -> Result<PathBuf> {
if ino == ROOT_ID {
return Ok(self.get_inode(ino, false)?.name().into());
}
let mut path = PathBuf::new();
let mut cur_ino = ino;
let mut inode;
loop {
inode = self.get_inode(cur_ino, false)?;
let e: PathBuf = inode.name().into();
path = e.join(path);
if inode.ino() == ROOT_ID {
break;
} else {
cur_ino = inode.parent();
}
}
Ok(path)
}
pub fn ino_from_path(&self, f: &Path) -> Result<u64> {
let root_ino = self.superblock.root_ino();
if f == Path::new("/") {
return Ok(root_ino);
}
if !f.starts_with("/") {
return Err(einval!());
}
let mut parent = self.get_inode(root_ino, self.validate_digest)?;
let entries = f
.components()
.filter(|comp| *comp != Component::RootDir)
.map(|comp| match comp {
Component::Normal(name) => Some(name),
Component::ParentDir => Some(OsStr::from_bytes(DOTDOT.as_bytes())),
Component::CurDir => Some(OsStr::from_bytes(DOT.as_bytes())),
_ => None,
})
.collect::<Vec<_>>();
if entries.is_empty() {
warn!("Path can't be parsed {:?}", f);
return Err(enoent!());
}
for p in entries {
if p.is_none() {
error!("Illegal specified path {:?}", f);
return Err(einval!());
}
match parent.get_child_by_name(p.unwrap()) {
Ok(p) => parent = p,
Err(_) => {
warn!("File {:?} not in rafs", p.unwrap());
return Err(enoent!());
}
}
}
Ok(parent.ino())
}
pub fn prefetch_files(
&self,
r: &mut RafsIoReader,
files: Option<Vec<Inode>>,
fetcher: &dyn Fn(&mut BlobIoVec),
) -> RafsResult<()> {
if let Some(files) = files {
let mut hardlinks: HashSet<u64> = HashSet::new();
let mut head_desc = BlobIoVec {
bi_size: 0,
bi_flags: 0,
bi_vec: Vec::new(),
};
for f_ino in files {
self.prefetch_data(f_ino, &mut head_desc, &mut hardlinks, fetcher)
.map_err(|e| RafsError::Prefetch(e.to_string()))?;
}
fetcher(&mut head_desc);
Ok(())
} else if self.meta.is_v5() {
self.prefetch_data_v5(r, fetcher).map(|_| ())
} else if self.meta.is_v6() {
self.prefetch_data_v6(r, fetcher).map(|_| ())
} else {
Err(RafsError::Prefetch(
"Unknown filesystem version, prefetch disabled".to_string(),
))
}
}
#[inline]
fn prefetch_inode<F>(
inode: &Arc<dyn RafsInode>,
head_desc: &mut BlobIoVec,
hardlinks: &mut HashSet<u64>,
prefetcher: F,
) -> Result<()>
where
F: Fn(&mut BlobIoVec, bool),
{
if inode.is_hardlink() {
if hardlinks.contains(&inode.ino()) {
return Ok(());
} else {
hardlinks.insert(inode.ino());
}
}
let descs = inode.alloc_bio_vecs(0, inode.size() as usize, false)?;
for desc in descs {
if !head_desc.has_same_blob(&desc) {
prefetcher(head_desc, true);
}
head_desc.append(desc);
prefetcher(head_desc, false);
}
Ok(())
}
fn prefetch_data<F>(
&self,
ino: u64,
head_desc: &mut BlobIoVec,
hardlinks: &mut HashSet<u64>,
fetcher: F,
) -> Result<()>
where
F: Fn(&mut BlobIoVec),
{
let try_prefetch = |desc: &mut BlobIoVec, flush: bool| {
if flush || desc.bi_size >= (4 * RAFS_DEFAULT_CHUNK_SIZE) as usize {
trace!("fetching head bio size {}", desc.bi_size);
fetcher(desc);
desc.reset();
}
};
let inode = self
.superblock
.get_inode(ino, self.validate_digest)
.map_err(|_e| enoent!("Can't find inode"))?;
if inode.is_dir() {
let mut descendants = Vec::new();
let _ = inode.collect_descendants_inodes(&mut descendants)?;
for i in descendants.iter() {
Self::prefetch_inode(i, head_desc, hardlinks, try_prefetch)?;
}
} else if !inode.is_empty_size() && inode.is_reg() {
Self::prefetch_inode(&inode, head_desc, hardlinks, try_prefetch)?;
}
Ok(())
}
pub fn walk_dir(
&self,
ino: Inode,
parent: Option<&PathBuf>,
cb: &mut dyn FnMut(&dyn RafsInode, &Path) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
let inode = self.get_inode(ino, false)?;
if !inode.is_dir() {
bail!("inode {} is not a directory", ino);
}
self.walk_dir_inner(inode.as_ref(), parent, cb)
}
fn walk_dir_inner(
&self,
inode: &dyn RafsInode,
parent: Option<&PathBuf>,
cb: &mut dyn FnMut(&dyn RafsInode, &Path) -> anyhow::Result<()>,
) -> anyhow::Result<()> {
let path = if let Some(parent) = parent {
parent.join(inode.name())
} else {
PathBuf::from("/")
};
cb(inode, &path)?;
if !inode.is_dir() {
return Ok(());
}
let child_count = inode.get_child_count();
for idx in 0..child_count {
let child = inode.get_child_by_index(idx)?;
self.walk_dir_inner(child.as_ref(), Some(&path), cb)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rafs_mode() {
assert!(RafsMode::from_str("").is_err());
assert!(RafsMode::from_str("directed").is_err());
assert!(RafsMode::from_str("Direct").is_err());
assert!(RafsMode::from_str("Cached").is_err());
assert_eq!(RafsMode::from_str("direct").unwrap(), RafsMode::Direct);
assert_eq!(RafsMode::from_str("cached").unwrap(), RafsMode::Cached);
assert_eq!(&format!("{}", RafsMode::Direct), "direct");
assert_eq!(&format!("{}", RafsMode::Cached), "cached");
}
}