From 2af71ff72a8266e3f28f78ac6278347c242718dd Mon Sep 17 00:00:00 2001 From: Shaowei Song Date: Fri, 28 Mar 2025 06:02:13 +0000 Subject: [PATCH] Add xattr support for ext2 --- kernel/src/fs/ext2/impl_for_vfs/inode.rs | 23 +- kernel/src/fs/ext2/inode.rs | 64 ++- kernel/src/fs/ext2/mod.rs | 1 + kernel/src/fs/ext2/xattr.rs | 490 +++++++++++++++++++++++ 4 files changed, 576 insertions(+), 2 deletions(-) create mode 100644 kernel/src/fs/ext2/xattr.rs diff --git a/kernel/src/fs/ext2/impl_for_vfs/inode.rs b/kernel/src/fs/ext2/impl_for_vfs/inode.rs index a639f706..dc68a3e0 100644 --- a/kernel/src/fs/ext2/impl_for_vfs/inode.rs +++ b/kernel/src/fs/ext2/impl_for_vfs/inode.rs @@ -11,7 +11,7 @@ use crate::{ ext2::{FilePerm, Inode as Ext2Inode}, utils::{ DirentVisitor, Extension, FallocMode, FileSystem, Inode, InodeMode, InodeType, - IoctlCmd, Metadata, MknodType, + IoctlCmd, Metadata, MknodType, XattrName, XattrNamespace, XattrSetFlags, }, }, prelude::*, @@ -194,6 +194,27 @@ impl Inode for Ext2Inode { fn extension(&self) -> Option<&Extension> { Some(self.extension()) } + + fn set_xattr( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()> { + self.set_xattr(name, value_reader, flags) + } + + fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result { + self.get_xattr(name, value_writer) + } + + fn list_xattr(&self, namespace: XattrNamespace, list_writer: &mut VmWriter) -> Result { + self.list_xattr(namespace, list_writer) + } + + fn remove_xattr(&self, name: XattrName) -> Result<()> { + self.remove_xattr(name) + } } impl From for InodeMode { diff --git a/kernel/src/fs/ext2/inode.rs b/kernel/src/fs/ext2/inode.rs index 329f2161..1e19c0a2 100644 --- a/kernel/src/fs/ext2/inode.rs +++ b/kernel/src/fs/ext2/inode.rs @@ -16,11 +16,15 @@ use super::{ indirect_block_cache::{IndirectBlock, IndirectBlockCache}, prelude::*, utils::now, + xattr::Xattr, }; use crate::{ fs::{ path::{is_dot, is_dot_or_dotdot, is_dotdot}, - utils::{Extension, FallocMode, InodeMode, Metadata}, + utils::{ + Extension, FallocMode, Inode as _, InodeMode, Metadata, Permission, XattrName, + XattrNamespace, XattrSetFlags, + }, }, process::{posix_thread::AsPosixThread, Gid, Uid}, }; @@ -39,6 +43,7 @@ pub struct Inode { inner: RwMutex, fs: Weak, extension: Extension, + xattr: Option, } impl Inode { @@ -52,6 +57,9 @@ impl Inode { ino, type_: desc.type_, block_group_idx, + xattr: desc + .acl + .map(|acl| Xattr::new(acl, weak_self.clone(), fs.clone())), inner: RwMutex::new(InodeInner::new(desc, weak_self.clone(), fs.clone())), fs, extension: Extension::new(), @@ -745,6 +753,9 @@ impl Inode { let mut inner = self.inner.write(); inner.sync_data()?; inner.sync_metadata()?; + if let Some(xattr) = self.xattr.as_ref() { + xattr.flush()?; + } Ok(()) } @@ -808,6 +819,48 @@ impl Inode { } } } + + pub fn set_xattr( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()> { + let xattr = self.xattr.as_ref().ok_or(Error::with_message( + Errno::EPERM, + "xattr is not supported on the file type", + ))?; + self.check_permission(Permission::MAY_WRITE)?; + xattr.set(name, value_reader, flags) + } + + pub fn get_xattr(&self, name: XattrName, value_writer: &mut VmWriter) -> Result { + if self.xattr.is_none() { + return_errno_with_message!(Errno::ENODATA, "no available xattrs"); + } + self.check_permission(Permission::MAY_READ)?; + self.xattr.as_ref().unwrap().get(name, value_writer) + } + + pub fn list_xattr( + &self, + namespace: XattrNamespace, + list_writer: &mut VmWriter, + ) -> Result { + if self.xattr.is_none() || self.check_permission(Permission::MAY_ACCESS).is_err() { + return Ok(0); + } + self.xattr.as_ref().unwrap().list(namespace, list_writer) + } + + pub fn remove_xattr(&self, name: XattrName) -> Result<()> { + let xattr = self.xattr.as_ref().ok_or(Error::with_message( + Errno::EPERM, + "xattr is not supported on the file type", + ))?; + self.check_permission(Permission::MAY_WRITE)?; + self.xattr.as_ref().unwrap().remove(name) + } } #[inherit_methods(from = "self.inner.read()")] @@ -827,6 +880,7 @@ impl Inode { #[inherit_methods(from = "self.inner.write()")] impl Inode { + pub fn set_acl(&self, bid: Bid); pub fn set_atime(&self, time: Duration); pub fn set_mtime(&self, time: Duration); pub fn set_ctime(&self, time: Duration); @@ -1123,6 +1177,7 @@ impl InodeInner { pub fn dec_hard_links(&mut self); pub fn blocks_count(&self) -> Ext2Bid; pub fn acl(&self) -> Option; + pub fn set_acl(&mut self, bid: Bid); pub fn atime(&self) -> Duration; pub fn set_atime(&mut self, time: Duration); pub fn mtime(&self) -> Duration; @@ -1224,6 +1279,10 @@ impl InodeImpl { self.desc.acl } + pub fn set_acl(&mut self, bid: Bid) { + self.desc.acl = Some(bid); + } + pub fn atime(&self) -> Duration { self.desc.atime } @@ -1292,6 +1351,9 @@ impl InodeImpl { inode .fs() .free_inode(inode.ino(), self.desc.type_ == InodeType::Dir)?; + if let Some(xattr) = &inode.xattr { + xattr.free()?; + } self.is_freed = true; } } diff --git a/kernel/src/fs/ext2/mod.rs b/kernel/src/fs/ext2/mod.rs index 81963e41..20f3bbff 100644 --- a/kernel/src/fs/ext2/mod.rs +++ b/kernel/src/fs/ext2/mod.rs @@ -50,3 +50,4 @@ mod inode; mod prelude; mod super_block; mod utils; +mod xattr; diff --git a/kernel/src/fs/ext2/xattr.rs b/kernel/src/fs/ext2/xattr.rs new file mode 100644 index 00000000..c17e75a2 --- /dev/null +++ b/kernel/src/fs/ext2/xattr.rs @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::mm::UntypedMem; + +use super::{block_ptr::Ext2Bid, prelude::*, Ext2, Inode}; +use crate::fs::utils::{XattrName, XattrNamespace, XattrSetFlags, XATTR_NAME_MAX_LEN}; + +const EXT2_XATTR_MAGIC: u32 = 0xEA020000; + +/// The xattr header of an ext2 inode, organized +/// at the beginning of an xattr block. +#[repr(C)] +#[derive(Clone, Copy, Pod, Debug)] +struct XattrHeader { + magic: u32, + ref_count: u32, + nblocks: u32, + hash: u32, + reserved: [u32; 4], +} + +const XATTR_HEADER_SIZE: usize = size_of::(); +const XATTR_ALIGN: usize = 4; +const XATTR_ENTRY_VALUE_GAP: usize = XATTR_ALIGN; + +/// The xattr entry of an ext2 inode, organized on an xattr block. +#[repr(C)] +#[derive(Clone, Copy, Pod, Debug)] +struct XattrEntry { + name_len: u8, + name_index: u8, + value_offset: u16, + value_block: u32, + value_len: u32, + hash: u32, +} + +const XATTR_ENTRY_SIZE: usize = size_of::(); + +/// An xattr object of an ext2 inode. +/// An xattr is used to manage special 'name-value' pairs of an inode. +/// +/// An xattr block layout (objects are aligned to 4 bytes): +/// +--------------------+ +/// | XattrHeader | +/// | XattrEntry 1 | | +/// | XattrName 1 | | +/// | XattrEntry 2 | | growing downwards +/// | XattrName 2 | | +/// | XattrEntry 3 | v +/// | XattrName 3 | +/// | ... | +/// | Gap (4 null bytes) | +/// | ... | +/// | XattrValue 3 | ^ +/// | XattrValue 2 | | growing upwards +/// | XattrValue 1 | | +/// +--------------------+ +#[derive(Debug)] +pub(super) struct Xattr { + /// The buffer of the xattr block, which always keeps its content up-to-date. + blocks_buf: USegment, + /// A cache to assist the xattr operations. + cache: RwMutex>, + inode: Weak, + fs: Weak, +} + +/// Gathers helper metadata to describe an xattr block. This is +/// primarily for convenience when manipulating xattr entries or values. +/// Without this cache, the raw buffer would need to be parsed every time. +#[derive(Debug)] +struct XattrCache { + bid: Bid, + header: Option, + entries: BTreeMap, // K: offset, V: entry + values: BTreeMap, // K: offset, V: len + capacity_bytes: usize, +} + +/// The number of blocks for an xattr. This value should be +/// the consistent with `XattrHeader::nblocks`. +pub(super) const XATTR_NBLOCKS: usize = 1; + +impl XattrEntry { + fn total_len(&self) -> usize { + XATTR_ENTRY_SIZE + (self.name_len as usize).align_up(XATTR_ALIGN) + } + + fn target_len(name_len: usize) -> usize { + XATTR_ENTRY_SIZE + name_len.align_up(XATTR_ALIGN) + } +} + +impl Xattr { + pub fn new(bid: Bid, inode: Weak, fs: Weak) -> Self { + let blocks_buf: USegment = FrameAllocOptions::new() + .zeroed(true) + .alloc_segment(XATTR_NBLOCKS) + .unwrap() + .into(); + Self { + cache: RwMutex::new(Dirty::new(XattrCache::new(bid, blocks_buf.size()))), + blocks_buf, + inode, + fs, + } + } + + /// Lazily initialize the xattr structures only when actual xattr operations are performed. + fn lazy_init(&self) -> Result<()> { + let fs = self.fs(); + let cache = self.cache.upread(); + + // Need to allocate a new xattr block + if cache.bid.to_raw() == 0 { + assert!(cache.header.is_none()); + let new_bid = { + let allocated = fs + .alloc_blocks(self.inode().block_group_idx(), XATTR_NBLOCKS as _) + .ok_or(Error::new(Errno::ENOSPC))? + .start as u64; + Bid::new(allocated) + }; + + let new_header = XattrHeader::default(); + self.blocks_buf.write_val(0, &new_header)?; + + let mut cache = cache.upgrade(); + cache.header = Some(new_header); + cache.bid = new_bid; + self.inode().set_acl(new_bid); + // Need to load the xattr block from device + } else if cache.header.is_none() { + fs.block_device().read_blocks( + cache.bid, + BioSegment::new_from_segment(self.blocks_buf.clone(), BioDirection::FromDevice), + )?; + + let header = self.blocks_buf.read_val::(0)?; + if header.magic != EXT2_XATTR_MAGIC { + return_errno_with_message!(Errno::EINVAL, "invalid xattr magic"); + } + + let mut cache = cache.upgrade(); + cache.header = Some(header); + cache.build_entries_from_buf(&self.blocks_buf)?; + } + Ok(()) + } + + pub fn set( + &self, + name: XattrName, + value_reader: &mut VmReader, + flags: XattrSetFlags, + ) -> Result<()> { + self.lazy_init()?; + + let namespace = name.namespace(); + let name_len = name.full_name_len(); + let value_len = value_reader.remain(); + let cache = self.cache.upread(); + if let Some((offset, mut entry)) = cache.find_entry(&name, &self.blocks_buf) { + if flags.contains(XattrSetFlags::CREATE_ONLY) { + return_errno_with_message!(Errno::EEXIST, "the target xattr already exists"); + } + + if value_len <= entry.value_len as usize { + self.blocks_buf + .write(entry.value_offset as _, value_reader)?; + + if value_len != entry.value_len as usize { + entry.value_len = value_len as _; + self.blocks_buf.write_val(offset, &entry)?; + + let mut cache = cache.upgrade(); + let _ = cache.entries.insert(offset, entry); + let _ = cache.values.insert(entry.value_offset as _, value_len); + } + } else { + let new_value_offset = cache + .find_room_for_new_value(value_len) + .ok_or(Error::new(Errno::ENOSPC))?; + self.blocks_buf.write(new_value_offset, value_reader)?; + + let mut cache = cache.upgrade(); + let _ = cache.values.remove(&(entry.value_offset as _)); + entry.value_offset = new_value_offset as _; + entry.value_len = value_len as _; + self.blocks_buf.write_val(offset, &entry)?; + + let _ = cache.entries.insert(offset, entry); + let _ = cache.values.insert(new_value_offset as _, value_len); + } + } else { + if flags.contains(XattrSetFlags::REPLACE_ONLY) { + return_errno_with_message!(Errno::ENODATA, "the target xattr does not exist"); + } + + let new_entry_offset = cache + .find_room_for_new_entry(name_len) + .ok_or(Error::new(Errno::ENOSPC))?; + let new_value_offset = cache + .find_room_for_new_value(value_len) + .ok_or(Error::new(Errno::ENOSPC))?; + let new_entry = XattrEntry { + name_len: name_len as _, + name_index: namespace as _, + value_offset: new_value_offset as _, + value_block: 0, // TBD + value_len: value_len as _, + hash: 0, // TBD + }; + + self.blocks_buf.write_val(new_entry_offset, &new_entry)?; + self.blocks_buf.write_bytes( + new_entry_offset + XATTR_ENTRY_SIZE, + name.full_name().as_bytes(), + )?; + self.blocks_buf.write(new_value_offset, value_reader)?; + + let mut cache = cache.upgrade(); + let _ = cache.entries.insert(new_entry_offset, new_entry); + let _ = cache.values.insert(new_value_offset, value_len); + } + + Ok(()) + } + + pub fn get(&self, name: XattrName, value_writer: &mut VmWriter) -> Result { + self.lazy_init()?; + + let value_avail_len = value_writer.avail(); + let (_, entry) = self + .cache + .read() + .find_entry(&name, &self.blocks_buf) + .ok_or(Error::new(Errno::ENODATA))?; + + let value_len = entry.value_len as usize; + if value_avail_len == 0 { + return Ok(value_len); + } + if value_len > value_avail_len { + return_errno_with_message!(Errno::ERANGE, "the xattr value buffer is too small"); + } + + value_writer.write_fallible( + &mut self + .blocks_buf + .reader() + .to_fallible() + .skip(entry.value_offset as usize) + .limit(value_len), + )?; + + Ok(value_len) + } + + pub fn list(&self, namespace: XattrNamespace, list_writer: &mut VmWriter) -> Result { + self.lazy_init()?; + + let list_avail_len = list_writer.avail(); + let cache = self.cache.read(); + + let target_list: Vec<_> = cache + .entries + .iter() + .filter_map(|(offset, entry)| { + if namespace.is_user() && entry.name_index != XattrNamespace::User as u8 { + None + } else { + Some((offset, entry.name_len as usize)) + } + }) + .collect(); + // Include the null byte following each name + let list_actual_len = target_list + .iter() + .map(|(_, name_len)| name_len) + .sum::() + + target_list.len(); + + if list_avail_len == 0 { + return Ok(list_actual_len); + } + if list_actual_len > list_avail_len { + return_errno_with_message!(Errno::ERANGE, "the xattr list buffer is too small"); + } + + for (offset, name_len) in target_list { + let mut reader = self.blocks_buf.reader().to_fallible(); + let name_reader = reader.skip(offset + XATTR_ENTRY_SIZE).limit(name_len); + + list_writer.write_fallible(name_reader)?; + list_writer.write_val(&0u8)?; + } + + Ok(list_actual_len) + } + + pub fn remove(&self, name: XattrName) -> Result<()> { + self.lazy_init()?; + + let cache = self.cache.upread(); + let (offset, entry) = + cache + .find_entry(&name, &self.blocks_buf) + .ok_or(Error::with_message( + Errno::ENODATA, + "the target xattr does not exist", + ))?; + + let len = entry.total_len(); + self.blocks_buf + .writer() + .to_fallible() + .skip(offset) + .limit(len) + .fill_zeros(len)?; + + let mut cache = cache.upgrade(); + let _ = cache.entries.remove(&offset); + let _ = cache.values.remove(&(entry.value_offset as _)); + + Ok(()) + } + + pub fn flush(&self) -> Result<()> { + let cache = self.cache.upread(); + if cache.is_dirty() { + self.fs().block_device().write_blocks( + cache.bid, + BioSegment::new_from_segment(self.blocks_buf.clone(), BioDirection::ToDevice), + )?; + cache.upgrade().clear_dirty(); + } + Ok(()) + } + + pub fn free(&self) -> Result<()> { + let cache = self.cache.upread(); + let bid = cache.bid.to_raw() as Ext2Bid; + if bid == 0 { + return Ok(()); + } + self.fs().free_blocks(bid..bid)?; + cache.upgrade().bid = Bid::new(0); + Ok(()) + } + + fn fs(&self) -> Arc { + self.fs.upgrade().unwrap() + } + + fn inode(&self) -> Arc { + self.inode.upgrade().unwrap() + } +} + +impl XattrCache { + pub fn new(bid: Bid, capacity_bytes: usize) -> Self { + Self { + bid, + header: None, + entries: BTreeMap::new(), + values: BTreeMap::new(), + capacity_bytes, + } + } + + pub fn build_entries_from_buf(&mut self, blocks_buf: &USegment) -> Result<()> { + let mut offset = XATTR_HEADER_SIZE; + + while offset < self.capacity_bytes - XATTR_ENTRY_VALUE_GAP - XATTR_ENTRY_SIZE { + let entry = blocks_buf.read_val::(offset)?; + if entry.name_len == 0 + || XattrNamespace::try_from(entry.name_index).is_err() + || entry.value_offset as usize + entry.value_len as usize > self.capacity_bytes + { + offset += XATTR_ALIGN; + continue; + } + + let _ = self.entries.insert(offset, entry); + let _ = self + .values + .insert(entry.value_offset as _, entry.value_len as _); + offset += entry.total_len(); + } + Ok(()) + } + + pub fn find_entry(&self, name: &XattrName, buf: &USegment) -> Option<(usize, XattrEntry)> { + let namespace = name.namespace(); + let name_bytes = name.full_name().as_bytes(); + let name_len = name_bytes.len(); + debug_assert!(name_len > 0); + let mut name_buf = [0u8; XATTR_NAME_MAX_LEN]; + for (offset, entry) in &self.entries { + if entry.name_index == namespace as u8 && entry.name_len == name_len as u8 { + buf.read_bytes(offset + XATTR_ENTRY_SIZE, &mut name_buf[..name_len]) + .unwrap(); + if &name_buf[..name_len] == name_bytes { + return Some((*offset, *entry)); + } + } + } + None + } + + pub fn find_room_for_new_entry(&self, name_len: usize) -> Option { + let target_len = XattrEntry::target_len(name_len); + + // Fast path: find at the entries' end + let bottom = self + .values + .first_key_value() + .map(|(offset, _)| *offset) + .unwrap_or(self.capacity_bytes) + - XATTR_ENTRY_VALUE_GAP; + let last_entry_end = self + .entries + .last_key_value() + .map(|(offset, entry)| *offset + entry.total_len()) + .unwrap_or(XATTR_HEADER_SIZE); + if bottom - last_entry_end >= target_len { + return Some(last_entry_end); + } + + // Slow path: find around the gaps + let mut pre_end = XATTR_HEADER_SIZE; + for (offset, entry) in &self.entries { + if offset - pre_end >= target_len { + return Some(pre_end); + } else { + pre_end = offset + entry.total_len(); + } + } + + None + } + + pub fn find_room_for_new_value(&self, value_len: usize) -> Option { + let target_len = value_len.align_up(XATTR_ALIGN); + + // Fast path: find at the values' end + let top = self + .entries + .last_key_value() + .map(|(offset, entry)| *offset + entry.total_len()) + .unwrap_or(XATTR_HEADER_SIZE) + + XATTR_ENTRY_VALUE_GAP; + let last_value_start = self + .values + .first_key_value() + .map(|(offset, _)| *offset) + .unwrap_or(self.capacity_bytes); + + if last_value_start - top >= target_len { + return Some(last_value_start - target_len); + } + + // Slow path: find around the gaps + let mut pre_end = self.capacity_bytes; + for (offset, value_len) in self.values.iter().rev() { + let cur_end = offset + value_len.align_up(XATTR_ALIGN); + if pre_end - cur_end >= target_len { + return Some(cur_end); + } else { + pre_end = *offset; + } + } + + None + } +} + +impl Default for XattrHeader { + fn default() -> Self { + Self { + magic: EXT2_XATTR_MAGIC, + nblocks: XATTR_NBLOCKS as _, + ref_count: Default::default(), + hash: Default::default(), + reserved: Default::default(), + } + } +}