From: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx> Allow Rust file system modules to create inodes that are symlinks, pipes, sockets, char devices and block devices (in addition to the already-supported directories and regular files). Signed-off-by: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx> --- rust/helpers.c | 13 ++++ rust/kernel/fs/file.rs | 5 ++ rust/kernel/fs/inode.rs | 131 +++++++++++++++++++++++++++++++++++++- samples/rust/rust_rofs.rs | 43 ++++++++++++- 4 files changed, 187 insertions(+), 5 deletions(-) diff --git a/rust/helpers.c b/rust/helpers.c index 2db5df578df2..360a1d38ac19 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -288,6 +288,12 @@ void rust_helper_mapping_set_large_folios(struct address_space *mapping) } EXPORT_SYMBOL_GPL(rust_helper_mapping_set_large_folios); +unsigned int rust_helper_MKDEV(unsigned int major, unsigned int minor) +{ + return MKDEV(major, minor); +} +EXPORT_SYMBOL_GPL(rust_helper_MKDEV); + unsigned long rust_helper_copy_to_user(void __user *to, const void *from, unsigned long n) { @@ -307,6 +313,13 @@ void rust_helper_inode_unlock_shared(struct inode *inode) } EXPORT_SYMBOL_GPL(rust_helper_inode_unlock_shared); +void rust_helper_set_delayed_call(struct delayed_call *call, + void (*fn)(void *), void *arg) +{ + set_delayed_call(call, fn, arg); +} +EXPORT_SYMBOL_GPL(rust_helper_set_delayed_call); + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs index 0828676eae1c..a819724b75f8 100644 --- a/rust/kernel/fs/file.rs +++ b/rust/kernel/fs/file.rs @@ -521,8 +521,13 @@ pub enum DirEntryType { impl From<inode::Type> for DirEntryType { fn from(value: inode::Type) -> Self { match value { + inode::Type::Fifo => DirEntryType::Fifo, + inode::Type::Chr(_, _) => DirEntryType::Chr, inode::Type::Dir => DirEntryType::Dir, + inode::Type::Blk(_, _) => DirEntryType::Blk, inode::Type::Reg => DirEntryType::Reg, + inode::Type::Lnk => DirEntryType::Lnk, + inode::Type::Sock => DirEntryType::Sock, } } } diff --git a/rust/kernel/fs/inode.rs b/rust/kernel/fs/inode.rs index 1a41c824d30d..75b68d697a6e 100644 --- a/rust/kernel/fs/inode.rs +++ b/rust/kernel/fs/inode.rs @@ -10,8 +10,8 @@ address_space, dentry, dentry::DEntry, file, sb::SuperBlock, FileSystem, Offset, UnspecifiedFS, }; use crate::error::{code::*, Result}; -use crate::types::{ARef, AlwaysRefCounted, Lockable, Locked, Opaque}; -use crate::{bindings, block, time::Timespec}; +use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Lockable, Locked, Opaque}; +use crate::{bindings, block, str::CStr, str::CString, time::Timespec}; use core::mem::ManuallyDrop; use core::{marker::PhantomData, ptr}; use macros::vtable; @@ -25,6 +25,18 @@ pub trait Operations { /// File system that these operations are compatible with. type FileSystem: FileSystem + ?Sized; + /// Returns the string that represents the name of the file a symbolic link inode points to. + /// + /// When `dentry` is `None`, `get_link` is called with the RCU read-side lock held, so it may + /// not sleep. Implementations must return `Err(ECHILD)` for it to be called again without + /// holding the RCU lock. + fn get_link<'a>( + _dentry: Option<&DEntry<Self::FileSystem>>, + _inode: &'a INode<Self::FileSystem>, + ) -> Result<Either<CString, &'a CStr>> { + Err(ENOTSUPP) + } + /// Returns the inode corresponding to the directory entry with the given name. fn lookup( _parent: &Locked<&INode<Self::FileSystem>, ReadSem>, @@ -134,6 +146,52 @@ pub fn init(mut self, params: Params) -> Result<ARef<INode<T>>> { unsafe { bindings::mapping_set_large_folios(inode.i_mapping) }; bindings::S_IFREG } + Type::Lnk => { + // If we are using `page_get_link`, we need to prevent the use of high mem. + if !inode.i_op.is_null() { + // SAFETY: We just checked that `i_op` is non-null, and we always just set it + // to valid values. + if unsafe { + (*inode.i_op).get_link == bindings::page_symlink_inode_operations.get_link + } { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::inode_nohighmem(inode) }; + } + } + bindings::S_IFLNK + } + Type::Fifo => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::init_special_inode(inode, bindings::S_IFIFO as _, 0) }; + bindings::S_IFIFO + } + Type::Sock => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::init_special_inode(inode, bindings::S_IFSOCK as _, 0) }; + bindings::S_IFSOCK + } + Type::Chr(major, minor) => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { + bindings::init_special_inode( + inode, + bindings::S_IFCHR as _, + bindings::MKDEV(major, minor & bindings::MINORMASK), + ) + }; + bindings::S_IFCHR + } + Type::Blk(major, minor) => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { + bindings::init_special_inode( + inode, + bindings::S_IFBLK as _, + bindings::MKDEV(major, minor & bindings::MINORMASK), + ) + }; + bindings::S_IFBLK + } }; inode.i_mode = (params.mode & 0o777) | u16::try_from(mode)?; @@ -194,11 +252,26 @@ fn drop(&mut self) { /// The type of an inode. #[derive(Copy, Clone)] pub enum Type { + /// Named pipe (first-in, first-out) type. + Fifo, + + /// Character device type. + Chr(u32, u32), + /// Directory type. Dir, + /// Block device type. + Blk(u32, u32), + /// Regular file type. Reg, + + /// Symbolic link type. + Lnk, + + /// Named unix-domain socket type. + Sock, } /// Required inode parameters. @@ -245,6 +318,15 @@ pub struct Params { pub struct Ops<T: FileSystem + ?Sized>(*const bindings::inode_operations, PhantomData<T>); impl<T: FileSystem + ?Sized> Ops<T> { + /// Returns inode operations for symbolic links that are stored in a single page. + pub fn page_symlink_inode() -> Self { + // SAFETY: This is a constant in C, it never changes. + Self( + unsafe { &bindings::page_symlink_inode_operations }, + PhantomData, + ) + } + /// Creates the inode operations from a type that implements the [`Operations`] trait. pub const fn new<U: Operations<FileSystem = T> + ?Sized>() -> Self { struct Table<T: Operations + ?Sized>(PhantomData<T>); @@ -255,7 +337,11 @@ impl<T: Operations + ?Sized> Table<T> { } else { None }, - get_link: None, + get_link: if T::HAS_GET_LINK { + Some(Self::get_link_callback) + } else { + None + }, permission: None, get_inode_acl: None, readlink: None, @@ -303,6 +389,45 @@ extern "C" fn lookup_callback( Ok(Some(ret)) => ManuallyDrop::new(ret).0.get(), } } + + extern "C" fn get_link_callback( + dentry_ptr: *mut bindings::dentry, + inode_ptr: *mut bindings::inode, + delayed_call: *mut bindings::delayed_call, + ) -> *const core::ffi::c_char { + extern "C" fn drop_cstring(ptr: *mut core::ffi::c_void) { + // SAFETY: The argument came from a previous call to `into_foreign` below. + unsafe { CString::from_foreign(ptr) }; + } + + let dentry = if dentry_ptr.is_null() { + None + } else { + // SAFETY: The C API guarantees that `dentry_ptr` is a valid dentry when it's + // non-null. + Some(unsafe { DEntry::from_raw(dentry_ptr) }) + }; + + // SAFETY: The C API guarantees that `parent_ptr` is a valid inode. + let inode = unsafe { INode::from_raw(inode_ptr) }; + + match T::get_link(dentry, inode) { + Err(e) => e.to_ptr::<core::ffi::c_char>(), + Ok(Either::Right(str)) => str.as_char_ptr(), + Ok(Either::Left(str)) => { + let ptr = str.into_foreign(); + unsafe { + bindings::set_delayed_call( + delayed_call, + Some(drop_cstring), + ptr.cast_mut(), + ) + }; + + ptr.cast::<core::ffi::c_char>() + } + } + } } Self(&Table::<U>::TABLE, PhantomData) } diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 8005fd14b2e1..7a09e2db878d 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -7,7 +7,7 @@ }; use kernel::prelude::*; use kernel::types::{ARef, Either, Locked}; -use kernel::{c_str, folio::Folio, folio::PageCache, fs, time::UNIX_EPOCH, user}; +use kernel::{c_str, folio::Folio, folio::PageCache, fs, str::CString, time::UNIX_EPOCH, user}; kernel::module_fs! { type: RoFs, @@ -24,7 +24,7 @@ struct Entry { contents: &'static [u8], } -const ENTRIES: [Entry; 3] = [ +const ENTRIES: [Entry; 4] = [ Entry { name: b".", ino: 1, @@ -43,11 +43,18 @@ struct Entry { etype: inode::Type::Reg, contents: b"hello world\n", }, + Entry { + name: b"link.txt", + ino: 3, + etype: inode::Type::Lnk, + contents: b"./test.txt", + }, ]; const DIR_FOPS: file::Ops<RoFs> = file::Ops::new::<RoFs>(); const DIR_IOPS: inode::Ops<RoFs> = inode::Ops::new::<RoFs>(); const FILE_AOPS: address_space::Ops<RoFs> = address_space::Ops::new::<RoFs>(); +const LNK_IOPS: inode::Ops<RoFs> = inode::Ops::new::<Link>(); struct RoFs; @@ -68,6 +75,11 @@ fn iget(sb: &sb::SuperBlock<Self>, e: &'static Entry) -> Result<ARef<INode<Self> .set_aops(FILE_AOPS); (0o444, 1, e.contents.len().try_into()?) } + inode::Type::Lnk => { + new.set_iops(LNK_IOPS); + (0o444, 1, e.contents.len().try_into()?) + } + _ => return Err(ENOENT), }; new.init(inode::Params { @@ -123,6 +135,33 @@ fn lookup( } } +struct Link; +#[vtable] +impl inode::Operations for Link { + type FileSystem = RoFs; + + fn get_link<'a>( + dentry: Option<&DEntry<RoFs>>, + inode: &'a INode<RoFs>, + ) -> Result<Either<CString, &'a CStr>> { + if dentry.is_none() { + return Err(ECHILD); + } + + let name_buf = match inode.ino() { + 3 => ENTRIES[3].contents, + _ => return Err(EINVAL), + }; + let mut name = Box::new_slice( + name_buf.len().checked_add(1).ok_or(ENOMEM)?, + b'\0', + GFP_NOFS, + )?; + name[..name_buf.len()].copy_from_slice(name_buf); + Ok(Either::Left(name.try_into()?)) + } +} + #[vtable] impl address_space::Operations for RoFs { type FileSystem = Self; -- 2.34.1