Skip to content

Commit 6ee9a19

Browse files
committed
debugfs.rs: Rust API for debugfs
Add a Rust API to create directories and files in `debugfs`. Signed-off-by: Adam Bratschi-Kaye <[email protected]>
1 parent 459035a commit 6ee9a19

File tree

8 files changed

+404
-7
lines changed

8 files changed

+404
-7
lines changed

fs/debugfs/inode.c

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,35 +713,70 @@ static void __debugfs_file_removed(struct dentry *dentry)
713713
wait_for_completion(&fsd->active_users_drained);
714714
}
715715

716-
static void remove_one(struct dentry *victim)
716+
void debugfs_remove_one_with_callback(struct dentry *victim,
717+
void (*callback)(struct dentry *))
717718
{
719+
callback(victim);
718720
if (d_is_reg(victim))
719721
__debugfs_file_removed(victim);
720722
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
721723
}
724+
EXPORT_SYMBOL_GPL(debugfs_remove_one_with_callback);
725+
726+
static void empty_callback(struct dentry *dentry) {}
727+
728+
static void remove_one(struct dentry *victim)
729+
{
730+
debugfs_remove_one_with_callback(victim, empty_callback);
731+
}
722732

723733
/**
724-
* debugfs_remove - recursively removes a directory
734+
* debugfs_remove_with_callback - recursively removes a directory with an
735+
* additional callback to be run on each dentry. This is intended to be used
736+
* from Rust modules that need to pass in a Rust destructor to drop data in the
737+
* dentry's.
725738
* @dentry: a pointer to a the dentry of the directory to be removed. If this
726739
* parameter is NULL or an error value, nothing will be done.
740+
* @callback: a pointer to a callback which will be run on each dentry before
741+
* it is removed.
727742
*
728743
* This function recursively removes a directory tree in debugfs that
729744
* was previously created with a call to another debugfs function
730745
* (like debugfs_create_file() or variants thereof.)
731746
*
732-
* This function is required to be called in order for the file to be
733-
* removed, no automatic cleanup of files will happen when a module is
734-
* removed, you are responsible here.
747+
* This function (or debugfs_remove) is required to be called in order for the
748+
* file to be removed, no automatic cleanup of files will happen when a module
749+
* is removed, you are responsible here.
735750
*/
736-
void debugfs_remove(struct dentry *dentry)
751+
void debugfs_remove_with_callback(struct dentry *dentry,
752+
void (*callback)(struct dentry *))
737753
{
738754
if (IS_ERR_OR_NULL(dentry))
739755
return;
740756

741757
simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count);
742-
simple_recursive_removal(dentry, remove_one);
758+
simple_recursive_removal(dentry, callback);
743759
simple_release_fs(&debugfs_mount, &debugfs_mount_count);
744760
}
761+
EXPORT_SYMBOL_GPL(debugfs_remove_with_callback);
762+
763+
/**
764+
* debugfs_remove - recursively removes a directory
765+
* @dentry: a pointer to a the dentry of the directory to be removed. If this
766+
* parameter is NULL or an error value, nothing will be done.
767+
*
768+
* This function recursively removes a directory tree in debugfs that
769+
* was previously created with a call to another debugfs function
770+
* (like debugfs_create_file() or variants thereof.)
771+
*
772+
* This function is required to be called in order for the file to be
773+
* removed, no automatic cleanup of files will happen when a module is
774+
* removed, you are responsible here.
775+
*/
776+
void debugfs_remove(struct dentry *dentry)
777+
{
778+
debugfs_remove_with_callback(dentry, remove_one);
779+
}
745780
EXPORT_SYMBOL_GPL(debugfs_remove);
746781

747782
/**

include/linux/debugfs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ struct dentry *debugfs_create_automount(const char *name,
9090

9191
void debugfs_remove(struct dentry *dentry);
9292
#define debugfs_remove_recursive debugfs_remove
93+
void debugfs_remove_with_callback(struct dentry *dentry,
94+
void (*callback)(struct dentry *));
95+
void debugfs_remove_one_with_callback(struct dentry *victim,
96+
void (*callback)(struct dentry *));
9397

9498
const struct file_operations *debugfs_real_fops(const struct file *filp);
9599

rust/bindings/bindings_helper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <linux/amba/bus.h>
1111
#include <linux/cdev.h>
1212
#include <linux/clk.h>
13+
#include <linux/debugfs.h>
1314
#include <linux/errname.h>
1415
#include <linux/file.h>
1516
#include <linux/fs.h>

rust/kernel/debugfs.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
}

rust/kernel/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub mod chrdev;
4949
#[cfg(CONFIG_COMMON_CLK)]
5050
pub mod clk;
5151
pub mod cred;
52+
#[cfg(CONFIG_DEBUG_FS)]
53+
pub mod debugfs;
5254
pub mod delay;
5355
pub mod device;
5456
pub mod driver;

samples/rust/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,16 @@ config SAMPLE_RUST_SELFTESTS
163163

164164
If unsure, say N.
165165

166+
config SAMPLE_RUST_DEBUGFS
167+
tristate "Debug fs"
168+
depends on DEBUG_FS
169+
help
170+
This option builds the Rust debugfs sample.
171+
172+
To compile this as a module, choose M here:
173+
the module will be called rust_debugfs.
174+
175+
If unsure, say N.
176+
177+
166178
endif # SAMPLES_RUST

samples/rust/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ obj-$(CONFIG_SAMPLE_RUST_NETFILTER) += rust_netfilter.o
1515
obj-$(CONFIG_SAMPLE_RUST_ECHO_SERVER) += rust_echo_server.o
1616
obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o
1717
obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o
18+
obj-$(CONFIG_SAMPLE_RUST_DEBUGFS) += rust_debugfs.o
1819

1920
subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs

0 commit comments

Comments
 (0)