1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (C) 2025 Google LLC. 3 4 use crate::{ 5 debugfs::file_ops::FileOps, 6 prelude::*, 7 str::{ 8 CStr, 9 CStrExt as _, // 10 }, 11 sync::Arc, 12 }; 13 14 use 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`. 21 pub(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. 31 unsafe impl Send for Entry<'_> {} 32 33 // SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. 34 unsafe impl Sync for Entry<'_> {} 35 36 impl 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 89 impl<'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 142 impl 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 164 impl 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 } 171