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}