Skip to content

Commit 06e110b

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 af63dba commit 06e110b

File tree

3 files changed

+129
-14
lines changed

3 files changed

+129
-14
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
@@ -16,10 +16,11 @@ pub mod ro {
1616
use crate::error::{code::*, from_result, to_result, Error, Result};
1717
use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque};
1818
use crate::{
19-
bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Time, try_pin_init,
20-
ThisModule,
19+
bindings, container_of, folio::LockedFolio, init::PinInit, mem_cache::MemCache, str::CStr,
20+
time::Time, try_pin_init, ThisModule,
2121
};
22-
use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr};
22+
use core::mem::{size_of, ManuallyDrop, MaybeUninit};
23+
use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
2324
use macros::{pin_data, pinned_drop};
2425

2526
/// Type of superblock keying.
@@ -38,6 +39,9 @@ pub mod ro {
3839
/// Data associated with each file system instance (super-block).
3940
type Data: ForeignOwnable + Send + Sync;
4041

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

@@ -173,6 +177,7 @@ pub mod ro {
173177
pub struct Registration {
174178
#[pin]
175179
fs: Opaque<bindings::file_system_type>,
180+
inode_cache: Option<MemCache>,
176181
#[pin]
177182
_pin: PhantomPinned,
178183
}
@@ -190,6 +195,14 @@ pub mod ro {
190195
pub fn new<T: Type + ?Sized>(module: &'static ThisModule) -> impl PinInit<Self, Error> {
191196
try_pin_init!(Self {
192197
_pin: PhantomPinned,
198+
inode_cache: if size_of::<T::INodeData>() == 0 {
199+
None
200+
} else {
201+
Some(MemCache::try_new::<INodeWithData<T::INodeData>>(
202+
T::NAME,
203+
Some(Self::inode_init_once_callback::<T>),
204+
)?)
205+
},
193206
fs <- Opaque::try_ffi_init(|fs_ptr| {
194207
// SAFETY: `pin_init_from_closure` guarantees that `fs_ptr` is valid for write.
195208
let fs = unsafe { &mut *fs_ptr };
@@ -244,6 +257,16 @@ pub mod ro {
244257
unsafe { T::Data::from_foreign(ptr) };
245258
}
246259
}
260+
261+
unsafe extern "C" fn inode_init_once_callback<T: Type + ?Sized>(
262+
outer_inode: *mut core::ffi::c_void,
263+
) {
264+
let ptr = outer_inode.cast::<INodeWithData<T::INodeData>>();
265+
266+
// SAFETY: This is only used in `new`, so we know that we have a valid `INodeWithData`
267+
// instance whose inode part can be initialised.
268+
unsafe { bindings::inode_init_once(ptr::addr_of_mut!((*ptr).inode)) };
269+
}
247270
}
248271

249272
#[pinned_drop]
@@ -280,6 +303,15 @@ pub mod ro {
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 @@ pub mod ro {
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: Type + ?Sized>(ARef<INode<T>>);
306343

307344
impl<T: Type + ?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 => {
@@ -425,7 +471,7 @@ pub mod ro {
425471
/// Required inode parameters.
426472
///
427473
/// This is used when creating new inodes.
428-
pub struct INodeParams {
474+
pub struct INodeParams<T> {
429475
/// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to
430476
/// everyone, the owner group, and the owner.
431477
pub mode: u16,
@@ -460,6 +506,9 @@ pub mod ro {
460506

461507
/// Last access time.
462508
pub atime: Time,
509+
510+
/// Value to attach to this node.
511+
pub value: T,
463512
}
464513

465514
/// A file system super block.
@@ -759,8 +808,12 @@ pub mod ro {
759808
}
760809

761810
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
762-
alloc_inode: None,
763-
destroy_inode: None,
811+
alloc_inode: if size_of::<T::INodeData>() != 0 {
812+
Some(Self::alloc_inode_callback)
813+
} else {
814+
None
815+
},
816+
destroy_inode: Some(Self::destroy_inode_callback),
764817
free_inode: None,
765818
dirty_inode: None,
766819
write_inode: None,
@@ -790,6 +843,61 @@ pub mod ro {
790843
shutdown: None,
791844
};
792845

846+
unsafe extern "C" fn alloc_inode_callback(
847+
sb: *mut bindings::super_block,
848+
) -> *mut bindings::inode {
849+
// SAFETY: The callback contract guarantees that `sb` is valid for read.
850+
let super_type = unsafe { (*sb).s_type };
851+
852+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
853+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
854+
// superblock associated to it.
855+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
856+
857+
// SAFETY: `sb` and `cache` are guaranteed to be valid by the callback contract and by
858+
// the existence of a superblock respectively.
859+
let ptr = unsafe {
860+
bindings::alloc_inode_sb(sb, MemCache::ptr(&reg.inode_cache), bindings::GFP_KERNEL)
861+
}
862+
.cast::<INodeWithData<T::INodeData>>();
863+
if ptr.is_null() {
864+
return ptr::null_mut();
865+
}
866+
ptr::addr_of_mut!((*ptr).inode)
867+
}
868+
869+
unsafe extern "C" fn destroy_inode_callback(inode: *mut bindings::inode) {
870+
// SAFETY: By the C contrat, inode is a valid pointer.
871+
let is_bad = unsafe { bindings::is_bad_inode(inode) };
872+
873+
// SAFETY: The inode is guaranteed to be valid by the callback contract. Additionally,
874+
// the superblock is also guaranteed to still be valid by the inode existence.
875+
let super_type = unsafe { (*(*inode).i_sb).s_type };
876+
877+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
878+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
879+
// superblock associated to it.
880+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
881+
let ptr = container_of!(inode, INodeWithData<T::INodeData>, inode).cast_mut();
882+
883+
if !is_bad {
884+
// SAFETY: The code either initialises the data or marks the inode as bad, since
885+
// it's not bad, it's safey to drop it here.
886+
unsafe { ptr::drop_in_place((*ptr).data.as_mut_ptr()) };
887+
}
888+
889+
if size_of::<T::INodeData>() == 0 {
890+
// SAFETY: When the size of `INodeData` is zero, we don't use a separate
891+
// mem_cache, so it is allocated from the regular mem_cache, which is what
892+
// `free_inode_nonrcu` uses to free the inode.
893+
unsafe { bindings::free_inode_nonrcu(inode) };
894+
} else {
895+
// The callback contract guarantees that the inode was previously allocated via the
896+
// `alloc_inode_callback` callback, so it is safe to free it back to the cache.
897+
unsafe { bindings::kmem_cache_free(MemCache::ptr(&reg.inode_cache), ptr.cast()) };
898+
}
899+
}
900+
793901
unsafe extern "C" fn statfs_callback(
794902
dentry: *mut bindings::dentry,
795903
buf: *mut bindings::kstatfs,
@@ -1089,6 +1197,7 @@ pub mod ro {
10891197
/// impl fs::ro::Type for MyFs {
10901198
/// // ...
10911199
/// # type Data = ();
1200+
/// # type INodeData = ();
10921201
/// # const NAME: &'static CStr = c_str!("myfs");
10931202
/// # fn fill_super(
10941203
/// # _: fs::ro::NewSuperBlock<'_, Self>

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::ro::Type 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::ro::Type 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::ro::Type 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::ro::Type 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)