kernel/debugfs/entry.rs
1// SPDX-License-Identifier: GPL-2.0
2// Copyright (C) 2025 Google LLC.
3
4use crate::debugfs::file_ops::FileOps;
5use crate::ffi::c_void;
6use crate::str::CStr;
7use crate::sync::Arc;
8use core::marker::PhantomData;
9
10/// Owning handle to a DebugFS entry.
11///
12/// # Invariants
13///
14/// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`.
15pub(crate) struct Entry<'a> {
16 entry: *mut bindings::dentry,
17 // If we were created with an owning parent, this is the keep-alive
18 _parent: Option<Arc<Entry<'static>>>,
19 // If we were created with a non-owning parent, this prevents us from outliving it
20 _phantom: PhantomData<&'a ()>,
21}
22
23// SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred
24// between threads.
25unsafe impl Send for Entry<'_> {}
26
27// SAFETY: All the C functions we call on the `dentry` pointer are threadsafe.
28unsafe impl Sync for Entry<'_> {}
29
30impl Entry<'static> {
31 pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self {
32 let parent_ptr = match &parent {
33 Some(entry) => entry.as_ptr(),
34 None => core::ptr::null_mut(),
35 };
36 // SAFETY: The invariants of this function's arguments ensure the safety of this call.
37 // * `name` is a valid C string by the invariants of `&CStr`.
38 // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid
39 // `dentry` by our invariant. `debugfs_create_dir` handles `NULL` pointers correctly.
40 let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
41
42 Entry {
43 entry,
44 _parent: parent,
45 _phantom: PhantomData,
46 }
47 }
48
49 /// # Safety
50 ///
51 /// * `data` must outlive the returned `Entry`.
52 pub(crate) unsafe fn dynamic_file<T>(
53 name: &CStr,
54 parent: Arc<Self>,
55 data: &T,
56 file_ops: &'static FileOps<T>,
57 ) -> Self {
58 // SAFETY: The invariants of this function's arguments ensure the safety of this call.
59 // * `name` is a valid C string by the invariants of `&CStr`.
60 // * `parent.as_ptr()` is a pointer to a valid `dentry` by invariant.
61 // * The caller guarantees that `data` will outlive the returned `Entry`.
62 // * The guarantees on `FileOps` assert the vtable will be compatible with the data we have
63 // provided.
64 let entry = unsafe {
65 bindings::debugfs_create_file_full(
66 name.as_char_ptr(),
67 file_ops.mode(),
68 parent.as_ptr(),
69 core::ptr::from_ref(data) as *mut c_void,
70 core::ptr::null(),
71 &**file_ops,
72 )
73 };
74
75 Entry {
76 entry,
77 _parent: Some(parent),
78 _phantom: PhantomData,
79 }
80 }
81}
82
83impl<'a> Entry<'a> {
84 pub(crate) fn dir(name: &CStr, parent: Option<&'a Entry<'_>>) -> Self {
85 let parent_ptr = match &parent {
86 Some(entry) => entry.as_ptr(),
87 None => core::ptr::null_mut(),
88 };
89 // SAFETY: The invariants of this function's arguments ensure the safety of this call.
90 // * `name` is a valid C string by the invariants of `&CStr`.
91 // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid
92 // `dentry` (because `parent` is a valid reference to an `Entry`). The lifetime `'a`
93 // ensures that the parent outlives this entry.
94 let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };
95
96 Entry {
97 entry,
98 _parent: None,
99 _phantom: PhantomData,
100 }
101 }
102
103 pub(crate) fn file<T>(
104 name: &CStr,
105 parent: &'a Entry<'_>,
106 data: &'a T,
107 file_ops: &FileOps<T>,
108 ) -> Self {
109 // SAFETY: The invariants of this function's arguments ensure the safety of this call.
110 // * `name` is a valid C string by the invariants of `&CStr`.
111 // * `parent.as_ptr()` is a pointer to a valid `dentry` because we have `&'a Entry`.
112 // * `data` is a valid pointer to `T` for lifetime `'a`.
113 // * The returned `Entry` has lifetime `'a`, so it cannot outlive `parent` or `data`.
114 // * The caller guarantees that `vtable` is compatible with `data`.
115 // * The guarantees on `FileOps` assert the vtable will be compatible with the data we have
116 // provided.
117 let entry = unsafe {
118 bindings::debugfs_create_file_full(
119 name.as_char_ptr(),
120 file_ops.mode(),
121 parent.as_ptr(),
122 core::ptr::from_ref(data) as *mut c_void,
123 core::ptr::null(),
124 &**file_ops,
125 )
126 };
127
128 Entry {
129 entry,
130 _parent: None,
131 _phantom: PhantomData,
132 }
133 }
134}
135
136impl Entry<'_> {
137 /// Constructs a placeholder DebugFS [`Entry`].
138 pub(crate) fn empty() -> Self {
139 Self {
140 entry: core::ptr::null_mut(),
141 _parent: None,
142 _phantom: PhantomData,
143 }
144 }
145
146 /// Returns the pointer representation of the DebugFS directory.
147 ///
148 /// # Guarantees
149 ///
150 /// Due to the type invariant, the value returned from this function will always be an error
151 /// code, NULL, or a live DebugFS directory. If it is live, it will remain live at least as
152 /// long as this entry lives.
153 pub(crate) fn as_ptr(&self) -> *mut bindings::dentry {
154 self.entry
155 }
156}
157
158impl Drop for Entry<'_> {
159 fn drop(&mut self) {
160 // SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries.
161 // `as_ptr` guarantees that the pointer is of this form.
162 unsafe { bindings::debugfs_remove(self.as_ptr()) }
163 }
164}