Skip to content

Commit 6e69647

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 5b22a0f commit 6e69647

File tree

8 files changed

+399
-7
lines changed

8 files changed

+399
-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: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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. The directory will be
43+
/// recursively removed on drop.
44+
pub fn create(name: &CStr, parent: Option<&mut DebugFsDirectory>) -> Result<Self> {
45+
let name = name.as_char_ptr();
46+
let has_parent = parent.is_some();
47+
let parent_ptr = parent.map(|p| p.dentry).unwrap_or_else(ptr::null_mut);
48+
// SAFETY: Calling a C function. `name` is a valid null-terminated
49+
// string because it came from a [`CStr`] and `parent` is either null or
50+
// valid because it came from a [`DebugFsDirectory`].
51+
let dentry =
52+
error::from_kernel_err_ptr(unsafe { bindings::debugfs_create_dir(name, parent_ptr) })?;
53+
Ok(DebugFsDirectory { dentry, has_parent })
54+
}
55+
}
56+
57+
impl Drop for DebugFsDirectory {
58+
fn drop(&mut self) {
59+
// If this entry has a parent, we don't need to worry about removal
60+
// because the parent will remove its children when dropped. Otherwise
61+
// we need to clean up.
62+
if !self.has_parent {
63+
// SAFETY: Calling a C function. `dentry` must have been created by
64+
// a call to `DebugFsDirectory::create` which always returns a
65+
// valid `dentry`. There is no parent, so the
66+
// `dentry` couldn't have been removed and must still be valid.
67+
//
68+
// This `dentry` and every `dentry` in it was created with either
69+
// `DebugFsDirectory::create` or `DebugFsFile::create`. Both
70+
// functions guarantee that the created `dentry` has a valide
71+
// `inode` and the `inode`'s `i_private` field will be either null
72+
// or come from calling `PointerWrapper::into_pointer` on a
73+
// `Box<Box<dyn Any>>`. They both only create dentry's in debugfs.
74+
// This makes it safe to call `remove_rust_dentry` on each `dentry`
75+
// in `self.dentry`.
76+
unsafe { debugfs_remove_with_callback(self.dentry, Some(remove_rust_dentry)) };
77+
}
78+
}
79+
}
80+
81+
/// A `dentry` for a file in debugfs with a `T` stored in `i_private`.
82+
pub struct DebugFsFile<T> {
83+
dentry: Option<*mut bindings::dentry>,
84+
_t: PhantomData<T>,
85+
}
86+
87+
// SAFETY: There are no public methods available on [`DebugFsFile`] so a thread
88+
// can't actually do anything with a `&DebugFsFile`. This makes it is safe to
89+
// share across threads.
90+
unsafe impl<T> Sync for DebugFsFile<T> {}
91+
92+
impl<T: Any> DebugFsFile<T> {
93+
/// Create a file in the `debugfs` directory under `parent`. If `parent` is
94+
/// `None` then the file will be created at the root of the `debugfs`
95+
/// directory.
96+
///
97+
/// # Safety
98+
///
99+
/// `fops` must be valid when opening an `inode` with a `Box<Box<dyn
100+
/// Any>>::into_pointer` that can be downcast to `T` stored in `i_private`.
101+
#[allow(dead_code)] // Remove when a caller is implemented.
102+
pub(crate) unsafe fn create(
103+
name: &CStr,
104+
parent: Option<&mut DebugFsDirectory>,
105+
data: T,
106+
fops: &'static bindings::file_operations,
107+
) -> Result<DebugFsFile<T>> {
108+
let has_parent = parent.is_some();
109+
let name = name.as_char_ptr();
110+
let boxed1: Box<dyn Any> = Box::try_new(data)?;
111+
let boxed2 = Box::try_new(boxed1)?;
112+
let data = PointerWrapper::into_pointer(boxed2) as *mut _;
113+
let parent = parent.map(|p| p.dentry).unwrap_or_else(ptr::null_mut);
114+
// SAFETY: Calling a C function. `name` will be a valid null-terminated
115+
// string because it came from a [`CStr`]. The caller guarantees that
116+
// `fops` is valid for an inode with a `Box<Box<dyn Any>>::into_pointer`
117+
// that can be downcast to `T` stored in `i_private`.
118+
let dentry_ptr = error::from_kernel_err_ptr(unsafe {
119+
bindings::debugfs_create_file(name, 0, parent, data, fops)
120+
});
121+
match dentry_ptr {
122+
Err(err) => {
123+
// SAFETY: `data` was created by calling
124+
// `PointerWrapper::into_pointer` on a `Box<Box<dyn Any>>` just
125+
// above.
126+
let _: Box<Box<dyn Any>> = unsafe { PointerWrapper::from_pointer(data) };
127+
Err(err)
128+
}
129+
Ok(dentry) => Ok(DebugFsFile {
130+
dentry: if has_parent { None } else { Some(dentry) },
131+
_t: PhantomData,
132+
}),
133+
}
134+
}
135+
}
136+
137+
impl<T> Drop for DebugFsFile<T> {
138+
fn drop(&mut self) {
139+
// If there is no dentry then this file has a parent `DebugFsDirectory`
140+
// which is responsible for removal.
141+
if let Some(dentry) = self.dentry {
142+
// SAFETY: Calling a C function. `dentry` must have been created by
143+
// a call to [`DebugFsFile::create`] which always returns a valid
144+
// `dentry`. Since there is no parent that can remove the `dentry`
145+
// it must still exist.
146+
//
147+
// A `DebugFsFile` is created by calling `debugfs_create_file`
148+
// (which always creates a valid `dentry` in debugfs with a valid
149+
// `d_inode` field) and passing in a pointer coming from a
150+
// `Box<Box<dyn Any>>` which gets put in the `inode`'s `i_private`
151+
// field. This is sufficient for `remove_rust_dentry` to be safely
152+
// called on the `dentry`.
153+
unsafe { debugfs_remove_with_callback(dentry, Some(remove_rust_dentry)) };
154+
}
155+
}
156+
}
157+
158+
/// # Safety
159+
/// `dentry` must be a valid `bindings::dentry` with a valid `d_inode` field. In
160+
/// addition, the `i_private` field of `d_inode` must be either a null pointer
161+
/// or one created by calling `PointerWrapper::into_pointer` on a `Box<Box<dyn
162+
/// Any>>`.
163+
unsafe extern "C" fn drop_i_private(dentry: *mut bindings::dentry) {
164+
// SAFETY: Caller guarantees that `dentry->d_inode` can be dereferenced.
165+
let i_private = unsafe { (*(*dentry).d_inode).i_private };
166+
// SAFETY: Caller guarantees that `dentry->d_inode->i_private` is either
167+
// null, or generated by calling `PointerWrapper::into_pointer` on a
168+
// `Box<Box<dyn Any>>`.
169+
if !i_private.is_null() {
170+
let _: Box<Box<dyn Any>> = unsafe { PointerWrapper::from_pointer(i_private) };
171+
}
172+
}
173+
174+
/// # Safety
175+
/// `dentry` must be in debugfs and satify all the requirements for
176+
/// `drop_i_private` to be safely called on it.
177+
unsafe extern "C" fn remove_rust_dentry(dentry: *mut bindings::dentry) {
178+
// SAFETY: The caller is responsible for ensuring that `drop_i_private` can
179+
// be called on `dentry` and that the dentry is in debugfs.
180+
unsafe { bindings::debugfs_remove_one_with_callback(dentry, Some(drop_i_private)) }
181+
}
182+
183+
impl<T: Any + Sync> OpenAdapter<T> for DebugFsFile<T> {
184+
unsafe fn convert(inode: *mut bindings::inode, _file: *mut bindings::file) -> *const T {
185+
let data: &dyn Any =
186+
unsafe { <Box<Box<dyn Any>> as PointerWrapper>::borrow((*inode).i_private) }.as_ref();
187+
let open_data: &T = match data.downcast_ref() {
188+
Some(data) => data,
189+
None => panic!(),
190+
};
191+
open_data
192+
}
193+
}
194+
195+
/// TODOABK
196+
pub fn debugfs_create<T: Operations>(
197+
name: &CStr,
198+
parent: Option<&mut DebugFsDirectory>,
199+
data: T::OpenData,
200+
) -> Result<DebugFsFile<T::OpenData>>
201+
where
202+
T::OpenData: 'static,
203+
{
204+
// TODOABK
205+
unsafe {
206+
DebugFsFile::create(
207+
name,
208+
parent,
209+
data,
210+
OperationsVtable::<DebugFsFile<T::OpenData>, T>::build(),
211+
)
212+
}
213+
}

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
@@ -162,4 +162,16 @@ config SAMPLE_RUST_SELFTESTS
162162

163163
If unsure, say N.
164164

165+
config SAMPLE_RUST_DEBUGFS
166+
tristate "Debug fs"
167+
depends on DEBUG_FS
168+
help
169+
This option builds the Rust debugfs sample.
170+
171+
To compile this as a module, choose M here:
172+
the module will be called rust_debugfs.
173+
174+
If unsure, say N.
175+
176+
165177
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)