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 use 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`. 15 pub(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. 25 unsafe impl Send for Entry<'_> {} 26 27 // SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. 28 unsafe impl Sync for Entry<'_> {} 29 30 impl 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 83 impl<'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 136 impl 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 158 impl 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 } 165