use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{Result, SeekFrom};
use std::mem::size_of;
use std::ops::Deref;
use std::os::unix::{
ffi::{OsStrExt, OsStringExt},
io::{FromRawFd, IntoRawFd, RawFd},
};
use std::slice;
use std::sync::Arc;
use arc_swap::{ArcSwap, Guard};
use crate::metadata::layout::MetaRange;
use crate::metadata::{
layout::{
bytes_to_os_str,
v5::RafsV5ChunkInfo,
v6::{
recover_namespace, RafsV6BlobTable, RafsV6Dirent, RafsV6InodeChunkAddr,
RafsV6InodeCompact, RafsV6InodeExtended, RafsV6OndiskInode, RafsV6XattrEntry,
RafsV6XattrIbodyHeader, EROFS_BLOCK_SIZE, EROFS_INODE_CHUNK_BASED,
EROFS_INODE_FLAT_INLINE, EROFS_INODE_FLAT_PLAIN, EROFS_INODE_SLOT_SIZE,
EROFS_I_DATALAYOUT_BITS, EROFS_I_VERSION_BIT, EROFS_I_VERSION_BITS,
},
XattrName, XattrValue,
},
{
Attr, ChildInodeHandler, Entry, Inode, PostWalkAction, RafsInode, RafsSuperBlock,
RafsSuperInodes, RafsSuperMeta, RAFS_ATTR_BLOCK_SIZE, RAFS_MAX_NAME,
},
};
use crate::{MetaType, RafsError, RafsIoReader, RafsResult};
use nydus_utils::{
digest::{Algorithm, RafsDigest},
div_round_up, round_up,
};
use storage::device::{
v5::BlobV5ChunkInfo, BlobChunkFlags, BlobChunkInfo, BlobInfo, BlobIoChunk, BlobIoDesc,
BlobIoVec,
};
use storage::utils::readahead;
thread_local! {
static CHUNK_DICT_MAP: RefCell<Option<HashMap<RafsV6InodeChunkAddr, Arc<dyn BlobChunkInfo>>>> = RefCell::new(None);
}
fn err_invalidate_data(rafs_err: RafsError) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidData, rafs_err)
}
#[derive(Clone)]
struct DirectMappingState {
meta: RafsSuperMeta,
blob_table: Arc<RafsV6BlobTable>,
base: *const u8,
end: *const u8,
size: usize,
fd: RawFd,
validate_digest: bool,
}
unsafe impl Send for DirectMappingState {}
unsafe impl Sync for DirectMappingState {}
impl DirectMappingState {
fn new(meta: &RafsSuperMeta, validate_digest: bool) -> Self {
DirectMappingState {
meta: *meta,
blob_table: Arc::new(RafsV6BlobTable::default()),
fd: -1,
base: std::ptr::null(),
end: std::ptr::null(),
size: 0,
validate_digest,
}
}
fn cast_to_ref<T>(&self, base: *const u8, offset: usize) -> Result<&T> {
let start = base.wrapping_add(offset);
let end = start.wrapping_add(size_of::<T>());
if start > end
|| start < self.base
|| end < self.base
|| end > self.end
|| start as usize & (std::mem::align_of::<T>() - 1) != 0
{
return Err(einval!("invalid mmap offset"));
}
Ok(unsafe { &*(start as *const T) })
}
#[inline]
fn validate_range(&self, offset: usize, size: usize) -> Result<()> {
let start = self.base.wrapping_add(offset);
let end = start.wrapping_add(size);
if start > end || start < self.base || end < self.base || end > self.end {
return Err(einval!("invalid range"));
}
Ok(())
}
}
impl Drop for DirectMappingState {
fn drop(&mut self) {
if !self.base.is_null() {
unsafe { libc::munmap(self.base as *mut u8 as *mut libc::c_void, self.size) };
self.base = std::ptr::null();
self.end = std::ptr::null();
self.size = 0;
}
if self.fd >= 0 {
let _ = nix::unistd::close(self.fd);
self.fd = -1;
}
}
}
#[derive(Clone)]
pub struct DirectSuperBlockV6 {
state: ArcSwap<DirectMappingState>,
}
impl DirectSuperBlockV6 {
pub fn new(meta: &RafsSuperMeta, validate_digest: bool) -> Self {
CHUNK_DICT_MAP.with(|dict| *dict.borrow_mut() = None);
let state = DirectMappingState::new(meta, validate_digest);
Self {
state: ArcSwap::new(Arc::new(state)),
}
}
fn disk_inode(&self, offset: usize) -> &dyn RafsV6OndiskInode {
let m = self.state.load();
let i = unsafe { &*(m.base.add(offset) as *const RafsV6InodeExtended) };
if i.format() & EROFS_I_VERSION_BITS != 0 {
i
} else {
unsafe { &*(m.base.add(offset) as *const RafsV6InodeCompact) }
}
}
fn inode_wrapper(&self, nid: u64) -> Result<OndiskInodeWrapper> {
let offset = self.calculate_inode_offset(nid) as usize;
let inode = self.disk_inode(offset);
let blocks_count = div_round_up(inode.size(), EROFS_BLOCK_SIZE);
Ok(OndiskInodeWrapper {
mapping: self.clone(),
offset,
blocks_count,
parent_inode: Cell::new(None),
name: RefCell::new(None),
})
}
fn inode_wrapper_with_info(
&self,
nid: u64,
parent_inode: Inode,
name: OsString,
) -> Result<OndiskInodeWrapper> {
self.inode_wrapper(nid).map(|inode| {
let mut inode = inode;
inode.parent_inode = Cell::new(Some(parent_inode));
inode.name = RefCell::new(Some(name));
inode
})
}
fn calculate_inode_offset(&self, nid: u64) -> u64 {
let meta_offset = self.state.load().meta.meta_blkaddr as u64 * EROFS_BLOCK_SIZE;
meta_offset + nid * EROFS_INODE_SLOT_SIZE as u64
}
#[allow(clippy::cast_ptr_alignment)]
fn update_state(&self, r: &mut RafsIoReader) -> Result<()> {
let old_state = self.state.load();
let fd = unsafe { libc::dup(r.as_raw_fd()) };
if fd < 0 {
return Err(last_error!("failed to dup bootstrap file fd"));
}
let file = unsafe { File::from_raw_fd(fd) };
let md = file.metadata()?;
let len = md.len();
let size = len as usize;
let md_range =
MetaRange::new(EROFS_BLOCK_SIZE as u64, len - EROFS_BLOCK_SIZE as u64, true)?;
let blob_table_size = old_state.meta.blob_table_size as u64;
let blob_table_start = old_state.meta.blob_table_offset;
let blob_table_range = MetaRange::new(blob_table_start, blob_table_size, false)?;
if !blob_table_range.is_subrange_of(&md_range) {
return Err(ebadf!("invalid blob table"));
}
readahead(fd, 0, len);
let base = unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ,
libc::MAP_NORESERVE | libc::MAP_PRIVATE,
fd,
0,
)
} as *const u8;
if base as *mut core::ffi::c_void == libc::MAP_FAILED {
return Err(last_error!("failed to mmap bootstrap"));
}
if base.is_null() {
return Err(ebadf!("failed to mmap bootstrap"));
}
let end = unsafe { base.add(size) };
let mut blob_table = RafsV6BlobTable::new();
let meta = &old_state.meta;
r.seek(SeekFrom::Start(meta.blob_table_offset))?;
blob_table.load(r, meta.blob_table_size, meta.chunk_size, meta.flags)?;
let validate_digest = old_state.validate_digest;
let state = DirectMappingState {
meta: old_state.meta,
blob_table: Arc::new(blob_table),
fd: file.into_raw_fd(),
base,
end,
size,
validate_digest,
};
self.state.store(Arc::new(state));
Ok(())
}
fn load_chunk_map(&self) -> Result<HashMap<RafsV6InodeChunkAddr, Arc<dyn BlobChunkInfo>>> {
let mut chunk_dict: HashMap<RafsV6InodeChunkAddr, Arc<dyn BlobChunkInfo>> =
HashMap::default();
let state = self.state.load();
let size = state.meta.chunk_table_size as usize;
if size == 0 {
return Ok(chunk_dict);
}
let unit_size = size_of::<RafsV5ChunkInfo>();
if size % unit_size != 0 {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}
for idx in 0..(size / unit_size) {
let chunk = self.get_chunk_info(idx)?;
let mut v6_chunk = RafsV6InodeChunkAddr::new();
v6_chunk.set_blob_index((chunk.blob_index() + 1) as u8);
v6_chunk.set_blob_comp_index(chunk.id());
v6_chunk.set_block_addr((chunk.uncompress_offset() / EROFS_BLOCK_SIZE) as u32);
chunk_dict.insert(v6_chunk, chunk);
}
Ok(chunk_dict)
}
}
impl RafsSuperInodes for DirectSuperBlockV6 {
fn get_max_ino(&self) -> Inode {
0xff_ffff_ffff_ffff - 1
}
fn get_inode(&self, ino: Inode, _validate_digest: bool) -> Result<Arc<dyn RafsInode>> {
Ok(Arc::new(self.inode_wrapper(ino)?) as Arc<dyn RafsInode>)
}
fn validate_digest(
&self,
_inode: Arc<dyn RafsInode>,
_recursive: bool,
_digester: Algorithm,
) -> Result<bool> {
Ok(true)
}
}
impl RafsSuperBlock for DirectSuperBlockV6 {
fn load(&mut self, r: &mut RafsIoReader) -> Result<()> {
self.update_state(r)
}
fn update(&self, r: &mut RafsIoReader) -> RafsResult<()> {
self.update_state(r).map_err(RafsError::SwapBackend)
}
fn destroy(&mut self) {
let state = DirectMappingState::new(&RafsSuperMeta::default(), false);
self.state.store(Arc::new(state));
}
fn get_blob_infos(&self) -> Vec<Arc<BlobInfo>> {
self.state.load().blob_table.entries.clone()
}
fn root_ino(&self) -> u64 {
self.state.load().meta.root_nid as u64
}
fn get_chunk_info(&self, idx: usize) -> Result<Arc<dyn BlobChunkInfo>> {
let state = self.state.load();
let unit_size = size_of::<RafsV5ChunkInfo>();
let offset = state.meta.chunk_table_offset as usize + idx * unit_size;
if offset + unit_size
> (state.meta.chunk_table_offset + state.meta.chunk_table_size) as usize
{
return Err(einval!(format!(
"invalid chunk offset {} chunk table {} {}",
offset, state.meta.chunk_table_offset, state.meta.chunk_table_size
)));
}
let chunk = state.cast_to_ref::<RafsV5ChunkInfo>(state.base, offset)?;
let wrapper = DirectChunkInfoV6::new(chunk, self.clone(), offset);
Ok(Arc::new(wrapper) as Arc<dyn BlobChunkInfo>)
}
}
pub struct OndiskInodeWrapper {
pub mapping: DirectSuperBlockV6,
pub offset: usize,
pub blocks_count: u64,
parent_inode: Cell<Option<Inode>>,
name: RefCell<Option<OsString>>,
}
impl OndiskInodeWrapper {
fn disk_inode(&self) -> &dyn RafsV6OndiskInode {
self.mapping.disk_inode(self.offset)
}
fn blocks_count(&self) -> u64 {
self.blocks_count
}
fn data_block_mapping(&self, index: usize) -> RafsResult<*const u8> {
let inode = self.disk_inode();
if (inode.format() & (!(((1 << EROFS_I_DATALAYOUT_BITS) - 1) << 1 | EROFS_I_VERSION_BITS)))
!= 0
{
return Err(RafsError::Incompatible(inode.format()));
}
let s = self.this_inode_size();
let layout = inode.format() >> EROFS_I_VERSION_BITS;
let m = self.mapping.state.load();
let r = match layout {
EROFS_INODE_FLAT_PLAIN => {
unsafe {
m.base.add(
(inode.union() as u64 * EROFS_BLOCK_SIZE) as usize
+ index * EROFS_BLOCK_SIZE as usize,
)
}
}
EROFS_INODE_FLAT_INLINE => {
if index as u64 != self.blocks_count() - 1 {
unsafe {
m.base.add(
(inode.union() as u64 * EROFS_BLOCK_SIZE) as usize
+ index * EROFS_BLOCK_SIZE as usize,
)
}
} else {
unsafe {
m.base
.add(self.offset as usize + s + self.xattr_size() as usize)
}
}
}
_ => {
panic!("layout is {}", layout)
}
};
Ok(r)
}
fn get_entry(&self, block_index: usize, index: usize) -> RafsResult<&RafsV6Dirent> {
let block_mapping = self.data_block_mapping(block_index)?;
Ok(unsafe {
&*(block_mapping.add(size_of::<RafsV6Dirent>() * index) as *const RafsV6Dirent)
})
}
fn entry_name(
&self,
block_index: usize,
index: usize,
max_entries: usize,
) -> RafsResult<&OsStr> {
let block_mapping = self.data_block_mapping(block_index)?;
let de = self.get_entry(block_index, index)?;
if index < max_entries - 1 {
let next_de = self.get_entry(block_index, index + 1)?;
let (next_de_name_off, de_name_off) = (next_de.e_nameoff, de.e_nameoff);
let len = next_de.e_nameoff.checked_sub(de.e_nameoff).ok_or_else(|| {
error!(
"nid {} entry index {} block index {} next dir entry {:?} current dir entry {:?}",
self.ino(), index, block_index, next_de, de
);
RafsError::IllegalMetaStruct(
MetaType::Dir,
format!("cur {} next {}", next_de_name_off, de_name_off),
)
})?;
let n = unsafe {
bytes_to_os_str(std::slice::from_raw_parts(
block_mapping.add(de.e_nameoff as usize),
len as usize,
))
};
Ok(n)
} else {
unsafe {
let head_de = self.get_entry(block_index, 0)?;
let s = (de.e_nameoff - head_de.e_nameoff) as u64
+ (size_of::<RafsV6Dirent>() * max_entries) as u64;
let len = if div_round_up(self.size(), EROFS_BLOCK_SIZE) as usize == block_index + 1
{
(self.size() % EROFS_BLOCK_SIZE - s) as usize
} else {
(EROFS_BLOCK_SIZE - s) as usize
};
let e = slice::from_raw_parts(block_mapping.add(de.e_nameoff as usize), len);
let mut l: usize = 0;
for i in e {
if *i != 0 {
l += 1;
if len == l {
break;
}
} else {
break;
}
}
let n = bytes_to_os_str(&e[0..l]);
Ok(n)
}
}
}
fn mode_format_bits(&self) -> u32 {
let i = self.disk_inode();
i.mode() as u32 & libc::S_IFMT as u32
}
fn make_chunk_io(
&self,
chunk_addr: &RafsV6InodeChunkAddr,
content_offset: u32,
content_len: u32,
user_io: bool,
) -> BlobIoDesc {
let state = self.mapping.state.load();
let blob_table = &state.blob_table.entries;
let blob_index = chunk_addr.blob_index() - 1;
let chunk_index = chunk_addr.blob_comp_index();
let io_chunk = BlobIoChunk::Address(blob_index as u32, chunk_index);
let blob = blob_table[blob_index as usize].clone();
BlobIoDesc::new(
blob,
io_chunk,
content_offset,
content_len as usize,
user_io,
)
}
fn chunk_size(&self) -> u32 {
self.mapping.state.load().meta.chunk_size
}
fn this_inode_size(&self) -> usize {
let inode = self.disk_inode();
if (inode.format() & 1 << EROFS_I_VERSION_BIT) != 0 {
size_of::<RafsV6InodeExtended>()
} else {
size_of::<RafsV6InodeCompact>()
}
}
fn xattr_size(&self) -> u32 {
let inode = self.disk_inode();
if inode.xattr_inline_count() > 0 {
(inode.xattr_inline_count() as u32 - 1) * size_of::<RafsV6XattrEntry>() as u32
+ size_of::<RafsV6XattrIbodyHeader>() as u32
} else {
0
}
}
fn chunk_addresses(&self, head_chunk_index: u32) -> RafsResult<&[RafsV6InodeChunkAddr]> {
let total_chunk_addresses = div_round_up(self.size(), self.chunk_size() as u64) as u32;
assert_eq!(
self.disk_inode().format() >> EROFS_I_VERSION_BITS,
EROFS_INODE_CHUNK_BASED
);
let m = self.mapping.state.load();
let indices = unsafe {
m.base.add(
self.offset as usize
+ round_up(
self.this_inode_size() as u64 + self.xattr_size() as u64,
size_of::<RafsV6InodeChunkAddr>() as u64,
) as usize,
)
};
let chunks: &[RafsV6InodeChunkAddr] = unsafe {
std::slice::from_raw_parts(
indices.add(head_chunk_index as usize * size_of::<RafsV6InodeChunkAddr>())
as *const RafsV6InodeChunkAddr,
(total_chunk_addresses - head_chunk_index) as usize,
)
};
Ok(chunks)
}
}
#[allow(unused_variables)]
impl RafsInode for OndiskInodeWrapper {
#[allow(clippy::collapsible_if)]
fn validate(&self, _inode_count: u64, chunk_size: u64) -> Result<()> {
let state = self.mapping.state.load();
let inode = self.disk_inode();
let max_inode = self.mapping.get_max_ino();
if self.ino() > max_inode
|| inode.nlink() == 0
|| self.get_name_size() as usize > (RAFS_MAX_NAME + 1)
{
return Err(ebadf!(format!(
"inode validation failure, inode {:?}",
inode
)));
}
let xattr_size = self.xattr_size() as usize;
if self.is_reg() {
if state.meta.is_chunk_dict() {
return Err(std::io::Error::from_raw_os_error(libc::EOPNOTSUPP));
}
let size = round_up(
self.this_inode_size() as u64 + xattr_size as u64,
size_of::<RafsV6InodeChunkAddr>() as u64,
) as usize
+ div_round_up(self.size(), self.chunk_size() as u64) as usize
* size_of::<RafsV6InodeChunkAddr>();
state.validate_range(self.offset, size)?;
} else if self.is_dir() {
if self.get_child_count() as u64 >= max_inode {
return Err(einval!("invalid directory"));
}
let size = self.this_inode_size() + xattr_size;
state.validate_range(self.offset, size)?;
} else if self.is_symlink() {
if self.size() == 0 {
return Err(einval!("invalid symlink target"));
}
}
Ok(())
}
fn get_entry(&self) -> Entry {
let state = self.mapping.state.load();
let inode = self.disk_inode();
Entry {
attr: self.get_attr().into(),
inode: self.ino(),
generation: 0,
attr_timeout: state.meta.attr_timeout,
entry_timeout: state.meta.entry_timeout,
..Default::default()
}
}
fn get_attr(&self) -> Attr {
let inode = self.disk_inode();
Attr {
ino: self.ino(),
size: inode.size(),
mode: inode.mode() as u32,
nlink: inode.nlink(),
blocks: div_round_up(inode.size(), 512),
uid: inode.ugid().0,
gid: inode.ugid().1,
mtime: inode.mtime_s_ns().0,
mtimensec: inode.mtime_s_ns().1,
blksize: RAFS_ATTR_BLOCK_SIZE,
..Default::default()
}
}
fn has_xattr(&self) -> bool {
self.disk_inode().xattr_inline_count() > 0
}
fn get_symlink(&self) -> Result<OsString> {
let inode = self.disk_inode();
let data = self.data_block_mapping(0).map_err(err_invalidate_data)?;
let s = unsafe {
bytes_to_os_str(std::slice::from_raw_parts(data, inode.size() as usize)).to_os_string()
};
Ok(s)
}
fn get_child_by_name(&self, name: &OsStr) -> Result<Arc<dyn RafsInode>> {
let inode = self.disk_inode();
if inode.size() == 0 {
return Err(enoent!());
}
let blocks_count = div_round_up(inode.size(), EROFS_BLOCK_SIZE);
let mut target: Option<u64> = None;
let nid = 'outer: for i in 0..blocks_count {
let head_entry = self.get_entry(i as usize, 0).map_err(err_invalidate_data)?;
let head_name_offset = head_entry.e_nameoff;
let entries_count = head_name_offset / size_of::<RafsV6Dirent>() as u16;
let d_name = self
.entry_name(i as usize, 0, entries_count as usize)
.map_err(err_invalidate_data)?;
if d_name == name {
target = Some(head_entry.e_nid);
break 'outer;
}
for j in 1..entries_count {
let de = self
.get_entry(i as usize, j as usize)
.map_err(err_invalidate_data)?;
let d_name = self
.entry_name(i as usize, j as usize, entries_count as usize)
.map_err(err_invalidate_data)?;
if d_name == name {
target = Some(de.e_nid);
break 'outer;
}
}
};
if let Some(nid) = target {
Ok(Arc::new(self.mapping.inode_wrapper_with_info(
nid,
self.ino(),
OsString::from(name),
)?) as Arc<dyn RafsInode>)
} else {
Err(enoent!())
}
}
fn get_child_by_index(&self, idx: u32) -> Result<Arc<dyn RafsInode>> {
let inode = self.disk_inode();
let child_count = self.get_child_count();
if !self.is_dir() {
return Err(einval!("inode is not a directory"));
}
let blocks_count = div_round_up(inode.size(), EROFS_BLOCK_SIZE);
let mut cur_idx = 0u32;
for i in 0..blocks_count {
let head_entry = self
.get_entry(i as usize, 0)
.map_err(err_invalidate_data)
.unwrap();
let name_offset = head_entry.e_nameoff;
let entries_count = name_offset as u32 / size_of::<RafsV6Dirent>() as u32;
for j in 0..entries_count {
let de = self
.get_entry(i as usize, j as usize)
.map_err(err_invalidate_data)?;
let name = self
.entry_name(i as usize, j as usize, entries_count as usize)
.map_err(err_invalidate_data)?;
if name == "." || name == ".." {
continue;
}
if cur_idx == idx {
let nid = de.e_nid;
return Ok(Arc::new(self.mapping.inode_wrapper_with_info(
nid,
self.ino(),
OsString::from(name),
)?) as Arc<dyn RafsInode>);
}
cur_idx += 1;
}
}
Err(enoent!("invalid child index"))
}
#[inline]
fn get_child_count(&self) -> u32 {
if !self.is_dir() {
return div_round_up(self.size(), self.chunk_size() as u64) as u32;
}
let mut child_cnt = 0;
let inode = self.disk_inode();
let blocks_count = div_round_up(self.size(), EROFS_BLOCK_SIZE);
for i in 0..blocks_count {
let head_entry = self
.get_entry(i as usize, 0)
.map_err(err_invalidate_data)
.unwrap();
let name_offset = head_entry.e_nameoff;
let entries_count = name_offset / size_of::<RafsV6Dirent>() as u16;
child_cnt += entries_count as u32;
}
child_cnt - 2
}
fn get_child_index(&self) -> Result<u32> {
Ok(0)
}
fn walk_children_inodes(&self, entry_offset: u64, handler: ChildInodeHandler) -> Result<()> {
let inode = self.disk_inode();
if inode.size() == 0 {
return Err(enoent!());
}
let blocks_count = div_round_up(inode.size(), EROFS_BLOCK_SIZE);
let mut cur_offset = entry_offset;
let mut skipped = entry_offset;
trace!(
"Total blocks count {} skipped {} current offset {} nid {} inode {:?}",
blocks_count,
skipped,
cur_offset,
self.ino(),
inode,
);
for i in 0..blocks_count {
let head_entry = self.get_entry(i as usize, 0).map_err(err_invalidate_data)?;
let name_offset = head_entry.e_nameoff;
let entries_count = name_offset / size_of::<RafsV6Dirent>() as u16;
for j in 0..entries_count {
let de = self
.get_entry(i as usize, j as usize)
.map_err(err_invalidate_data)?;
let name = self
.entry_name(i as usize, j as usize, entries_count as usize)
.map_err(err_invalidate_data)?;
if skipped != 0 {
skipped -= 1;
continue;
}
let nid = de.e_nid;
let inode = Arc::new(self.mapping.inode_wrapper_with_info(
nid,
self.ino(),
OsString::from(name),
)?) as Arc<dyn RafsInode>;
trace!("found file {:?}, nid {}", name, nid);
cur_offset += 1;
match handler(Some(inode), name.to_os_string(), nid, cur_offset) {
Ok(PostWalkAction::Break) => break,
Ok(PostWalkAction::Continue) => continue,
Err(_) => break,
};
}
}
Ok(())
}
#[inline]
fn get_chunk_count(&self) -> u32 {
self.get_child_count()
}
#[allow(clippy::cast_ptr_alignment)]
fn get_chunk_info(&self, idx: u32) -> Result<Arc<dyn BlobChunkInfo>> {
let state = self.mapping.state.load();
let inode = self.disk_inode();
if !self.is_reg() || idx >= self.get_chunk_count() {
return Err(enoent!("invalid chunk info"));
}
let offset = self.offset as usize
+ round_up(
self.this_inode_size() as u64 + self.xattr_size() as u64,
size_of::<RafsV6InodeChunkAddr>() as u64,
) as usize
+ (idx as usize * size_of::<RafsV6InodeChunkAddr>());
let chunk_addr = state.cast_to_ref::<RafsV6InodeChunkAddr>(state.base, offset)?;
let mut find = None;
CHUNK_DICT_MAP.with(|dict| {
if dict.borrow().is_none() {
*dict.borrow_mut() = Some(self.mapping.load_chunk_map().unwrap());
}
find = dict
.borrow()
.as_ref()
.unwrap()
.get(chunk_addr)
.map(Arc::clone);
});
find.ok_or_else(|| enoent!("can't find chunk info"))
}
fn get_xattr(&self, name: &OsStr) -> Result<Option<XattrValue>> {
let inode = self.disk_inode();
let total = inode.xattr_inline_count();
if total == 0 {
return Ok(None);
}
let mut remaining = (total - 1) as usize * size_of::<RafsV6XattrEntry>()
+ size_of::<RafsV6XattrIbodyHeader>();
let m = self.mapping.state.load();
let mut cur = unsafe {
m.base
.add(self.offset + self.this_inode_size() + size_of::<RafsV6XattrIbodyHeader>())
};
remaining -= size_of::<RafsV6XattrIbodyHeader>();
while remaining > 0 {
let e = unsafe { &*(cur as *const RafsV6XattrEntry) };
let mut xa_name = recover_namespace(e.name_index())?;
let suffix = OsStr::from_bytes(unsafe {
slice::from_raw_parts(
cur.add(size_of::<RafsV6XattrEntry>()),
e.name_len() as usize,
)
});
xa_name.push(suffix);
if xa_name == name {
let value = unsafe {
slice::from_raw_parts(
cur.add(size_of::<RafsV6XattrEntry>() + e.name_len() as usize),
e.value_size() as usize,
)
}
.to_vec();
return Ok(Some(value));
}
let mut s = e.name_len() + e.value_size() + size_of::<RafsV6XattrEntry>() as u32;
s = round_up(s as u64, size_of::<RafsV6XattrEntry>() as u64) as u32;
remaining -= s as usize;
cur = unsafe { cur.add(s as usize) };
}
Ok(None)
}
fn get_xattrs(&self) -> Result<Vec<XattrName>> {
let inode = self.disk_inode();
let mut xattrs = Vec::new();
let total = inode.xattr_inline_count();
if total == 0 {
return Ok(xattrs);
}
let mut remaining = (total - 1) as usize * size_of::<RafsV6XattrEntry>()
+ size_of::<RafsV6XattrIbodyHeader>();
let m = self.mapping.state.load();
let mut cur = unsafe {
m.base
.add(self.offset + self.this_inode_size() + size_of::<RafsV6XattrIbodyHeader>())
};
remaining -= size_of::<RafsV6XattrIbodyHeader>();
while remaining > 0 {
let e = unsafe { &*(cur as *const RafsV6XattrEntry) };
let ns = recover_namespace(e.name_index())?;
let mut xa = ns.into_vec();
xa.extend_from_slice(unsafe {
slice::from_raw_parts(
cur.add(size_of::<RafsV6XattrEntry>()),
e.name_len() as usize,
)
});
xattrs.push(xa);
let mut s = e.name_len() + e.value_size() + size_of::<RafsV6XattrEntry>() as u32;
s = round_up(s as u64, size_of::<RafsV6XattrEntry>() as u64) as u32;
remaining -= s as usize;
cur = unsafe { cur.add(s as usize) };
}
Ok(xattrs)
}
fn ino(&self) -> u64 {
let meta_blkaddr = self.mapping.state.load().meta.meta_blkaddr as u64;
(self.offset as u64 - meta_blkaddr * EROFS_BLOCK_SIZE) / EROFS_INODE_SLOT_SIZE as u64
}
fn name(&self) -> OsString {
let mut curr_name = OsString::from("");
match self.name.borrow().as_ref() {
Some(name) => return name.clone(),
None => {
debug_assert!(self.is_dir());
let cur_ino = self.ino();
if cur_ino == self.mapping.root_ino() {
return OsString::from("");
}
let parent_inode = self.mapping.inode_wrapper(self.parent()).unwrap();
parent_inode
.walk_children_inodes(
0,
&mut |inode: Option<Arc<dyn RafsInode>>, name: OsString, ino, offset| {
if cur_ino == ino {
curr_name = name;
return Ok(PostWalkAction::Break);
}
Ok(PostWalkAction::Continue)
},
)
.unwrap();
}
}
*self.name.borrow_mut() = Some(curr_name.clone());
curr_name
}
fn flags(&self) -> u64 {
0
}
fn get_digest(&self) -> RafsDigest {
RafsDigest::default()
}
fn is_dir(&self) -> bool {
self.mode_format_bits() == libc::S_IFDIR as u32
}
fn is_symlink(&self) -> bool {
self.mode_format_bits() == libc::S_IFLNK as u32
}
fn is_reg(&self) -> bool {
self.mode_format_bits() == libc::S_IFREG as u32
}
fn is_hardlink(&self) -> bool {
let inode = self.disk_inode();
inode.nlink() > 1 && self.is_reg()
}
fn parent(&self) -> u64 {
match self.parent_inode.get() {
Some(parent) => parent,
None => {
debug_assert!(self.is_dir());
let ino = self.get_child_by_name(OsStr::new("..")).unwrap().ino();
self.parent_inode.set(Some(ino));
ino
}
}
}
fn rdev(&self) -> u32 {
self.disk_inode().union()
}
fn projid(&self) -> u32 {
0
}
fn size(&self) -> u64 {
let i = self.disk_inode();
i.size()
}
fn get_name_size(&self) -> u16 {
self.name().len() as u16
}
fn get_symlink_size(&self) -> u16 {
let inode = self.disk_inode();
inode.size() as u16
}
fn collect_descendants_inodes(
&self,
descendants: &mut Vec<Arc<dyn RafsInode>>,
) -> Result<usize> {
if !self.is_dir() {
return Err(enotdir!());
}
let mut child_dirs: Vec<Arc<dyn RafsInode>> = Vec::new();
self.walk_children_inodes(0, &mut |inode: Option<Arc<dyn RafsInode>>,
name: OsString,
ino,
offset| {
if let Some(child_inode) = inode {
if child_inode.is_dir() {
child_dirs.push(child_inode);
} else if !child_inode.is_empty_size() && child_inode.is_reg() {
descendants.push(child_inode);
}
Ok(PostWalkAction::Continue)
} else {
Ok(PostWalkAction::Continue)
}
})
.unwrap();
for d in child_dirs {
if d.name() == "." || d.name() == ".." {
continue;
}
d.collect_descendants_inodes(descendants)?;
}
Ok(0)
}
fn alloc_bio_vecs(&self, offset: u64, size: usize, user_io: bool) -> Result<Vec<BlobIoVec>> {
let chunk_size = self.chunk_size();
let head_chunk_index = offset / chunk_size as u64;
let mut vec: Vec<BlobIoVec> = Vec::new();
let chunks = self
.chunk_addresses(head_chunk_index as u32)
.map_err(err_invalidate_data)?;
if chunks.is_empty() {
return Ok(vec);
}
let content_offset = (offset % chunk_size as u64) as u32;
let mut left = std::cmp::min(self.size(), size as u64) as u32;
let mut content_len = std::cmp::min(chunk_size - content_offset, left);
let first_chunk_addr = chunks.first().unwrap();
let desc = self.make_chunk_io(first_chunk_addr, content_offset, content_len, user_io);
let mut descs = BlobIoVec::new();
descs.bi_vec.push(desc);
descs.bi_size += content_len as usize;
left -= content_len;
if left != 0 {
for c in chunks.iter().skip(1) {
content_len = std::cmp::min(chunk_size, left);
let desc = self.make_chunk_io(c, 0, content_len, user_io);
if c.blob_index() as u32 != descs.bi_vec[0].blob.blob_index() {
vec.push(descs);
descs = BlobIoVec::new();
}
descs.bi_vec.push(desc);
descs.bi_size += content_len as usize;
left -= content_len;
if left == 0 {
break;
}
}
}
if !descs.bi_vec.is_empty() {
vec.push(descs)
}
assert_eq!(left, 0);
Ok(vec)
}
fn as_any(&self) -> &dyn Any {
self
}
}
macro_rules! impl_chunkinfo_getter {
($G: ident, $U: ty) => {
#[inline]
fn $G(&self) -> $U {
let state = self.state();
self.chunk(state.deref()).$G
}
};
}
pub struct DirectChunkInfoV6 {
mapping: DirectSuperBlockV6,
offset: usize,
digest: RafsDigest,
}
unsafe impl Send for DirectChunkInfoV6 {}
unsafe impl Sync for DirectChunkInfoV6 {}
impl DirectChunkInfoV6 {
#[inline]
fn new(chunk: &RafsV5ChunkInfo, mapping: DirectSuperBlockV6, offset: usize) -> Self {
Self {
mapping,
offset,
digest: chunk.block_id,
}
}
#[inline]
fn state(&self) -> Guard<Arc<DirectMappingState>> {
self.mapping.state.load()
}
#[allow(clippy::cast_ptr_alignment)]
fn chunk<'a>(&self, state: &'a DirectMappingState) -> &'a RafsV5ChunkInfo {
unsafe {
let ptr = state.base.add(self.offset);
&*(ptr as *const RafsV5ChunkInfo)
}
}
}
impl BlobChunkInfo for DirectChunkInfoV6 {
fn chunk_id(&self) -> &RafsDigest {
&self.digest
}
fn id(&self) -> u32 {
self.index()
}
fn is_compressed(&self) -> bool {
self.chunk(self.state().deref())
.flags
.contains(BlobChunkFlags::COMPRESSED)
}
fn is_hole(&self) -> bool {
self.chunk(self.state().deref())
.flags
.contains(BlobChunkFlags::HOLECHUNK)
}
fn as_any(&self) -> &dyn Any {
self
}
impl_chunkinfo_getter!(blob_index, u32);
impl_chunkinfo_getter!(compress_offset, u64);
impl_chunkinfo_getter!(compress_size, u32);
impl_chunkinfo_getter!(uncompress_offset, u64);
impl_chunkinfo_getter!(uncompress_size, u32);
}
impl BlobV5ChunkInfo for DirectChunkInfoV6 {
fn as_base(&self) -> &dyn BlobChunkInfo {
self
}
impl_chunkinfo_getter!(index, u32);
impl_chunkinfo_getter!(file_offset, u64);
impl_chunkinfo_getter!(flags, BlobChunkFlags);
}