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}