diff mbox series

[RFC,05/19] rust: fs: introduce `INode<T>`

Message ID 20231018122518.128049-6-wedsonaf@gmail.com (mailing list archive)
State New, archived
Headers show
Series Rust abstractions for VFS | expand

Commit Message

Wedson Almeida Filho Oct. 18, 2023, 12:25 p.m. UTC
From: Wedson Almeida Filho <walmeida@microsoft.com>

Allow Rust file systems to handle typed and ref-counted inodes.

This is in preparation for creating new inodes (for example, to create
the root inode of a new superblock), which comes in the next patch in
the series.

Signed-off-by: Wedson Almeida Filho <walmeida@microsoft.com>
---
 rust/helpers.c    |  7 +++++++
 rust/kernel/fs.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 58 insertions(+), 2 deletions(-)

Comments

Alice Ryhl Oct. 28, 2023, 6 p.m. UTC | #1
On 10/18/23 14:25, Wedson Almeida Filho wrote:
> +    /// Returns the super-block that owns the inode.
> +    pub fn super_block(&self) -> &SuperBlock<T> {
> +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
> +        // shared reference (&self) to it.
> +        unsafe { &*(*self.0.get()).i_sb.cast() }
> +    }

This makes me a bit nervous. I had to look up whether this field was a 
pointer to a superblock, or just a superblock embedded directly in 
`struct inode`. It does look like it's correct as-is, but I'd feel more 
confident about it if it doesn't use a cast to completely ignore the 
type going in to the pointer cast.

Could you define a `from_raw` on `SuperBlock` and change this to:

     unsafe { &*SuperBlock::from_raw((*self.0.get()).i_sb) }

or perhaps add a type annotation like this:

     let i_sb: *mut super_block = unsafe { (*self.0.get()).i_sb };
     i_sb.cast()

Alice
Andreas Hindborg Jan. 3, 2024, 12:45 p.m. UTC | #2
Alice Ryhl <alice@ryhl.io> writes:

> On 10/18/23 14:25, Wedson Almeida Filho wrote:
>> +    /// Returns the super-block that owns the inode.
>> +    pub fn super_block(&self) -> &SuperBlock<T> {
>> +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
>> +        // shared reference (&self) to it.
>> +        unsafe { &*(*self.0.get()).i_sb.cast() }
>> +    }
>
> This makes me a bit nervous. I had to look up whether this field was a pointer
> to a superblock, or just a superblock embedded directly in `struct inode`. It
> does look like it's correct as-is, but I'd feel more confident about it if it
> doesn't use a cast to completely ignore the type going in to the pointer cast.
>
> Could you define a `from_raw` on `SuperBlock` and change this to:
>
>     unsafe { &*SuperBlock::from_raw((*self.0.get()).i_sb) }
>
> or perhaps add a type annotation like this:
>
>     let i_sb: *mut super_block = unsafe { (*self.0.get()).i_sb };
>     i_sb.cast()

I think it would also be nice to make the cast explicit:

  i_sb.cast::<SuperBlock<T>>()

otherwise the cast is no different than `as _` with all the caveats that
comes with.

BR Andreas
Andreas Hindborg Jan. 3, 2024, 12:54 p.m. UTC | #3
Wedson Almeida Filho <wedsonaf@gmail.com> writes:

> +/// The number of an inode.
> +pub type Ino = u64;

Would it be possible to use a descriptive name such as `INodeNumber`?

> +    /// Returns the super-block that owns the inode.
> +    pub fn super_block(&self) -> &SuperBlock<T> {
> +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
> +        // shared reference (&self) to it.
> +        unsafe { &*(*self.0.get()).i_sb.cast() }
> +    }

I think the safety comment should talk about the pointee rather than the
pointer? "The pointee of `i_sb` is immutable, and ..."

BR Andreas
Darrick J. Wong Jan. 4, 2024, 5:14 a.m. UTC | #4
On Wed, Oct 18, 2023 at 09:25:04AM -0300, Wedson Almeida Filho wrote:
> From: Wedson Almeida Filho <walmeida@microsoft.com>
> 
> Allow Rust file systems to handle typed and ref-counted inodes.
> 
> This is in preparation for creating new inodes (for example, to create
> the root inode of a new superblock), which comes in the next patch in
> the series.
> 
> Signed-off-by: Wedson Almeida Filho <walmeida@microsoft.com>
> ---
>  rust/helpers.c    |  7 +++++++
>  rust/kernel/fs.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 58 insertions(+), 2 deletions(-)
> 
> diff --git a/rust/helpers.c b/rust/helpers.c
> index 4c86fe4a7e05..fe45f8ddb31f 100644
> --- a/rust/helpers.c
> +++ b/rust/helpers.c
> @@ -25,6 +25,7 @@
>  #include <linux/build_bug.h>
>  #include <linux/err.h>
>  #include <linux/errname.h>
> +#include <linux/fs.h>
>  #include <linux/mutex.h>
>  #include <linux/refcount.h>
>  #include <linux/sched/signal.h>
> @@ -144,6 +145,12 @@ struct kunit *rust_helper_kunit_get_current_test(void)
>  }
>  EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test);
>  
> +off_t rust_helper_i_size_read(const struct inode *inode)

i_size_read returns a loff_t (aka __kernel_loff_t (aka long long)),
but this returns    an off_t (aka __kernel_off_t (aka long)).

Won't that cause truncation issues for files larger than 4GB on some
architectures?

> +{
> +	return i_size_read(inode);
> +}
> +EXPORT_SYMBOL_GPL(rust_helper_i_size_read);
> +
>  /*
>   * `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.rs b/rust/kernel/fs.rs
> index 31cf643aaded..30fa1f312f33 100644
> --- a/rust/kernel/fs.rs
> +++ b/rust/kernel/fs.rs
> @@ -7,9 +7,9 @@
>  //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h)
>  
>  use crate::error::{code::*, from_result, to_result, Error, Result};
> -use crate::types::Opaque;
> +use crate::types::{AlwaysRefCounted, Opaque};
>  use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule};
> -use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin};
> +use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
>  use macros::{pin_data, pinned_drop};
>  
>  /// Maximum size of an inode.
> @@ -94,6 +94,55 @@ fn drop(self: Pin<&mut Self>) {
>      }
>  }
>  
> +/// The number of an inode.
> +pub type Ino = u64;
> +
> +/// A node in the file system index (inode).
> +///
> +/// Wraps the kernel's `struct inode`.
> +///
> +/// # Invariants
> +///
> +/// Instances of this type are always ref-counted, that is, a call to `ihold` ensures that the
> +/// allocation remains valid at least until the matching call to `iput`.
> +#[repr(transparent)]
> +pub struct INode<T: FileSystem + ?Sized>(Opaque<bindings::inode>, PhantomData<T>);
> +
> +impl<T: FileSystem + ?Sized> INode<T> {
> +    /// Returns the number of the inode.
> +    pub fn ino(&self) -> Ino {
> +        // SAFETY: `i_ino` is immutable, and `self` is guaranteed to be valid by the existence of a
> +        // shared reference (&self) to it.
> +        unsafe { (*self.0.get()).i_ino }

Is "*self.0.get()" the means by which the Rust bindings get at the
actual C object?

(Forgive me, I've barely finished drying the primer coat on my rust-fu.)

> +    }
> +
> +    /// Returns the super-block that owns the inode.
> +    pub fn super_block(&self) -> &SuperBlock<T> {
> +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
> +        // shared reference (&self) to it.
> +        unsafe { &*(*self.0.get()).i_sb.cast() }
> +    }
> +
> +    /// Returns the size of the inode contents.
> +    pub fn size(&self) -> i64 {

I'm a little surprised I didn't see a

pub type loff_t = i64

followed by this function returning a loff_t.  Or maybe it would be
better to define it as:

struct loff_t(i64);

So that dopey fs developers like me cannot so easily assign a file
position (bytes) to a pgoff_t (page index) without either supplying an
actual conversion operator or seeing complaints from the compiler.

> +        // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
> +        unsafe { bindings::i_size_read(self.0.get()) }

It's confusing that rust_i_size_read returns a long but on the rust side,
INode::size returns an i64.

--D

> +    }
> +}
> +
> +// SAFETY: The type invariants guarantee that `INode` is always ref-counted.
> +unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
> +    fn inc_ref(&self) {
> +        // SAFETY: The existence of a shared reference means that the refcount is nonzero.
> +        unsafe { bindings::ihold(self.0.get()) };
> +    }
> +
> +    unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
> +        // SAFETY: The safety requirements guarantee that the refcount is nonzero.
> +        unsafe { bindings::iput(obj.cast().as_ptr()) }
> +    }
> +}
> +
>  /// A file system super block.
>  ///
>  /// Wraps the kernel's `struct super_block`.
> -- 
> 2.34.1
> 
>
Darrick J. Wong Jan. 4, 2024, 5:20 a.m. UTC | #5
On Wed, Jan 03, 2024 at 01:54:34PM +0100, Andreas Hindborg (Samsung) wrote:
> 
> Wedson Almeida Filho <wedsonaf@gmail.com> writes:
> 
> > +/// The number of an inode.
> > +pub type Ino = u64;
> 
> Would it be possible to use a descriptive name such as `INodeNumber`?

Filesystem programmers are lazy.  Originally the term was "index node
number", which was shortened to "inode number", shortened again to
"inumber", and finally "ino".  The Rust type name might as well mirror
the C type.

(There are probably greyerbeards than I who can quote even more arcane
points of Unix filesystem history.)

> > +    /// Returns the super-block that owns the inode.
> > +    pub fn super_block(&self) -> &SuperBlock<T> {
> > +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
> > +        // shared reference (&self) to it.
> > +        unsafe { &*(*self.0.get()).i_sb.cast() }
> > +    }
> 
> I think the safety comment should talk about the pointee rather than the
> pointer? "The pointee of `i_sb` is immutable, and ..."

inode::i_sb (the pointer) shouldn't be reassigned to a different
superblock during the lifetime of the inode; but the superblock object
itself (the pointee) is very much mutable.

--D

> BR Andreas
> 
>
Andreas Hindborg Jan. 4, 2024, 9:57 a.m. UTC | #6
"Darrick J. Wong" <djwong@kernel.org> writes:

[...]

>> > +    /// Returns the super-block that owns the inode.
>> > +    pub fn super_block(&self) -> &SuperBlock<T> {
>> > +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
>> > +        // shared reference (&self) to it.
>> > +        unsafe { &*(*self.0.get()).i_sb.cast() }
>> > +    }
>> 
>> I think the safety comment should talk about the pointee rather than the
>> pointer? "The pointee of `i_sb` is immutable, and ..."
>
> inode::i_sb (the pointer) shouldn't be reassigned to a different
> superblock during the lifetime of the inode; but the superblock object
> itself (the pointee) is very much mutable.

Ah, I thought the comment was about why it is sound to create
`&SuperBlock`, but it is referring to why it is sound to read `i_sb`.
Perhaps the comment should state this? Perhaps it is also worth
mentioning why it is OK to construct a shared reference from this
pointer?

Best regards
Andreas
Benno Lossin Jan. 10, 2024, 9:45 a.m. UTC | #7
On 03.01.24 13:54, Andreas Hindborg (Samsung) wrote:
> Wedson Almeida Filho <wedsonaf@gmail.com> writes:
>> +    /// Returns the super-block that owns the inode.
>> +    pub fn super_block(&self) -> &SuperBlock<T> {
>> +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
>> +        // shared reference (&self) to it.
>> +        unsafe { &*(*self.0.get()).i_sb.cast() }
>> +    }
> 
> I think the safety comment should talk about the pointee rather than the
> pointer? "The pointee of `i_sb` is immutable, and ..."

I think in this case it would be a very good idea to just split
the `unsafe` block into two parts. That would solve the issue
of "what does this safety comment justify?".
Wedson Almeida Filho Jan. 24, 2024, 6:17 p.m. UTC | #8
On Thu, 4 Jan 2024 at 02:14, Darrick J. Wong <djwong@kernel.org> wrote:
>
> On Wed, Oct 18, 2023 at 09:25:04AM -0300, Wedson Almeida Filho wrote:
> > From: Wedson Almeida Filho <walmeida@microsoft.com>
> >
> > Allow Rust file systems to handle typed and ref-counted inodes.
> >
> > This is in preparation for creating new inodes (for example, to create
> > the root inode of a new superblock), which comes in the next patch in
> > the series.
> >
> > Signed-off-by: Wedson Almeida Filho <walmeida@microsoft.com>
> > ---
> >  rust/helpers.c    |  7 +++++++
> >  rust/kernel/fs.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++--
> >  2 files changed, 58 insertions(+), 2 deletions(-)
> >
> > diff --git a/rust/helpers.c b/rust/helpers.c
> > index 4c86fe4a7e05..fe45f8ddb31f 100644
> > --- a/rust/helpers.c
> > +++ b/rust/helpers.c
> > @@ -25,6 +25,7 @@
> >  #include <linux/build_bug.h>
> >  #include <linux/err.h>
> >  #include <linux/errname.h>
> > +#include <linux/fs.h>
> >  #include <linux/mutex.h>
> >  #include <linux/refcount.h>
> >  #include <linux/sched/signal.h>
> > @@ -144,6 +145,12 @@ struct kunit *rust_helper_kunit_get_current_test(void)
> >  }
> >  EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test);
> >
> > +off_t rust_helper_i_size_read(const struct inode *inode)
>
> i_size_read returns a loff_t (aka __kernel_loff_t (aka long long)),
> but this returns    an off_t (aka __kernel_off_t (aka long)).
>
> Won't that cause truncation issues for files larger than 4GB on some
> architectures?

This is indeed a bug, thanks for catching it! Fixed in v2.

> > +{
> > +     return i_size_read(inode);
> > +}
> > +EXPORT_SYMBOL_GPL(rust_helper_i_size_read);
> > +
> >  /*
> >   * `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.rs b/rust/kernel/fs.rs
> > index 31cf643aaded..30fa1f312f33 100644
> > --- a/rust/kernel/fs.rs
> > +++ b/rust/kernel/fs.rs
> > @@ -7,9 +7,9 @@
> >  //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h)
> >
> >  use crate::error::{code::*, from_result, to_result, Error, Result};
> > -use crate::types::Opaque;
> > +use crate::types::{AlwaysRefCounted, Opaque};
> >  use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule};
> > -use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin};
> > +use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
> >  use macros::{pin_data, pinned_drop};
> >
> >  /// Maximum size of an inode.
> > @@ -94,6 +94,55 @@ fn drop(self: Pin<&mut Self>) {
> >      }
> >  }
> >
> > +/// The number of an inode.
> > +pub type Ino = u64;
> > +
> > +/// A node in the file system index (inode).
> > +///
> > +/// Wraps the kernel's `struct inode`.
> > +///
> > +/// # Invariants
> > +///
> > +/// Instances of this type are always ref-counted, that is, a call to `ihold` ensures that the
> > +/// allocation remains valid at least until the matching call to `iput`.
> > +#[repr(transparent)]
> > +pub struct INode<T: FileSystem + ?Sized>(Opaque<bindings::inode>, PhantomData<T>);
> > +
> > +impl<T: FileSystem + ?Sized> INode<T> {
> > +    /// Returns the number of the inode.
> > +    pub fn ino(&self) -> Ino {
> > +        // SAFETY: `i_ino` is immutable, and `self` is guaranteed to be valid by the existence of a
> > +        // shared reference (&self) to it.
> > +        unsafe { (*self.0.get()).i_ino }
>
> Is "*self.0.get()" the means by which the Rust bindings get at the
> actual C object?

It gets a pointer to the C object.

self's type is INode, which is a pair. `self.0` get the first element
of the pair, which has type `Opaque<bindings::inode>`. It has a method
called `get` that returns a pointer to the object it wraps.

> (Forgive me, I've barely finished drying the primer coat on my rust-fu.)
>
> > +    }
> > +
> > +    /// Returns the super-block that owns the inode.
> > +    pub fn super_block(&self) -> &SuperBlock<T> {
> > +        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
> > +        // shared reference (&self) to it.
> > +        unsafe { &*(*self.0.get()).i_sb.cast() }
> > +    }
> > +
> > +    /// Returns the size of the inode contents.
> > +    pub fn size(&self) -> i64 {
>
> I'm a little surprised I didn't see a
>
> pub type loff_t = i64
>
> followed by this function returning a loff_t.  Or maybe it would be
> better to define it as:

This was suggested by Matthew Wilcox as well, which I did for v2,
though I call it `Offset`.

> struct loff_t(i64);
>
> So that dopey fs developers like me cannot so easily assign a file
> position (bytes) to a pgoff_t (page index) without either supplying an
> actual conversion operator or seeing complaints from the compiler.

We may want to eventually do this, but for now I'm only doing the type alias.

The disadvantage of doing a new type is that we lose all arithmetic
operators as well, though we can redefine them by implementing the
appropriate traits.

Thanks,
-Wedson
diff mbox series

Patch

diff --git a/rust/helpers.c b/rust/helpers.c
index 4c86fe4a7e05..fe45f8ddb31f 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -25,6 +25,7 @@ 
 #include <linux/build_bug.h>
 #include <linux/err.h>
 #include <linux/errname.h>
+#include <linux/fs.h>
 #include <linux/mutex.h>
 #include <linux/refcount.h>
 #include <linux/sched/signal.h>
@@ -144,6 +145,12 @@  struct kunit *rust_helper_kunit_get_current_test(void)
 }
 EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test);
 
+off_t rust_helper_i_size_read(const struct inode *inode)
+{
+	return i_size_read(inode);
+}
+EXPORT_SYMBOL_GPL(rust_helper_i_size_read);
+
 /*
  * `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.rs b/rust/kernel/fs.rs
index 31cf643aaded..30fa1f312f33 100644
--- a/rust/kernel/fs.rs
+++ b/rust/kernel/fs.rs
@@ -7,9 +7,9 @@ 
 //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h)
 
 use crate::error::{code::*, from_result, to_result, Error, Result};
-use crate::types::Opaque;
+use crate::types::{AlwaysRefCounted, Opaque};
 use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule};
-use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin};
+use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
 use macros::{pin_data, pinned_drop};
 
 /// Maximum size of an inode.
@@ -94,6 +94,55 @@  fn drop(self: Pin<&mut Self>) {
     }
 }
 
+/// The number of an inode.
+pub type Ino = u64;
+
+/// A node in the file system index (inode).
+///
+/// Wraps the kernel's `struct inode`.
+///
+/// # Invariants
+///
+/// Instances of this type are always ref-counted, that is, a call to `ihold` ensures that the
+/// allocation remains valid at least until the matching call to `iput`.
+#[repr(transparent)]
+pub struct INode<T: FileSystem + ?Sized>(Opaque<bindings::inode>, PhantomData<T>);
+
+impl<T: FileSystem + ?Sized> INode<T> {
+    /// Returns the number of the inode.
+    pub fn ino(&self) -> Ino {
+        // SAFETY: `i_ino` is immutable, and `self` is guaranteed to be valid by the existence of a
+        // shared reference (&self) to it.
+        unsafe { (*self.0.get()).i_ino }
+    }
+
+    /// Returns the super-block that owns the inode.
+    pub fn super_block(&self) -> &SuperBlock<T> {
+        // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a
+        // shared reference (&self) to it.
+        unsafe { &*(*self.0.get()).i_sb.cast() }
+    }
+
+    /// Returns the size of the inode contents.
+    pub fn size(&self) -> i64 {
+        // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
+        unsafe { bindings::i_size_read(self.0.get()) }
+    }
+}
+
+// SAFETY: The type invariants guarantee that `INode` is always ref-counted.
+unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
+    fn inc_ref(&self) {
+        // SAFETY: The existence of a shared reference means that the refcount is nonzero.
+        unsafe { bindings::ihold(self.0.get()) };
+    }
+
+    unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
+        // SAFETY: The safety requirements guarantee that the refcount is nonzero.
+        unsafe { bindings::iput(obj.cast().as_ptr()) }
+    }
+}
+
 /// A file system super block.
 ///
 /// Wraps the kernel's `struct super_block`.