Skip to content

Commit 9abb155

Browse files
committed
rust: fs: allow per-inode data
Allow Rust file systems to attach extra [typed] data to each inode. If no data is needed, use the regular inode kmem_cache, otherwise we create a new one. Signed-off-by: Wedson Almeida Filho <[email protected]>
1 parent 1deb44a commit 9abb155

File tree

4 files changed

+131
-15
lines changed

4 files changed

+131
-15
lines changed

rust/helpers.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ void rust_helper_kunmap_local(const void *vaddr)
222222
}
223223
EXPORT_SYMBOL_GPL(rust_helper_kunmap_local);
224224

225+
void *rust_helper_alloc_inode_sb(struct super_block *sb,
226+
struct kmem_cache *cache, gfp_t gfp)
227+
{
228+
return alloc_inode_sb(sb, cache, gfp);
229+
}
230+
EXPORT_SYMBOL_GPL(rust_helper_alloc_inode_sb);
231+
225232
void rust_helper_i_uid_write(struct inode *inode, uid_t uid)
226233
{
227234
i_uid_write(inode, uid);

rust/kernel/fs.rs

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
use crate::error::{code::*, from_result, to_result, Error, Result};
1010
use crate::folio::{LockedFolio, UniqueFolio};
1111
use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque, ScopeGuard};
12-
use crate::{bindings, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule};
13-
use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr};
12+
use crate::{
13+
bindings, container_of, init::PinInit, mem_cache::MemCache, str::CStr, time::Timespec,
14+
try_pin_init, ThisModule,
15+
};
16+
use core::mem::{size_of, ManuallyDrop, MaybeUninit};
17+
use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
1418
use macros::{pin_data, pinned_drop};
1519

1620
#[cfg(CONFIG_BUFFER_HEAD)]
@@ -35,6 +39,9 @@ pub trait FileSystem {
3539
/// Data associated with each file system instance (super-block).
3640
type Data: ForeignOwnable + Send + Sync;
3741

42+
/// Type of data associated with each inode.
43+
type INodeData: Send + Sync;
44+
3845
/// The name of the file system type.
3946
const NAME: &'static CStr;
4047

@@ -165,6 +172,7 @@ impl core::convert::TryFrom<u32> for DirEntryType {
165172
pub struct Registration {
166173
#[pin]
167174
fs: Opaque<bindings::file_system_type>,
175+
inode_cache: Option<MemCache>,
168176
#[pin]
169177
_pin: PhantomPinned,
170178
}
@@ -182,6 +190,14 @@ impl Registration {
182190
pub fn new<T: FileSystem + ?Sized>(module: &'static ThisModule) -> impl PinInit<Self, Error> {
183191
try_pin_init!(Self {
184192
_pin: PhantomPinned,
193+
inode_cache: if size_of::<T::INodeData>() == 0 {
194+
None
195+
} else {
196+
Some(MemCache::try_new::<INodeWithData<T::INodeData>>(
197+
T::NAME,
198+
Some(Self::inode_init_once_callback::<T>),
199+
)?)
200+
},
185201
fs <- Opaque::try_ffi_init(|fs_ptr: *mut bindings::file_system_type| {
186202
// SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write.
187203
unsafe { fs_ptr.write(bindings::file_system_type::default()) };
@@ -239,6 +255,16 @@ impl Registration {
239255
unsafe { T::Data::from_foreign(ptr) };
240256
}
241257
}
258+
259+
unsafe extern "C" fn inode_init_once_callback<T: FileSystem + ?Sized>(
260+
outer_inode: *mut core::ffi::c_void,
261+
) {
262+
let ptr = outer_inode.cast::<INodeWithData<T::INodeData>>();
263+
264+
// SAFETY: This is only used in `new`, so we know that we have a valid `INodeWithData`
265+
// instance whose inode part can be initialised.
266+
unsafe { bindings::inode_init_once(ptr::addr_of_mut!((*ptr).inode)) };
267+
}
242268
}
243269

244270
#[pinned_drop]
@@ -280,6 +306,15 @@ impl<T: FileSystem + ?Sized> INode<T> {
280306
unsafe { &*(*self.0.get()).i_sb.cast() }
281307
}
282308

309+
/// Returns the data associated with the inode.
310+
pub fn data(&self) -> &T::INodeData {
311+
let outerp = container_of!(self.0.get(), INodeWithData<T::INodeData>, inode);
312+
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference
313+
// (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an
314+
// `INode`.
315+
unsafe { &*(*outerp).data.as_ptr() }
316+
}
317+
283318
/// Returns the size of the inode contents.
284319
pub fn size(&self) -> i64 {
285320
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
@@ -300,15 +335,29 @@ unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
300335
}
301336
}
302337

338+
struct INodeWithData<T> {
339+
data: MaybeUninit<T>,
340+
inode: bindings::inode,
341+
}
342+
303343
/// An inode that is locked and hasn't been initialised yet.
304344
#[repr(transparent)]
305345
pub struct NewINode<T: FileSystem + ?Sized>(ARef<INode<T>>);
306346

307347
impl<T: FileSystem + ?Sized> NewINode<T> {
308348
/// Initialises the new inode with the given parameters.
309-
pub fn init(self, params: INodeParams) -> Result<ARef<INode<T>>> {
310-
// SAFETY: This is a new inode, so it's safe to manipulate it mutably.
311-
let inode = unsafe { &mut *self.0 .0.get() };
349+
pub fn init(self, params: INodeParams<T::INodeData>) -> Result<ARef<INode<T>>> {
350+
let outerp = container_of!(self.0 .0.get(), INodeWithData<T::INodeData>, inode);
351+
352+
// SAFETY: This is a newly-created inode. No other references to it exist, so it is
353+
// safe to mutably dereference it.
354+
let outer = unsafe { &mut *outerp.cast_mut() };
355+
356+
// N.B. We must always write this to a newly allocated inode because the free callback
357+
// expects the data to be initialised and drops it.
358+
outer.data.write(params.value);
359+
360+
let inode = &mut outer.inode;
312361

313362
let mode = match params.typ {
314363
INodeType::Dir => {
@@ -424,7 +473,7 @@ pub enum INodeType {
424473
/// Required inode parameters.
425474
///
426475
/// This is used when creating new inodes.
427-
pub struct INodeParams {
476+
pub struct INodeParams<T> {
428477
/// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to
429478
/// everyone, the owner group, and the owner.
430479
pub mode: u16,
@@ -459,6 +508,9 @@ pub struct INodeParams {
459508

460509
/// Last access time.
461510
pub atime: Timespec,
511+
512+
/// Value to attach to this node.
513+
pub value: T,
462514
}
463515

464516
/// A file system super block.
@@ -735,8 +787,12 @@ impl<T: FileSystem + ?Sized> Tables<T> {
735787
}
736788

737789
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
738-
alloc_inode: None,
739-
destroy_inode: None,
790+
alloc_inode: if size_of::<T::INodeData>() != 0 {
791+
Some(Self::alloc_inode_callback)
792+
} else {
793+
None
794+
},
795+
destroy_inode: Some(Self::destroy_inode_callback),
740796
free_inode: None,
741797
dirty_inode: None,
742798
write_inode: None,
@@ -766,6 +822,61 @@ impl<T: FileSystem + ?Sized> Tables<T> {
766822
shutdown: None,
767823
};
768824

825+
unsafe extern "C" fn alloc_inode_callback(
826+
sb: *mut bindings::super_block,
827+
) -> *mut bindings::inode {
828+
// SAFETY: The callback contract guarantees that `sb` is valid for read.
829+
let super_type = unsafe { (*sb).s_type };
830+
831+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
832+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
833+
// superblock associated to it.
834+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
835+
836+
// SAFETY: `sb` and `cache` are guaranteed to be valid by the callback contract and by
837+
// the existence of a superblock respectively.
838+
let ptr = unsafe {
839+
bindings::alloc_inode_sb(sb, MemCache::ptr(&reg.inode_cache), bindings::GFP_KERNEL)
840+
}
841+
.cast::<INodeWithData<T::INodeData>>();
842+
if ptr.is_null() {
843+
return ptr::null_mut();
844+
}
845+
ptr::addr_of_mut!((*ptr).inode)
846+
}
847+
848+
unsafe extern "C" fn destroy_inode_callback(inode: *mut bindings::inode) {
849+
// SAFETY: By the C contract, `inode` is a valid pointer.
850+
let is_bad = unsafe { bindings::is_bad_inode(inode) };
851+
852+
// SAFETY: The inode is guaranteed to be valid by the callback contract. Additionally, the
853+
// superblock is also guaranteed to still be valid by the inode existence.
854+
let super_type = unsafe { (*(*inode).i_sb).s_type };
855+
856+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
857+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
858+
// superblock associated to it.
859+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
860+
let ptr = container_of!(inode, INodeWithData<T::INodeData>, inode).cast_mut();
861+
862+
if !is_bad {
863+
// SAFETY: The code either initialises the data or marks the inode as bad. Since the
864+
// inode is not bad, the data is initialised, and thus safe to drop.
865+
unsafe { ptr::drop_in_place((*ptr).data.as_mut_ptr()) };
866+
}
867+
868+
if size_of::<T::INodeData>() == 0 {
869+
// SAFETY: When the size of `INodeData` is zero, we don't use a separate mem_cache, so
870+
// it is allocated from the regular mem_cache, which is what `free_inode_nonrcu` uses
871+
// to free the inode.
872+
unsafe { bindings::free_inode_nonrcu(inode) };
873+
} else {
874+
// The callback contract guarantees that the inode was previously allocated via the
875+
// `alloc_inode_callback` callback, so it is safe to free it back to the cache.
876+
unsafe { bindings::kmem_cache_free(MemCache::ptr(&reg.inode_cache), ptr.cast()) };
877+
}
878+
}
879+
769880
unsafe extern "C" fn statfs_callback(
770881
dentry: *mut bindings::dentry,
771882
buf: *mut bindings::kstatfs,
@@ -1120,6 +1231,7 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
11201231
/// struct MyFs;
11211232
/// impl fs::FileSystem for MyFs {
11221233
/// type Data = ();
1234+
/// type INodeData =();
11231235
/// const NAME: &'static CStr = c_str!("myfs");
11241236
/// fn super_params(_: &NewSuperBlock<Self>) -> Result<SuperParams<Self::Data>> {
11251237
/// todo!()

rust/kernel/mem_cache.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ impl MemCache {
2020
/// Allocates a new `kmem_cache` for type `T`.
2121
///
2222
/// `init` is called by the C code when entries are allocated.
23-
#[allow(dead_code)]
2423
pub(crate) fn try_new<T>(
2524
name: &'static CStr,
2625
init: Option<unsafe extern "C" fn(*mut core::ffi::c_void)>,
@@ -43,7 +42,6 @@ impl MemCache {
4342
/// Returns the pointer to the `kmem_cache` instance, or null if it's `None`.
4443
///
4544
/// This is a helper for functions like `alloc_inode_sb` where the cache is optional.
46-
#[allow(dead_code)]
4745
pub(crate) fn ptr(c: &Option<Self>) -> *mut bindings::kmem_cache {
4846
match c {
4947
Some(m) => m.ptr.as_ptr(),

samples/rust/rust_rofs.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const ENTRIES: [Entry; 4] = [
5353
struct RoFs;
5454
impl fs::FileSystem for RoFs {
5555
type Data = ();
56+
type INodeData = &'static Entry;
5657
const NAME: &'static CStr = c_str!("rust-fs");
5758

5859
fn super_params(_sb: &NewSuperBlock<Self>) -> Result<SuperParams<Self::Data>> {
@@ -79,6 +80,7 @@ impl fs::FileSystem for RoFs {
7980
atime: UNIX_EPOCH,
8081
ctime: UNIX_EPOCH,
8182
mtime: UNIX_EPOCH,
83+
value: &ENTRIES[0],
8284
}),
8385
}
8486
}
@@ -122,6 +124,7 @@ impl fs::FileSystem for RoFs {
122124
atime: UNIX_EPOCH,
123125
ctime: UNIX_EPOCH,
124126
mtime: UNIX_EPOCH,
127+
value: e,
125128
}),
126129
};
127130
}
@@ -131,11 +134,7 @@ impl fs::FileSystem for RoFs {
131134
}
132135

133136
fn read_folio(inode: &INode<Self>, mut folio: LockedFolio<'_>) -> Result {
134-
let data = match inode.ino() {
135-
2 => ENTRIES[2].contents,
136-
3 => ENTRIES[3].contents,
137-
_ => return Err(EINVAL),
138-
};
137+
let data = inode.data().contents;
139138

140139
let pos = usize::try_from(folio.pos()).unwrap_or(usize::MAX);
141140
let copied = if pos >= data.len() {

0 commit comments

Comments
 (0)