Implement the listxattr callback in the filesystem abstractions. Implement both read_xattr and listxattr for PuzzleFS. Signed-off-by: Ariel Miculas <amiculas@xxxxxxxxx> --- fs/puzzlefs/puzzlefs.rs | 50 +++++++++++++++++++++++++++++-- rust/kernel/fs/inode.rs | 66 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/fs/puzzlefs/puzzlefs.rs b/fs/puzzlefs/puzzlefs.rs index a062bf0249f6..9622ea71eda0 100644 --- a/fs/puzzlefs/puzzlefs.rs +++ b/fs/puzzlefs/puzzlefs.rs @@ -107,8 +107,8 @@ fn fill_super( _: Option<inode::Mapper>, ) -> Result<Box<PuzzleFS>> { let puzzlefs = PuzzleFS::open( - c_str!("/home/puzzlefs_oci"), - c_str!("83aa96c40a20671edc4490cfefadbb487b2ab23dfc0570049b56f0cc49b56eaf"), + c_str!("/home/puzzlefs_xattr"), + c_str!("ed63ace21eccceabab08d89afb75e94dae47973f82a17a172396a19ea953c8ab"), ); if let Err(ref e) = puzzlefs { @@ -124,6 +124,36 @@ fn init_root(sb: &sb::SuperBlock<Self>) -> Result<dentry::Root<Self>> { let inode = Self::iget(sb, 1)?; dentry::Root::try_new(inode) } + + fn read_xattr( + _dentry: &DEntry<Self>, + inode: &INode<Self>, + name: &CStr, + outbuf: &mut [u8], + ) -> Result<usize> { + let inode = inode.data(); + let readonly = outbuf.len() == 0; + // pr_info!("outbuf len {}\n", outbuf.len()); + + if let Some(add) = &inode.additional { + let xattr = add + .xattrs + .iter() + .find(|elem| elem.key == name.as_bytes()) + .ok_or(ENODATA)?; + if readonly { + return Ok(xattr.val.len()); + } + + if xattr.val.len() > outbuf.len() { + return Err(ERANGE); + } + + outbuf[0..xattr.val.len()].copy_from_slice(xattr.val.as_slice()); + return Ok(xattr.val.len()); + } + Err(ENODATA) + } } #[vtable] @@ -143,6 +173,22 @@ fn lookup( } } + fn listxattr( + inode: &INode<Self>, + mut add_entry: impl FnMut(&[i8]) -> Result<()>, + ) -> Result<()> { + let inode = inode.data(); + + if let Some(add) = &inode.additional { + for xattr in &add.xattrs { + // convert a u8 slice into an i8 slice + let i8slice = unsafe { &*(xattr.key.as_slice() as *const _ as *const [i8]) }; + add_entry(i8slice)?; + } + } + Ok(()) + } + fn get_link<'a>( dentry: Option<&DEntry<PuzzleFsModule>>, inode: &'a INode<PuzzleFsModule>, diff --git a/rust/kernel/fs/inode.rs b/rust/kernel/fs/inode.rs index b2b7d000080e..a092ee150d43 100644 --- a/rust/kernel/fs/inode.rs +++ b/rust/kernel/fs/inode.rs @@ -10,7 +10,7 @@ address_space, dentry, dentry::DEntry, file, mode, sb::SuperBlock, FileSystem, Offset, PageOffset, UnspecifiedFS, }; -use crate::error::{code::*, from_err_ptr, Result}; +use crate::error::{code::*, from_err_ptr, from_result, Result}; use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Lockable, Locked, Opaque}; use crate::{ bindings, block, build_error, container_of, folio, folio::Folio, mem_cache::MemCache, @@ -48,6 +48,14 @@ fn lookup( ) -> Result<Option<ARef<DEntry<Self::FileSystem>>>> { Err(ENOTSUPP) } + + /// Get extended attributes list + fn listxattr<'a>( + _inode: &'a INode<Self::FileSystem>, + mut _add_entry: impl FnMut(&[i8]) -> Result<()>, + ) -> Result<()> { + Err(ENOSYS) + } } /// A node (inode) in the file index. @@ -615,7 +623,7 @@ impl<T: Operations + ?Sized> Table<T> { rename: None, setattr: None, getattr: None, - listxattr: None, + listxattr: Some(Self::listxattr_callback), fiemap: None, update_time: None, atomic_open: None, @@ -688,6 +696,60 @@ extern "C" fn drop_cstring(ptr: *mut core::ffi::c_void) { } } } + + extern "C" fn listxattr_callback( + dentry: *mut bindings::dentry, + buffer: *mut core::ffi::c_char, + buffer_size: usize, + ) -> isize { + from_result(|| { + // SAFETY: The C API guarantees that `dentry` is valid for read. + let inode = unsafe { bindings::d_inode(dentry) }; + // SAFETY: The C API guarantees that `d_inode` inside `dentry` is valid for read. + let inode = unsafe { INode::from_raw(inode) }; + + // `buffer_size` should be 0 when `buffer` is NULL, but we enforce it + let (mut buffer_ptr, buffer_size) = match ptr::NonNull::new(buffer) { + Some(buf) => (buf, buffer_size), + None => (ptr::NonNull::dangling(), 0), + }; + + // SAFETY: The C API guarantees that `buffer` is at least `buffer_size` bytes in + // length. Also, when `buffer_size` is 0, `buffer_ptr` is NonNull::dangling, as + // suggested by `from_raw_parts_mut` documentation + let outbuf = unsafe { + core::slice::from_raw_parts_mut(buffer_ptr.as_mut(), buffer_size) + }; + + let mut offset = 0; + let mut total_len = 0; + + // The extended attributes keys must be placed into the output buffer sequentially, + // separated by the NUL character. We do this in the callback because it simplifies + // the implementation of the `listxattr` abstraction: the user just calls the + // add_entry function for each extended attribute key, passing a slice. + T::listxattr(inode, |xattr_key| { + let len = xattr_key.len(); + total_len += isize::try_from(len)? + 1; + + if buffer_size == 0 { + return Ok(()); + } + + let max = offset + len + 1; + if max > buffer_size { + return Err(ERANGE); + } + + outbuf[offset..max - 1].copy_from_slice(xattr_key); + outbuf[max - 1] = 0; + offset = max; + Ok(()) + })?; + + Ok(total_len) + }) + } } Self(&Table::<U>::TABLE, PhantomData) } -- 2.34.1