[RFC PATCH v2 06/30] rust: fs: introduce `DEntry<T>`
From: Wedson Almeida Filho
Date: Tue May 14 2024 - 09:20:00 EST
From: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx>
Signed-off-by: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx>
---
rust/helpers.c | 6 ++
rust/kernel/error.rs | 2 -
rust/kernel/fs.rs | 1 +
rust/kernel/fs/dentry.rs | 137 +++++++++++++++++++++++++++++++++++++++
4 files changed, 144 insertions(+), 2 deletions(-)
create mode 100644 rust/kernel/fs/dentry.rs
diff --git a/rust/helpers.c b/rust/helpers.c
index c697c1c4c9d7..c7fe6917251e 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -165,6 +165,12 @@ struct file *rust_helper_get_file(struct file *f)
EXPORT_SYMBOL_GPL(rust_helper_get_file);
+struct dentry *rust_helper_dget(struct dentry *dentry)
+{
+ return dget(dentry);
+}
+EXPORT_SYMBOL_GPL(rust_helper_dget);
+
loff_t rust_helper_i_size_read(const struct inode *inode)
{
return i_size_read(inode);
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index f4fa2847e210..bb13bd4a7fa6 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -261,8 +261,6 @@ pub fn to_result(err: core::ffi::c_int) -> Result {
/// from_err_ptr(unsafe { bindings::devm_platform_ioremap_resource(pdev.to_ptr(), index) })
/// }
/// ```
-// TODO: Remove `dead_code` marker once an in-kernel client is available.
-#[allow(dead_code)]
pub(crate) fn from_err_ptr<T>(ptr: *mut T) -> Result<*mut T> {
// CAST: Casting a pointer to `*const core::ffi::c_void` is always valid.
let const_ptr: *const core::ffi::c_void = ptr.cast();
diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs
index 89dcd5537830..4f07da71e1ec 100644
--- a/rust/kernel/fs.rs
+++ b/rust/kernel/fs.rs
@@ -13,6 +13,7 @@
use macros::{pin_data, pinned_drop};
use sb::SuperBlock;
+pub mod dentry;
pub mod inode;
pub mod sb;
diff --git a/rust/kernel/fs/dentry.rs b/rust/kernel/fs/dentry.rs
new file mode 100644
index 000000000000..6a36a48cd28b
--- /dev/null
+++ b/rust/kernel/fs/dentry.rs
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! File system directory entries.
+//!
+//! This module allows Rust code to use dentries.
+//!
+//! C headers: [`include/linux/dcache.h`](srctree/include/linux/dcache.h)
+
+use super::{inode::INode, FileSystem, SuperBlock};
+use crate::bindings;
+use crate::error::{code::*, from_err_ptr, Result};
+use crate::types::{ARef, AlwaysRefCounted, Opaque};
+use core::{marker::PhantomData, mem::ManuallyDrop, ops::Deref, ptr};
+
+/// A directory entry.
+///
+/// Wraps the kernel's `struct dentry`.
+///
+/// # Invariants
+///
+/// Instances of this type are always ref-counted, that is, a call to `dget` ensures that the
+/// allocation remains valid at least until the matching call to `dput`.
+#[repr(transparent)]
+pub struct DEntry<T: FileSystem + ?Sized>(pub(crate) Opaque<bindings::dentry>, PhantomData<T>);
+
+// SAFETY: The type invariants guarantee that `DEntry` is always ref-counted.
+unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for DEntry<T> {
+ fn inc_ref(&self) {
+ // SAFETY: The existence of a shared reference means that the refcount is nonzero.
+ unsafe { bindings::dget(self.0.get()) };
+ }
+
+ unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
+ // SAFETY: The safety requirements guarantee that the refcount is nonzero.
+ unsafe { bindings::dput(obj.as_ref().0.get()) }
+ }
+}
+
+impl<T: FileSystem + ?Sized> DEntry<T> {
+ /// Creates a new [`DEntry`] from a raw C pointer.
+ ///
+ /// # Safety
+ ///
+ /// * `ptr` must be valid for at least the lifetime of the returned reference.
+ /// * `ptr` has the correct file system type, or `T` is [`super::UnspecifiedFS`].
+ #[allow(dead_code)]
+ pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::dentry) -> &'a Self {
+ // SAFETY: The safety requirements guarantee that the reference is and remains valid.
+ unsafe { &*ptr.cast::<Self>() }
+ }
+
+ /// Returns the superblock of the dentry.
+ pub fn super_block(&self) -> &SuperBlock<T> {
+ // `d_sb` is immutable, so it's safe to read it.
+ unsafe { SuperBlock::from_raw((*self.0.get()).d_sb) }
+ }
+}
+
+/// A dentry that is known to be unhashed.
+pub struct Unhashed<'a, T: FileSystem + ?Sized>(pub(crate) &'a DEntry<T>);
+
+impl<T: FileSystem + ?Sized> Unhashed<'_, T> {
+ /// Splices a disconnected dentry into the tree if one exists.
+ pub fn splice_alias(self, inode: Option<ARef<INode<T>>>) -> Result<Option<ARef<DEntry<T>>>> {
+ let inode_ptr = if let Some(i) = inode {
+ // Reject inode if it belongs to a different superblock.
+ if !ptr::eq(i.super_block(), self.0.super_block()) {
+ return Err(EINVAL);
+ }
+
+ ManuallyDrop::new(i).0.get()
+ } else {
+ ptr::null_mut()
+ };
+
+ // SAFETY: Both inode and dentry are known to be valid.
+ let ptr = from_err_ptr(unsafe { bindings::d_splice_alias(inode_ptr, self.0 .0.get()) })?;
+
+ // SAFETY: The C API guarantees that if a dentry is returned, the refcount has been
+ // incremented.
+ Ok(ptr::NonNull::new(ptr).map(|v| unsafe { ARef::from_raw(v.cast::<DEntry<T>>()) }))
+ }
+
+ /// Returns the name of the dentry.
+ ///
+ /// Being unhashed guarantees that the name won't change.
+ pub fn name(&self) -> &[u8] {
+ // SAFETY: The name is immutable, so it is ok to read it.
+ let name = unsafe { &*ptr::addr_of!((*self.0 .0.get()).d_name) };
+
+ // This ensures that a `u32` is representable in `usize`. If it isn't, we'll get a build
+ // break.
+ const _: usize = 0xffffffff;
+
+ // SAFETY: The union is just allow an easy way to get the `hash` and `len` at once. `len`
+ // is always valid.
+ let len = unsafe { name.__bindgen_anon_1.__bindgen_anon_1.len } as usize;
+
+ // SAFETY: The name is immutable, so it is ok to read it.
+ unsafe { core::slice::from_raw_parts(name.name, len) }
+ }
+}
+
+impl<T: FileSystem + ?Sized> Deref for Unhashed<'_, T> {
+ type Target = DEntry<T>;
+
+ fn deref(&self) -> &Self::Target {
+ self.0
+ }
+}
+
+/// A dentry that is meant to be used as the root of a file system.
+pub struct Root<T: FileSystem + ?Sized>(ARef<DEntry<T>>);
+
+impl<T: FileSystem + ?Sized> Root<T> {
+ /// Creates a root dentry.
+ pub fn try_new(inode: ARef<INode<T>>) -> Result<Root<T>> {
+ // SAFETY: `d_make_root` requires that `inode` be valid and referenced, which is the
+ // case for this call.
+ //
+ // It takes over the inode, even on failure, so we don't need to clean it up.
+ let dentry_ptr = unsafe { bindings::d_make_root(ManuallyDrop::new(inode).0.get()) };
+ let dentry = ptr::NonNull::new(dentry_ptr).ok_or(ENOMEM)?;
+
+ // SAFETY: `dentry` is valid and referenced. It reference ownership is transferred to
+ // `ARef`.
+ Ok(Root(unsafe { ARef::from_raw(dentry.cast::<DEntry<T>>()) }))
+ }
+}
+
+impl<T: FileSystem + ?Sized> Deref for Root<T> {
+ type Target = DEntry<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
--
2.34.1