1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (C) 2025 Google LLC. 3 4 use crate::debugfs::file_ops::FileOps; 5 use crate::ffi::c_void; 6 use crate::str::CStr; 7 use crate::sync::Arc; 8 9 /// Owning handle to a DebugFS entry. 10 /// 11 /// # Invariants 12 /// 13 /// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`. 14 pub(crate) struct Entry { 15 entry: *mut bindings::dentry, 16 // If we were created with an owning parent, this is the keep-alive 17 _parent: Option<Arc<Entry>>, 18 } 19 20 // SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred 21 // between threads. 22 unsafe impl Send for Entry {} 23 24 // SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. 25 unsafe impl Sync for Entry {} 26 27 impl Entry { 28 pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self { 29 let parent_ptr = match &parent { 30 Some(entry) => entry.as_ptr(), 31 None => core::ptr::null_mut(), 32 }; 33 // SAFETY: The invariants of this function's arguments ensure the safety of this call. 34 // * `name` is a valid C string by the invariants of `&CStr`. 35 // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid 36 // `dentry` by our invariant. `debugfs_create_dir` handles `NULL` pointers correctly. 37 let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) }; 38 39 Entry { 40 entry, 41 _parent: parent, 42 } 43 } 44 45 /// # Safety 46 /// 47 /// * `data` must outlive the returned `Entry`. 48 pub(crate) unsafe fn dynamic_file<T>( 49 name: &CStr, 50 parent: Arc<Self>, 51 data: &T, 52 file_ops: &'static FileOps<T>, 53 ) -> Self { 54 // SAFETY: The invariants of this function's arguments ensure the safety of this call. 55 // * `name` is a valid C string by the invariants of `&CStr`. 56 // * `parent.as_ptr()` is a pointer to a valid `dentry` by invariant. 57 // * The caller guarantees that `data` will outlive the returned `Entry`. 58 // * The guarantees on `FileOps` assert the vtable will be compatible with the data we have 59 // provided. 60 let entry = unsafe { 61 bindings::debugfs_create_file_full( 62 name.as_char_ptr(), 63 file_ops.mode(), 64 parent.as_ptr(), 65 core::ptr::from_ref(data) as *mut c_void, 66 core::ptr::null(), 67 &**file_ops, 68 ) 69 }; 70 71 Entry { 72 entry, 73 _parent: Some(parent), 74 } 75 } 76 77 /// Constructs a placeholder DebugFS [`Entry`]. 78 pub(crate) fn empty() -> Self { 79 Self { 80 entry: core::ptr::null_mut(), 81 _parent: None, 82 } 83 } 84 85 /// Returns the pointer representation of the DebugFS directory. 86 /// 87 /// # Guarantees 88 /// 89 /// Due to the type invariant, the value returned from this function will always be an error 90 /// code, NULL, or a live DebugFS directory. If it is live, it will remain live at least as 91 /// long as this entry lives. 92 pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { 93 self.entry 94 } 95 } 96 97 impl Drop for Entry { 98 fn drop(&mut self) { 99 // SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries. 100 // `as_ptr` guarantees that the pointer is of this form. 101 unsafe { bindings::debugfs_remove(self.as_ptr()) } 102 } 103 } 104