use alloc::{
borrow::ToOwned,
format,
string::{String, ToString},
sync::Arc,
};
use core::any::Any;
use axfs_ng_vfs::{
DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FileNodeOps, FilesystemOps,
FsIoEvents, FsPollable, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType,
Reference, VfsError, VfsResult, WeakDirEntry,
};
use rsext4::{BLOCK_SIZE, bmalloc::InodeNumber};
use super::{
Ext4Filesystem,
util::{dir_entry_type_to_vfs, inode_to_vfs_type, into_vfs_err, vfs_type_to_dir_entry},
};
use crate::highlevel::forget_cached_file_key;
pub struct Inode {
fs: Arc<Ext4Filesystem>,
ino: InodeNumber,
this: Option<WeakDirEntry>,
path: Option<String>,
}
impl Inode {
pub(crate) fn new(
fs: Arc<Ext4Filesystem>,
ino: InodeNumber,
this: Option<WeakDirEntry>,
path: Option<String>,
) -> Arc<Self> {
Arc::new(Self {
fs,
ino,
this,
path,
})
}
fn create_entry(
&self,
ino: InodeNumber,
inode: &rsext4::disknode::Ext4Inode,
name: impl Into<String>,
) -> DirEntry {
let name = name.into();
let reference = Reference::new(
self.this.as_ref().and_then(WeakDirEntry::upgrade),
name.clone(),
);
let path = self.dir_path().map(|dir| join_child_path(&dir, &name)).ok();
if inode.is_dir() {
DirEntry::new_dir(
|this| DirNode::new(Inode::new(self.fs.clone(), ino, Some(this), path.clone())),
reference,
)
} else {
DirEntry::new_file(
FileNode::new(Inode::new(self.fs.clone(), ino, None, path)),
inode_to_vfs_type(inode.i_mode),
reference,
)
}
}
fn dir_path(&self) -> VfsResult<String> {
if let Some(this) = self.this.as_ref().and_then(WeakDirEntry::upgrade) {
return Ok(this.absolute_path()?.to_string());
}
self.path.clone().ok_or(VfsError::InvalidInput)
}
fn lookup_locked(&self, name: &str) -> VfsResult<DirEntry> {
let path = join_child_path(&self.dir_path()?, name);
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let (ino, inode) = rsext4::dir::get_inode_with_num(fs, dev, &path)
.map_err(into_vfs_err)?
.ok_or(VfsError::NotFound)?;
Ok(self.create_entry(ino, &inode, name))
}
fn update_ctime_with(
fs: &mut rsext4::Ext4FileSystem,
dev: &mut rsext4::Jbd2Dev<super::Ext4Disk>,
ino: InodeNumber,
) -> VfsResult<()> {
fs.modify_inode(dev, ino, |inode| {
if cfg!(feature = "times") {
inode.i_ctime = crate::os::wall_time().as_secs() as u32;
}
})
.map_err(into_vfs_err)
}
}
impl NodeOps for Inode {
fn inode(&self) -> u64 {
self.ino.as_u64()
}
fn metadata(&self) -> VfsResult<Metadata> {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?;
let node_type = inode_to_vfs_type(inode.i_mode);
Ok(Metadata {
inode: self.ino.as_u64(),
device: 0,
nlink: inode.i_links_count as _,
mode: NodePermission::from_bits_truncate(inode.permissions()),
node_type,
uid: inode.uid(),
gid: inode.gid(),
size: inode.size(),
block_size: fs.superblock.block_size(),
blocks: inode.blocks_count(),
rdev: if matches!(node_type, NodeType::CharacterDevice | NodeType::BlockDevice) {
decode_ext4_rdev(&inode.i_block)
} else {
DeviceId::default()
},
atime: core::time::Duration::from_secs(inode.i_atime as u64),
mtime: core::time::Duration::from_secs(inode.i_mtime as u64),
ctime: core::time::Duration::from_secs(inode.i_ctime as u64),
})
}
fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()> {
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
fs.modify_inode(dev, self.ino, |inode| {
if let Some(mode) = update.mode {
inode.i_mode =
(inode.i_mode & rsext4::disknode::Ext4Inode::S_IFMT) | mode.bits();
}
if let Some((uid, gid)) = update.owner {
inode.i_uid = (uid & 0xffff) as u16;
inode.l_i_uid_high = ((uid >> 16) & 0xffff) as u16;
inode.i_gid = (gid & 0xffff) as u16;
inode.l_i_gid_high = ((gid >> 16) & 0xffff) as u16;
}
if let Some(rdev) = update.rdev {
let ty = inode_to_vfs_type(inode.i_mode);
if matches!(ty, NodeType::CharacterDevice | NodeType::BlockDevice) {
let (b0, b1) = encode_ext4_rdev(rdev);
inode.i_block[0] = b0;
inode.i_block[1] = b1;
}
}
if let Some(atime) = update.atime {
inode.i_atime = atime.as_secs() as u32;
}
if let Some(mtime) = update.mtime {
inode.i_mtime = mtime.as_secs() as u32;
}
if cfg!(feature = "times") {
inode.i_ctime = crate::os::wall_time().as_secs() as u32;
}
})
.map_err(into_vfs_err)?;
}
self.fs.sync_to_disk()
}
fn len(&self) -> VfsResult<u64> {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
fs.get_inode_by_num(dev, self.ino)
.map(|inode| inode.size())
.map_err(into_vfs_err)
}
fn filesystem(&self) -> &dyn FilesystemOps {
&*self.fs
}
fn sync(&self, _data_only: bool) -> VfsResult<()> {
self.fs.sync_to_disk()
}
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn flags(&self) -> NodeFlags {
NodeFlags::BLOCKING
}
}
impl FileNodeOps for Inode {
fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult<usize> {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
rsext4::read_inode_data_into(dev, fs, self.ino, offset, buf).map_err(into_vfs_err)
}
fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult<usize> {
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
rsext4::write_inode_data(dev, fs, self.ino, offset, buf).map_err(into_vfs_err)?;
}
Ok(buf.len())
}
fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> {
let length = {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?;
let length = inode.size();
rsext4::write_inode_data(dev, fs, self.ino, length, buf).map_err(into_vfs_err)?;
length
};
Ok((buf.len(), length + buf.len() as u64))
}
fn set_len(&self, len: u64) -> VfsResult<()> {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
rsext4::truncate(
dev,
fs,
&self.path.clone().ok_or(VfsError::InvalidInput)?,
len,
)
.map_err(into_vfs_err)
}
fn set_symlink(&self, target: &str) -> VfsResult<()> {
let Some(_path) = self.path.clone() else {
return Err(VfsError::InvalidInput);
};
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let mut inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?;
if !inode.is_symlink() {
return Err(VfsError::InvalidInput);
}
if let Ok(blocks) = rsext4::loopfile::resolve_inode_block_allextend(fs, dev, &mut inode)
{
for blk in blocks.values() {
let _ = fs.free_block(dev, *blk);
}
}
let target_bytes = target.as_bytes();
let target_len = target_bytes.len();
inode.i_size_lo = (target_len as u64 & 0xffffffff) as u32;
inode.i_size_high = ((target_len as u64) >> 32) as u32;
inode.i_blocks_lo = 0;
inode.l_i_blocks_high = 0;
inode.i_block = [0; 15];
if target_len == 0 {
inode.i_flags &= !rsext4::disknode::Ext4Inode::EXT4_EXTENTS_FL;
} else if target_len <= 60 {
inode.i_flags &= !rsext4::disknode::Ext4Inode::EXT4_EXTENTS_FL;
let mut raw = [0u8; 60];
raw[..target_len].copy_from_slice(target_bytes);
for i in 0..15 {
inode.i_block[i] = u32::from_le_bytes([
raw[i * 4],
raw[i * 4 + 1],
raw[i * 4 + 2],
raw[i * 4 + 3],
]);
}
} else {
if !fs.superblock.has_extents() {
return Err(VfsError::Unsupported);
}
let mut data_blocks = alloc::vec::Vec::new();
let mut remaining = target_len;
let mut src_off = 0usize;
while remaining > 0 {
let blk = fs.alloc_block(dev).map_err(into_vfs_err)?;
let write_len = core::cmp::min(remaining, BLOCK_SIZE);
fs.datablock_cache
.modify_new(dev, blk, |data| {
for b in data.iter_mut() {
*b = 0;
}
let end = src_off + write_len;
data[..write_len].copy_from_slice(&target_bytes[src_off..end]);
})
.map_err(into_vfs_err)?;
data_blocks.push(blk);
remaining -= write_len;
src_off += write_len;
}
let used_datablocks = data_blocks.len() as u64;
let iblocks_used = used_datablocks.saturating_mul(BLOCK_SIZE as u64 / 512) as u32;
inode.i_blocks_lo = iblocks_used;
inode.l_i_blocks_high = 0;
rsext4::file::build_file_block_mapping_with_inode_num(
fs,
&mut inode,
self.ino,
&data_blocks,
dev,
);
}
fs.modify_inode(dev, self.ino, |on_disk| {
*on_disk = inode;
})
.map_err(into_vfs_err)?;
}
self.fs.sync_to_disk()
}
}
impl FsPollable for Inode {
fn poll(&self) -> FsIoEvents {
FsIoEvents::IN | FsIoEvents::OUT
}
fn register(&self, _context: &mut core::task::Context<'_>, _events: FsIoEvents) {}
}
impl DirNodeOps for Inode {
fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult<usize> {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let mut inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?;
let blocks = rsext4::loopfile::resolve_inode_block_allextend(fs, dev, &mut inode)
.map_err(into_vfs_err)?;
let mut byte_offset: u64 = 0;
let mut count = 0usize;
for &phys in blocks.values() {
let cached = fs
.datablock_cache
.get_or_load(dev, phys)
.map_err(into_vfs_err)?;
let data = &cached.data[..BLOCK_SIZE];
let mut pos = 0usize;
while pos + 8 <= data.len() {
let entry_inode =
u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
let rec_len = u16::from_le_bytes([data[pos + 4], data[pos + 5]]);
if rec_len < 8 {
break;
}
let rec_usize = rec_len as usize;
if pos + rec_usize > data.len() {
break;
}
let entry_offset = byte_offset;
byte_offset += rec_len as u64;
pos += rec_usize;
if entry_inode == 0 {
continue;
}
if entry_offset < offset {
continue;
}
let name_len = data[pos - rec_usize + 6] as usize;
let file_type = data[pos - rec_usize + 7];
let name_start = pos - rec_usize + 8;
if name_len > rec_usize - 8 {
continue;
}
let name = core::str::from_utf8(&data[name_start..name_start + name_len])
.map_err(|_| VfsError::InvalidData)?
.to_owned();
let node_type = dir_entry_type_to_vfs(file_type);
if !sink.accept(&name, entry_inode as u64, node_type, byte_offset) {
return Ok(count);
}
count += 1;
}
}
Ok(count)
}
fn lookup(&self, name: &str) -> VfsResult<DirEntry> {
if name == "." {
return self
.this
.as_ref()
.and_then(WeakDirEntry::upgrade)
.ok_or(VfsError::NotFound);
}
if name == ".." {
return self
.this
.as_ref()
.and_then(WeakDirEntry::upgrade)
.and_then(|entry| entry.parent())
.ok_or(VfsError::NotFound);
}
self.lookup_locked(name)
}
fn create(
&self,
name: &str,
node_type: NodeType,
permission: NodePermission,
uid: u32,
gid: u32,
) -> VfsResult<DirEntry> {
let Some(dir_path) = self.dir_path().ok() else {
return Err(VfsError::InvalidInput);
};
let path = join_child_path(&dir_path, name);
let ino = {
let mut state = self.fs.lock();
let (fs, dev) = state.split();
if rsext4::dir::get_inode_with_num(fs, dev, &path)
.map_err(into_vfs_err)?
.is_some()
{
return Err(VfsError::AlreadyExists);
}
if node_type == NodeType::Directory {
rsext4::mkdir_with_owner(dev, fs, &path, uid, gid).map_err(into_vfs_err)?;
} else {
let file_type = vfs_type_to_dir_entry(node_type).ok_or(VfsError::InvalidData)?;
rsext4::mkfile_with_owner(dev, fs, &path, None, Some(file_type), uid, gid)
.map_err(into_vfs_err)?;
};
let (ino, _inode) = rsext4::dir::get_inode_with_num(fs, dev, &path)
.map_err(into_vfs_err)?
.ok_or(VfsError::NotFound)?;
let mode_bits = permission.bits();
fs.modify_inode(dev, ino, |node| {
node.i_mode = (node.i_mode & rsext4::disknode::Ext4Inode::S_IFMT) | mode_bits;
})
.map_err(into_vfs_err)?;
Self::update_ctime_with(fs, dev, ino)?;
ino
};
self.fs.sync_to_disk()?;
let reference = Reference::new(
self.this.as_ref().and_then(WeakDirEntry::upgrade),
name.to_owned(),
);
Ok(if node_type == NodeType::Directory {
DirEntry::new_dir(
|this| DirNode::new(Inode::new(self.fs.clone(), ino, Some(this), Some(path))),
reference,
)
} else {
DirEntry::new_file(
FileNode::new(Inode::new(self.fs.clone(), ino, None, Some(path))),
node_type,
reference,
)
})
}
fn link(&self, name: &str, node: &DirEntry) -> VfsResult<DirEntry> {
let dir_path = self.dir_path()?;
let link_path = join_child_path(&dir_path, name);
let target_path = node.absolute_path()?.to_string();
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
if rsext4::dir::get_inode_with_num(fs, dev, &target_path)
.map_err(into_vfs_err)?
.is_none()
{
return Err(VfsError::NotFound);
}
if rsext4::dir::get_inode_with_num(fs, dev, &link_path)
.map_err(into_vfs_err)?
.is_some()
{
return Err(VfsError::AlreadyExists);
}
rsext4::link(fs, dev, &link_path, &target_path).map_err(into_vfs_err)?;
let target_ino = InodeNumber::new(node.inode() as u32).map_err(into_vfs_err)?;
Self::update_ctime_with(fs, dev, target_ino)?;
}
self.fs.sync_to_disk()?;
self.lookup_locked(name)
}
fn unlink(&self, name: &str, is_dir: bool) -> VfsResult<()> {
let dir_path = self.dir_path()?;
let path = join_child_path(&dir_path, name);
let mut forget_file_ino = None;
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
let inode_info =
rsext4::dir::get_inode_with_num(fs, dev, &path).map_err(into_vfs_err)?;
if inode_info.is_none() {
return Err(VfsError::NotFound);
}
let (ino, inode) = inode_info.unwrap();
match (inode.is_dir(), is_dir) {
(true, false) => return Err(VfsError::IsADirectory),
(false, true) => return Err(VfsError::NotADirectory),
_ => {}
}
if inode.is_dir() {
let mut dir_inode = inode; if !rsext4::is_dir_empty(fs, dev, &mut dir_inode).map_err(into_vfs_err)? {
return Err(VfsError::DirectoryNotEmpty);
}
rsext4::delete_dir(fs, dev, &path).map_err(into_vfs_err)?;
} else {
if inode.i_links_count <= 1 {
forget_file_ino = Some(ino);
}
rsext4::unlink(fs, dev, &path).map_err(into_vfs_err)?;
}
}
if let Some(ino) = forget_file_ino {
forget_cached_file_key(&*self.fs, ino.as_u64());
}
self.fs.sync_to_disk()
}
fn rename(&self, src_name: &str, dst_dir: &DirNode, dst_name: &str) -> VfsResult<()> {
let dst_dir: Arc<Self> = dst_dir.downcast().map_err(|_| VfsError::InvalidInput)?;
let src_path = join_child_path(&self.dir_path()?, src_name);
let dst_path = join_child_path(&dst_dir.dir_path()?, dst_name);
let replaced_file_ino = {
let mut state = dst_dir.fs.lock();
let (fs, dev) = state.split();
rsext4::dir::get_inode_with_num(fs, dev, &dst_path)
.map_err(into_vfs_err)?
.and_then(|(ino, inode)| {
(!inode.is_dir() && inode.i_links_count <= 1).then_some(ino)
})
};
{
let mut state = self.fs.lock();
let (fs, dev) = state.split();
rsext4::rename(dev, fs, &src_path, &dst_path).map_err(into_vfs_err)?;
}
if let Some(ino) = replaced_file_ino {
forget_cached_file_key(&*self.fs, ino.as_u64());
}
self.fs.sync_to_disk()
}
}
fn join_child_path(parent: &str, name: &str) -> String {
if parent == "/" {
format!("/{name}")
} else {
format!("{parent}/{name}")
}
}
const EXT4_OLD_MAJOR_BITS: u32 = 8;
const EXT4_OLD_MINOR_BITS: u32 = 8;
const EXT4_OLD_MAJOR_MAX: u32 = (1 << EXT4_OLD_MAJOR_BITS) - 1; const EXT4_OLD_MINOR_MAX: u32 = (1 << EXT4_OLD_MINOR_BITS) - 1;
const EXT4_NEW_MAJOR_BITS: u32 = 12;
const EXT4_NEW_MINOR_BITS: u32 = 20;
const EXT4_NEW_MAJOR_MASK: u32 = (1 << EXT4_NEW_MAJOR_BITS) - 1; const EXT4_NEW_MINOR_LOW_MASK: u32 = (1 << EXT4_OLD_MINOR_BITS) - 1; const EXT4_NEW_MINOR_HIGH_MASK: u32 =
((1 << EXT4_NEW_MINOR_BITS) - 1) & !((1 << EXT4_OLD_MINOR_BITS) - 1);
fn decode_ext4_rdev(i_block: &[u32; 15]) -> DeviceId {
if i_block[0] & 0xFFFF != 0 {
let v = i_block[0] as u16;
let major = (v >> EXT4_OLD_MINOR_BITS) & (EXT4_OLD_MAJOR_MAX as u16);
let minor = v & (EXT4_OLD_MINOR_MAX as u16);
DeviceId::new(major as u32, minor as u32)
} else {
let v = i_block[1];
let major = (v >> EXT4_OLD_MINOR_BITS) & EXT4_NEW_MAJOR_MASK;
let minor =
(v & EXT4_NEW_MINOR_LOW_MASK) | ((v >> EXT4_NEW_MAJOR_BITS) & EXT4_NEW_MINOR_HIGH_MASK);
DeviceId::new(major, minor)
}
}
fn encode_ext4_rdev(rdev: DeviceId) -> (u32, u32) {
let major = rdev.major();
let minor = rdev.minor();
if major <= EXT4_OLD_MAJOR_MAX && minor <= EXT4_OLD_MINOR_MAX {
((major << EXT4_OLD_MINOR_BITS) | minor, 0)
} else {
let encoded = (minor & EXT4_NEW_MINOR_LOW_MASK)
| ((major & EXT4_NEW_MAJOR_MASK) << EXT4_OLD_MINOR_BITS)
| ((minor & EXT4_NEW_MINOR_HIGH_MASK) << EXT4_NEW_MAJOR_BITS);
(0, encoded)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn roundtrip(major: u32, minor: u32) -> (u32, u32) {
let dev = DeviceId::new(major, minor);
let (b0, b1) = encode_ext4_rdev(dev);
let mut iblock = [0u32; 15];
iblock[0] = b0;
iblock[1] = b1;
let back = decode_ext4_rdev(&iblock);
(back.major(), back.minor())
}
#[test]
fn rdev_old_format_small() {
assert_eq!(roundtrip(1, 3), (1, 3));
}
#[test]
fn rdev_old_format_boundary() {
assert_eq!(roundtrip(255, 255), (255, 255));
assert_eq!(roundtrip(0, 0), (0, 0));
}
#[test]
fn rdev_new_format_minor_exceeds_old() {
assert_eq!(roundtrip(1, 256), (1, 256));
}
#[test]
fn rdev_new_format_large_minor() {
assert_eq!(roundtrip(1, 1040), (1, 1040));
assert_eq!(roundtrip(8, 511), (8, 511));
}
#[test]
fn rdev_old_on_disk_decodes_correctly() {
let mut iblock = [0u32; 15];
iblock[0] = (1 << EXT4_OLD_MINOR_BITS) | 3;
let dev = decode_ext4_rdev(&iblock);
assert_eq!(dev.major(), 1);
assert_eq!(dev.minor(), 3);
}
#[test]
fn rdev_new_on_disk_decodes_correctly() {
let encoded = (256u32 & EXT4_NEW_MINOR_LOW_MASK)
| ((1u32 & EXT4_NEW_MAJOR_MASK) << EXT4_OLD_MINOR_BITS)
| ((256u32 & EXT4_NEW_MINOR_HIGH_MASK) << EXT4_NEW_MAJOR_BITS);
let mut iblock = [0u32; 15];
iblock[0] = 0;
iblock[1] = encoded;
let dev = decode_ext4_rdev(&iblock);
assert_eq!(dev.major(), 1);
assert_eq!(dev.minor(), 256);
}
}