Skip to content
This repository was archived by the owner on Aug 18, 2025. It is now read-only.

Work on ramfs 2023

fidoriel edited this page Jul 23, 2023 · 1 revision

Work on ramfs 2023

Our base filesystem-abstractions

We use the abstractions introduced by the branch of @ariel-miculas rust-next-fs mentioned in issue #1004.

improved vtable definition used for filesystems

vtables (virtual method/function table) are structs of function pointers and play a role in class inheritance. As there is no class construct in C, they play a big role in the linux-source.

vtables can be created using rust traits by the Attribute Macro macros::vtable. It adds a HAS_* constant for each method in the trait, which indicates if the implementor has overriden this method.

An example can be found in the Rust for Linux Docs.

This allows to build a c-like vtable-struct in rust behind the scenes: Another private struct holds the c-entry-points of all the functions, which may be in the vtable. They are usually called *_callback and reference the [vtable]-trait after the arguments are converted from c to rust. The result of the referenced implementation is then usually converted back to c and returned.

This allows to build the member const VTABLE, the actual c vtable-struct. For optional implementations, it usually sets Some(*_callback) if the trait has HAS_* set, None (null-Pointer) otherwise. For required functions, Some(*_callback) is defined directly.

So, the rust trait can implement pure rust functions, working with the abstractions introduced by the vtable-struct so that (hopefully) no unsafe code is required from this point on.

An example is the file_operations c-struct. One can look at the OperationsVtable in rust/kernel/file.rs. It has implemented some unsafe extern "C" functions, such as open_callback and read_callback. Note that open_callback calls T::open after some conversions; T is of type Operation, which is the vtable-trait from above! So, T::open must be the rust implementation of open of an rust file system.

Beside the c-function callbacks, is has the VTABLE member of type file_operationsas promised. In it, its field open is defined as Some(Self::open_callback), as open is a required field, whereas read is set to Some(Self::read_callback) if T::HAS_READ is set, i.e. T has an implementation for read.

pub(crate) struct OperationsVtable<A, T>(marker::PhantomData<A>, marker::PhantomData<T>);


impl<A: OpenAdapter<T::OpenData>, T: Operations> OperationsVtable<A, T> {
   ...
   unsafe extern "C" fn open_callback(
       inode: *mut bindings::inode,
       file: *mut bindings::file,
   ) -> core::ffi::c_int {
       from_kernel_result! {
           ...
           let arg = unsafe { A::convert(inode, file) };
           ...
           let fileref = unsafe { File::from_ptr(file) };
           ...
           let ptr = T::open(unsafe { &*arg }, fileref)?.into_foreign();
          ...
           Ok(0)
       }
   }

   unsafe extern "C" fn read_callback(
       file: *mut bindings::file,
       buf: *mut core::ffi::c_char,
       len: core::ffi::c_size_t,
       offset: *mut bindings::loff_t,
   ) -> core::ffi::c_ssize_t {
       from_kernel_result! {
           let mut data = unsafe { UserSlicePtr::new(buf as *mut core::ffi::c_void, len).writer() };
           ...
           let read = T::read(
               f,
               unsafe { File::from_ptr(file) },
               &mut data,
               unsafe { *offset }.try_into()?,
           )?;
           ...
           Ok(read as _)
       }
   }
   
   ...
   
    const VTABLE: bindings::file_operations = bindings::file_operations {
       open: Some(Self::open_callback),
       ...
       read: if T::HAS_READ {
           Some(Self::read_callback)
       } else {
           None
       },
       ...
   };

As mentioned above, vtables are a general concept used thoughout linux, and are not merely relevant in context of filesystems. This is the general way, Rust for Linux seem to handle Linux vtables.

Usage example

#[vtable]
impl file::Operations for YourFS {
    
    fn read(_context: &(), _file: &File) -> Result<Self::Data> {
        Ok(())
    }

Now the vtable macro knows what to register in the vtable. Pay attention with nested types.

Basic Setup

This is a very relevant example directly taken from the already documented source but hard to find.

Declares a kernel module that exposes a single file system. The type argument must be a type which implements the [Type] trait. Also accepts various forms of kernel metadata.

use kernel::prelude::*;
use kernel::{c_str, fs};

module_fs! {
     type: MyFs,
     name: b"my_fs_kernel_module",
     author: b"Rust for Linux Contributors",
     description: b"My very own file system kernel module!",
     license: b"GPL",
}

struct MyFs;

impl fs::Type for MyFs {
     const SUPER_TYPE: fs::Super = fs::Super::Independent;
     const NAME: &'static CStr = c_str!("example");
     const FLAGS: i32 = 0;

     fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBlock<Self>> {
         let sb = sb.init(
             (),
             &fs::SuperParams {
                 magic: 0x6578616d,
                 ..fs::SuperParams::DEFAULT
             },
         )?;
         let root_inode = sb.try_new_dcache_dir_inode(fs::INodeParams {
             mode: 0o755,
             ino: 1,
             value: (),
         })?;
         let root = sb.try_new_root_dentry(root_inode)?;
         let sb = sb.init_root(root)?;
        Ok(sb)
    }
}

Contact to the Community

The Rust for Linux Community can be found here. There is a filesystems chat about abstractions, which can come in handy. The 2023 updated RamFS branch is based on the abstractions by Cisco.

puzzlefs

At time of writing, Cisco is experimenting with a new Container FS written in rust. more info For this, the Cisco Team has created the PR mentioned above.

Clone this wiki locally