|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | + |
| 3 | +//! Rust implementation of `debugfs`. |
| 4 | +//! |
| 5 | +//! This module allows Rust kernel modules to create directories and files in |
| 6 | +//! `/debugfs`. |
| 7 | +//! |
| 8 | +//! C header: [`include/linux/debugfs.h`](../../../include/linux/debugfs.h) |
| 9 | +//! |
| 10 | +//! Reference: <https://www.kernel.org/doc/html/latest/filesystems/debugfs.html> |
| 11 | +
|
| 12 | +use alloc::boxed::Box; |
| 13 | +use core::{ |
| 14 | + any::Any, |
| 15 | + marker::{PhantomData, Sync}, |
| 16 | + ptr, |
| 17 | +}; |
| 18 | + |
| 19 | +use crate::{ |
| 20 | + bindings::{self, debugfs_remove_with_callback}, |
| 21 | + error, |
| 22 | + file::{OpenAdapter, Operations, OperationsVtable}, |
| 23 | + str::CStr, |
| 24 | + types::PointerWrapper, |
| 25 | + Result, |
| 26 | +}; |
| 27 | + |
| 28 | +/// An `dentry` for a directory in debugfs. |
| 29 | +pub struct DebugFsDirectory { |
| 30 | + dentry: *mut bindings::dentry, |
| 31 | + has_parent: bool, |
| 32 | +} |
| 33 | + |
| 34 | +// SAFETY: There are no public functions that take a shared [`DebugFsDirectory`] |
| 35 | +// reference and all its fields are private so a thread can't actually do |
| 36 | +// anything with a `&DebugFsDirectory`. This makes it is safe to share across |
| 37 | +// threads. |
| 38 | +unsafe impl Sync for DebugFsDirectory {} |
| 39 | + |
| 40 | +impl DebugFsDirectory { |
| 41 | + /// Create a new directory in `debugfs` under `parent`. If `parent` is |
| 42 | + /// `None`, it will be created at the `debugfs` root and removed on drop. If |
| 43 | + /// a `parent` is given then the `parent` is responsible for removing the |
| 44 | + /// directory. |
| 45 | + pub fn create(name: &CStr, parent: Option<&mut DebugFsDirectory>) -> Result<Self> { |
| 46 | + let name = name.as_char_ptr(); |
| 47 | + let has_parent = parent.is_some(); |
| 48 | + let parent_ptr = parent.map(|p| p.dentry).unwrap_or_else(ptr::null_mut); |
| 49 | + // SAFETY: Calling a C function. `name` is a valid null-terminated |
| 50 | + // string because it came from a [`CStr`] and `parent` is either null or |
| 51 | + // valid because it came from a [`DebugFsDirectory`]. |
| 52 | + let dentry = |
| 53 | + error::from_kernel_err_ptr(unsafe { bindings::debugfs_create_dir(name, parent_ptr) })?; |
| 54 | + Ok(DebugFsDirectory { dentry, has_parent }) |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +impl Drop for DebugFsDirectory { |
| 59 | + fn drop(&mut self) { |
| 60 | + // If this entry has a parent, we don't need to worry about removal |
| 61 | + // because the parent will remove its children when dropped. Otherwise |
| 62 | + // we need to clean up. |
| 63 | + if !self.has_parent { |
| 64 | + // SAFETY: Calling a C function. `dentry` must have been created by |
| 65 | + // a call to `DebugFsDirectory::create` which always returns a |
| 66 | + // valid `dentry`. There is no parent, so the |
| 67 | + // `dentry` couldn't have been removed and must still be valid. |
| 68 | + // |
| 69 | + // This `dentry` and every `dentry` in it was created with either |
| 70 | + // `DebugFsDirectory::create` or `DebugFsFile::create`. Both |
| 71 | + // functions guarantee that the created `dentry` has a valide |
| 72 | + // `inode` and the `inode`'s `i_private` field will be either null |
| 73 | + // or come from calling `PointerWrapper::into_pointer` on a |
| 74 | + // `Box<Box<dyn Any>>`. They both only create dentry's in debugfs. |
| 75 | + // This makes it safe to call `remove_rust_dentry` on each `dentry` |
| 76 | + // in `self.dentry`. |
| 77 | + unsafe { debugfs_remove_with_callback(self.dentry, Some(remove_rust_dentry)) }; |
| 78 | + } |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +/// A `dentry` for a file in debugfs with a `T` stored in `i_private`. |
| 83 | +pub struct DebugFsFile<T> { |
| 84 | + dentry: Option<*mut bindings::dentry>, |
| 85 | + _t: PhantomData<T>, |
| 86 | +} |
| 87 | + |
| 88 | +// SAFETY: There are no public methods available on [`DebugFsFile`] so a thread |
| 89 | +// can't actually do anything with a `&DebugFsFile`. This makes it is safe to |
| 90 | +// share across threads. |
| 91 | +unsafe impl<T> Sync for DebugFsFile<T> {} |
| 92 | + |
| 93 | +impl<T: Any> DebugFsFile<T> { |
| 94 | + /// Create a file in the `debugfs` directory under `parent`. If `parent` is |
| 95 | + /// `None` then the file will be created at the root of the `debugfs` |
| 96 | + /// directory and it will be removed on drop. If a `parent` is provided then |
| 97 | + /// the `parent` is responsible for removing this file. |
| 98 | + /// |
| 99 | + /// # Safety |
| 100 | + /// |
| 101 | + /// `fops` must be valid when opening an `inode` with a `Box<Box<dyn |
| 102 | + /// Any>>::into_pointer` that can be downcast to `T` stored in `i_private`. |
| 103 | + unsafe fn create( |
| 104 | + name: &CStr, |
| 105 | + parent: Option<&mut DebugFsDirectory>, |
| 106 | + data: T, |
| 107 | + fops: &'static bindings::file_operations, |
| 108 | + ) -> Result<DebugFsFile<T>> { |
| 109 | + let has_parent = parent.is_some(); |
| 110 | + let name = name.as_char_ptr(); |
| 111 | + let boxed1: Box<dyn Any> = Box::try_new(data)?; |
| 112 | + let boxed2 = Box::try_new(boxed1)?; |
| 113 | + let data = PointerWrapper::into_pointer(boxed2) as *mut _; |
| 114 | + let parent = parent.map(|p| p.dentry).unwrap_or_else(ptr::null_mut); |
| 115 | + // SAFETY: Calling a C function. `name` will be a valid null-terminated |
| 116 | + // string because it came from a [`CStr`]. The caller guarantees that |
| 117 | + // `fops` is valid for an inode with a `Box<Box<dyn Any>>::into_pointer` |
| 118 | + // that can be downcast to `T` stored in `i_private`. |
| 119 | + let dentry_ptr = error::from_kernel_err_ptr(unsafe { |
| 120 | + bindings::debugfs_create_file(name, 0, parent, data, fops) |
| 121 | + }); |
| 122 | + match dentry_ptr { |
| 123 | + Err(err) => { |
| 124 | + // SAFETY: `data` was created by calling |
| 125 | + // `PointerWrapper::into_pointer` on a `Box<Box<dyn Any>>` just |
| 126 | + // above. |
| 127 | + let _: Box<Box<dyn Any>> = unsafe { PointerWrapper::from_pointer(data) }; |
| 128 | + Err(err) |
| 129 | + } |
| 130 | + Ok(dentry) => Ok(DebugFsFile { |
| 131 | + dentry: if has_parent { None } else { Some(dentry) }, |
| 132 | + _t: PhantomData, |
| 133 | + }), |
| 134 | + } |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +impl<T> Drop for DebugFsFile<T> { |
| 139 | + fn drop(&mut self) { |
| 140 | + // If there is no dentry then this file has a parent `DebugFsDirectory` |
| 141 | + // which is responsible for removal. |
| 142 | + if let Some(dentry) = self.dentry { |
| 143 | + // SAFETY: Calling a C function. `dentry` must have been created by |
| 144 | + // a call to [`DebugFsFile::create`] which always returns a valid |
| 145 | + // `dentry`. Since there is no parent that can remove the `dentry` |
| 146 | + // it must still exist. |
| 147 | + // |
| 148 | + // A `DebugFsFile` is created by calling `debugfs_create_file` |
| 149 | + // (which always creates a valid `dentry` in debugfs with a valid |
| 150 | + // `d_inode` field) and passing in a pointer coming from a |
| 151 | + // `Box<Box<dyn Any>>` which gets put in the `inode`'s `i_private` |
| 152 | + // field. This is sufficient for `remove_rust_dentry` to be safely |
| 153 | + // called on the `dentry`. |
| 154 | + unsafe { debugfs_remove_with_callback(dentry, Some(remove_rust_dentry)) }; |
| 155 | + } |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +/// # Safety |
| 160 | +/// `dentry` must be a valid `bindings::dentry` with a valid `d_inode` field. In |
| 161 | +/// addition, the `i_private` field of `d_inode` must be either a null pointer |
| 162 | +/// or one created by calling `PointerWrapper::into_pointer` on a `Box<Box<dyn |
| 163 | +/// Any>>`. |
| 164 | +unsafe extern "C" fn drop_i_private(dentry: *mut bindings::dentry) { |
| 165 | + // SAFETY: Caller guarantees that `dentry->d_inode` can be dereferenced. |
| 166 | + let i_private = unsafe { (*(*dentry).d_inode).i_private }; |
| 167 | + // SAFETY: Caller guarantees that `dentry->d_inode->i_private` is either |
| 168 | + // null, or generated by calling `PointerWrapper::into_pointer` on a |
| 169 | + // `Box<Box<dyn Any>>`. |
| 170 | + if !i_private.is_null() { |
| 171 | + let _: Box<Box<dyn Any>> = unsafe { PointerWrapper::from_pointer(i_private) }; |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +/// # Safety |
| 176 | +/// `dentry` must be in debugfs and satify all the requirements for |
| 177 | +/// `drop_i_private` to be safely called on it. |
| 178 | +unsafe extern "C" fn remove_rust_dentry(dentry: *mut bindings::dentry) { |
| 179 | + // SAFETY: The caller is responsible for ensuring that `drop_i_private` can |
| 180 | + // be called on `dentry` and that the dentry is in debugfs. |
| 181 | + unsafe { bindings::debugfs_remove_one_with_callback(dentry, Some(drop_i_private)) } |
| 182 | +} |
| 183 | + |
| 184 | +impl<T: Any + Sync> OpenAdapter<T> for DebugFsFile<T> { |
| 185 | + unsafe fn convert(inode: *mut bindings::inode, _file: *mut bindings::file) -> *const T { |
| 186 | + let data: &dyn Any = |
| 187 | + unsafe { <Box<Box<dyn Any>> as PointerWrapper>::borrow((*inode).i_private) }.as_ref(); |
| 188 | + let open_data: &T = match data.downcast_ref() { |
| 189 | + Some(data) => data, |
| 190 | + None => panic!(), |
| 191 | + }; |
| 192 | + open_data |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +/// Create a file in `debugfs` under `parent`. If `parent` is `None` then the |
| 197 | +/// folder will be created at the top level of `debugfs`. |
| 198 | +pub fn debugfs_create<T: Operations>( |
| 199 | + name: &CStr, |
| 200 | + parent: Option<&mut DebugFsDirectory>, |
| 201 | + data: T::OpenData, |
| 202 | +) -> Result<DebugFsFile<T::OpenData>> |
| 203 | +where |
| 204 | + T::OpenData: 'static, |
| 205 | +{ |
| 206 | + // SAFETY: The `OpenAdapter` implementation for `DebugFsFile<T::OpenData>` |
| 207 | + // expects a the opened `inode` to have a `Box<Box<dyn Any>>` in `i_private` |
| 208 | + // that can be downcast to `T::OpenData`. `data` has type `T::OpenData` so |
| 209 | + // this satisfies the safety requirements on `DebugFsFile::create`. |
| 210 | + unsafe { |
| 211 | + DebugFsFile::create( |
| 212 | + name, |
| 213 | + parent, |
| 214 | + data, |
| 215 | + OperationsVtable::<DebugFsFile<T::OpenData>, T>::build(), |
| 216 | + ) |
| 217 | + } |
| 218 | +} |
0 commit comments