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