Skip to content

Commit aa434c5

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

File tree

4 files changed

+129
-16
lines changed

4 files changed

+129
-16
lines changed

rust/helpers.c

+7
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ void rust_helper_kunmap_local(const void *vaddr)
205205
}
206206
EXPORT_SYMBOL_GPL(rust_helper_kunmap_local);
207207

208+
void *rust_helper_alloc_inode_sb(struct super_block *sb,
209+
struct kmem_cache *cache, gfp_t gfp)
210+
{
211+
return alloc_inode_sb(sb, cache, gfp);
212+
}
213+
EXPORT_SYMBOL_GPL(rust_helper_alloc_inode_sb);
214+
208215
void rust_helper_i_uid_write(struct inode *inode, uid_t uid)
209216
{
210217
i_uid_write(inode, uid);

rust/kernel/fs.rs

+118-9
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
use crate::error::{code::*, from_result, to_result, Error, Result};
1010
use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque};
1111
use crate::{
12-
bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Timespec, try_pin_init,
13-
ThisModule,
12+
bindings, container_of, folio::LockedFolio, init::PinInit, mem_cache::MemCache, str::CStr,
13+
time::Timespec, try_pin_init, ThisModule,
1414
};
15-
use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr};
15+
use core::mem::{size_of, ManuallyDrop, MaybeUninit};
16+
use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
1617
use macros::{pin_data, pinned_drop};
1718

1819
pub mod buffer;
@@ -36,6 +37,9 @@ pub trait FileSystem {
3637
/// Data associated with each file system instance (super-block).
3738
type Data: ForeignOwnable + Send + Sync;
3839

40+
/// Type of data associated with each inode.
41+
type INodeData: Send + Sync;
42+
3943
/// The name of the file system type.
4044
const NAME: &'static CStr;
4145

@@ -171,6 +175,7 @@ impl core::convert::TryFrom<u32> for DirEntryType {
171175
pub struct Registration {
172176
#[pin]
173177
fs: Opaque<bindings::file_system_type>,
178+
inode_cache: Option<MemCache>,
174179
#[pin]
175180
_pin: PhantomPinned,
176181
}
@@ -188,6 +193,14 @@ impl Registration {
188193
pub fn new<T: FileSystem + ?Sized>(module: &'static ThisModule) -> impl PinInit<Self, Error> {
189194
try_pin_init!(Self {
190195
_pin: PhantomPinned,
196+
inode_cache: if size_of::<T::INodeData>() == 0 {
197+
None
198+
} else {
199+
Some(MemCache::try_new::<INodeWithData<T::INodeData>>(
200+
T::NAME,
201+
Some(Self::inode_init_once_callback::<T>),
202+
)?)
203+
},
191204
fs <- Opaque::try_ffi_init(|fs_ptr| {
192205
// SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write.
193206
let fs = unsafe { &mut *fs_ptr };
@@ -242,6 +255,16 @@ impl Registration {
242255
unsafe { T::Data::from_foreign(ptr) };
243256
}
244257
}
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+
}
245268
}
246269

247270
#[pinned_drop]
@@ -280,6 +303,15 @@ impl<T: FileSystem + ?Sized> INode<T> {
280303
unsafe { &*(*self.0.get()).i_sb.cast() }
281304
}
282305

306+
/// Returns the data associated with the inode.
307+
pub fn data(&self) -> &T::INodeData {
308+
let outerp = container_of!(self.0.get(), INodeWithData<T::INodeData>, inode);
309+
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference
310+
// (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an
311+
// `INode`.
312+
unsafe { &*(*outerp).data.as_ptr() }
313+
}
314+
283315
/// Returns the size of the inode contents.
284316
pub fn size(&self) -> i64 {
285317
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
@@ -300,15 +332,29 @@ unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
300332
}
301333
}
302334

335+
struct INodeWithData<T> {
336+
data: MaybeUninit<T>,
337+
inode: bindings::inode,
338+
}
339+
303340
/// An inode that is locked and hasn't been initialised yet.
304341
#[repr(transparent)]
305342
pub struct NewINode<T: FileSystem + ?Sized>(ARef<INode<T>>);
306343

307344
impl<T: FileSystem + ?Sized> NewINode<T> {
308345
/// 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() };
346+
pub fn init(self, params: INodeParams<T::INodeData>) -> Result<ARef<INode<T>>> {
347+
let outerp = container_of!(self.0 .0.get(), INodeWithData<T::INodeData>, inode);
348+
349+
// SAFETY: This is a newly-created inode. No other references to it exist, so it is
350+
// safe to mutably dereference it.
351+
let outer = unsafe { &mut *outerp.cast_mut() };
352+
353+
// N.B. We must always write this to a newly allocated inode because the free callback
354+
// expects the data to be initialised and drops it.
355+
outer.data.write(params.value);
356+
357+
let inode = &mut outer.inode;
312358

313359
let mode = match params.typ {
314360
INodeType::Dir => {
@@ -424,7 +470,7 @@ pub enum INodeType {
424470
/// Required inode parameters.
425471
///
426472
/// This is used when creating new inodes.
427-
pub struct INodeParams {
473+
pub struct INodeParams<T> {
428474
/// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to
429475
/// everyone, the owner group, and the owner.
430476
pub mode: u16,
@@ -459,6 +505,9 @@ pub struct INodeParams {
459505

460506
/// Last access time.
461507
pub atime: Timespec,
508+
509+
/// Value to attach to this node.
510+
pub value: T,
462511
}
463512

464513
/// A file system super block.
@@ -752,8 +801,12 @@ impl<T: FileSystem + ?Sized> Tables<T> {
752801
}
753802

754803
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
755-
alloc_inode: None,
756-
destroy_inode: None,
804+
alloc_inode: if size_of::<T::INodeData>() != 0 {
805+
Some(Self::alloc_inode_callback)
806+
} else {
807+
None
808+
},
809+
destroy_inode: Some(Self::destroy_inode_callback),
757810
free_inode: None,
758811
dirty_inode: None,
759812
write_inode: None,
@@ -783,6 +836,61 @@ impl<T: FileSystem + ?Sized> Tables<T> {
783836
shutdown: None,
784837
};
785838

839+
unsafe extern "C" fn alloc_inode_callback(
840+
sb: *mut bindings::super_block,
841+
) -> *mut bindings::inode {
842+
// SAFETY: The callback contract guarantees that `sb` is valid for read.
843+
let super_type = unsafe { (*sb).s_type };
844+
845+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
846+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
847+
// superblock associated to it.
848+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
849+
850+
// SAFETY: `sb` and `cache` are guaranteed to be valid by the callback contract and by
851+
// the existence of a superblock respectively.
852+
let ptr = unsafe {
853+
bindings::alloc_inode_sb(sb, MemCache::ptr(&reg.inode_cache), bindings::GFP_KERNEL)
854+
}
855+
.cast::<INodeWithData<T::INodeData>>();
856+
if ptr.is_null() {
857+
return ptr::null_mut();
858+
}
859+
ptr::addr_of_mut!((*ptr).inode)
860+
}
861+
862+
unsafe extern "C" fn destroy_inode_callback(inode: *mut bindings::inode) {
863+
// SAFETY: By the C contract, `inode` is a valid pointer.
864+
let is_bad = unsafe { bindings::is_bad_inode(inode) };
865+
866+
// SAFETY: The inode is guaranteed to be valid by the callback contract. Additionally, the
867+
// superblock is also guaranteed to still be valid by the inode existence.
868+
let super_type = unsafe { (*(*inode).i_sb).s_type };
869+
870+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
871+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
872+
// superblock associated to it.
873+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
874+
let ptr = container_of!(inode, INodeWithData<T::INodeData>, inode).cast_mut();
875+
876+
if !is_bad {
877+
// SAFETY: The code either initialises the data or marks the inode as bad. Since the
878+
// inode is not bad, the data is initialised, and thus safe to drop.
879+
unsafe { ptr::drop_in_place((*ptr).data.as_mut_ptr()) };
880+
}
881+
882+
if size_of::<T::INodeData>() == 0 {
883+
// SAFETY: When the size of `INodeData` is zero, we don't use a separate mem_cache, so
884+
// it is allocated from the regular mem_cache, which is what `free_inode_nonrcu` uses
885+
// to free the inode.
886+
unsafe { bindings::free_inode_nonrcu(inode) };
887+
} else {
888+
// The callback contract guarantees that the inode was previously allocated via the
889+
// `alloc_inode_callback` callback, so it is safe to free it back to the cache.
890+
unsafe { bindings::kmem_cache_free(MemCache::ptr(&reg.inode_cache), ptr.cast()) };
891+
}
892+
}
893+
786894
unsafe extern "C" fn statfs_callback(
787895
dentry: *mut bindings::dentry,
788896
buf: *mut bindings::kstatfs,
@@ -1079,6 +1187,7 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
10791187
/// struct MyFs;
10801188
/// impl fs::FileSystem for MyFs {
10811189
/// type Data = ();
1190+
/// type INodeData =();
10821191
/// const NAME: &'static CStr = c_str!("myfs");
10831192
/// fn fill_super(_: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>> {
10841193
/// todo!()

rust/kernel/mem_cache.rs

-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ pub(crate) struct MemCache {
1717
}
1818

1919
impl MemCache {
20-
#[allow(dead_code)]
2120
pub(crate) fn try_new<T>(
2221
name: &'static CStr,
2322
init: Option<unsafe extern "C" fn(*mut core::ffi::c_void)>,
@@ -37,7 +36,6 @@ impl MemCache {
3736
Ok(Self { ptr })
3837
}
3938

40-
#[allow(dead_code)]
4139
pub(crate) fn ptr(c: &Option<Self>) -> *mut bindings::kmem_cache {
4240
match c {
4341
Some(m) => m.ptr.as_ptr(),

samples/rust/rust_rofs.rs

+4-5
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 fill_super(sb: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>> {
@@ -77,6 +78,7 @@ impl fs::FileSystem for RoFs {
7778
atime: UNIX_EPOCH,
7879
ctime: UNIX_EPOCH,
7980
mtime: UNIX_EPOCH,
81+
value: &ENTRIES[0],
8082
})?,
8183
};
8284
sb.init_root(root)
@@ -125,6 +127,7 @@ impl fs::FileSystem for RoFs {
125127
atime: UNIX_EPOCH,
126128
ctime: UNIX_EPOCH,
127129
mtime: UNIX_EPOCH,
130+
value: e,
128131
}),
129132
};
130133
}
@@ -134,11 +137,7 @@ impl fs::FileSystem for RoFs {
134137
}
135138

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

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

0 commit comments

Comments
 (0)