| // SPDX-License-Identifier: GPL-2.0 | |
| // Copyright (C) 2025 Google LLC. | |
| //! DebugFS Abstraction | |
| //! | |
| //! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h) | |
| // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful. | |
| #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))] | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| use crate::sync::Arc; | |
| use crate::{ | |
| fmt, | |
| prelude::*, | |
| str::CStr, | |
| uaccess::UserSliceReader, // | |
| }; | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| use core::mem::ManuallyDrop; | |
| use core::{ | |
| marker::{ | |
| PhantomData, | |
| PhantomPinned, // | |
| }, | |
| ops::Deref, | |
| }; | |
| mod traits; | |
| pub use traits::{ | |
| BinaryReader, | |
| BinaryReaderMut, | |
| BinaryWriter, | |
| Reader, | |
| Writer, // | |
| }; | |
| mod callback_adapters; | |
| use callback_adapters::{ | |
| FormatAdapter, | |
| NoWriter, | |
| WritableAdapter, // | |
| }; | |
| mod file_ops; | |
| use file_ops::{ | |
| BinaryReadFile, | |
| BinaryReadWriteFile, | |
| BinaryWriteFile, | |
| FileOps, | |
| ReadFile, | |
| ReadWriteFile, | |
| WriteFile, // | |
| }; | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| mod entry; | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| use entry::Entry; | |
| /// Owning handle to a DebugFS directory. | |
| /// | |
| /// The directory in the filesystem represented by [`Dir`] will be removed when handle has been | |
| /// dropped *and* all children have been removed. | |
| // If we have a parent, we hold a reference to it in the `Entry`. This prevents the `dentry` | |
| // we point to from being cleaned up if our parent `Dir`/`Entry` is dropped before us. | |
| // | |
| // The `None` option indicates that the `Arc` could not be allocated, so our children would not be | |
| // able to refer to us. In this case, we need to silently fail. All future child directories/files | |
| // will silently fail as well. | |
| #[derive(Clone)] | |
| pub struct Dir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<Entry<'static>>>); | |
| impl Dir { | |
| /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root. | |
| fn create(name: &CStr, parent: Option<&Dir>) -> Self { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| { | |
| let parent_entry = match parent { | |
| // If the parent couldn't be allocated, just early-return | |
| Some(Dir(None)) => return Self(None), | |
| Some(Dir(Some(entry))) => Some(entry.clone()), | |
| None => None, | |
| }; | |
| Self( | |
| // If Arc creation fails, the `Entry` will be dropped, so the directory will be | |
| // cleaned up. | |
| Arc::new(Entry::dynamic_dir(name, parent_entry), GFP_KERNEL).ok(), | |
| ) | |
| } | |
| #[cfg(not(CONFIG_DEBUG_FS))] | |
| Self() | |
| } | |
| /// Creates a DebugFS file which will own the data produced by the initializer provided in | |
| /// `data`. | |
| fn create_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| file_ops: &'static FileOps<T>, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Sync + 'static, | |
| { | |
| let scope = Scope::<T>::new(data, move |data| { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| if let Some(parent) = &self.0 { | |
| // SAFETY: Because data derives from a scope, and our entry will be dropped before | |
| // the data is dropped, it is guaranteed to outlive the entry we return. | |
| unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) } | |
| } else { | |
| Entry::empty() | |
| } | |
| }); | |
| try_pin_init! { | |
| File { | |
| scope <- scope | |
| } ? E | |
| } | |
| } | |
| /// Create a new directory in DebugFS at the root. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ``` | |
| /// # use kernel::debugfs::Dir; | |
| /// let debugfs = Dir::new(c"parent"); | |
| /// ``` | |
| pub fn new(name: &CStr) -> Self { | |
| Dir::create(name, None) | |
| } | |
| /// Creates a subdirectory within this directory. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ``` | |
| /// # use kernel::debugfs::Dir; | |
| /// let parent = Dir::new(c"parent"); | |
| /// let child = parent.subdir(c"child"); | |
| /// ``` | |
| pub fn subdir(&self, name: &CStr) -> Self { | |
| Dir::create(name, Some(self)) | |
| } | |
| /// Creates a read-only file in this directory. | |
| /// | |
| /// The file's contents are produced by invoking [`Writer::write`] on the value initialized by | |
| /// `data`. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ``` | |
| /// # use kernel::debugfs::Dir; | |
| /// # use kernel::prelude::*; | |
| /// # let dir = Dir::new(c"my_debugfs_dir"); | |
| /// let file = KBox::pin_init(dir.read_only_file(c"foo", 200), GFP_KERNEL)?; | |
| /// // "my_debugfs_dir/foo" now contains the number 200. | |
| /// // The file is removed when `file` is dropped. | |
| /// # Ok::<(), Error>(()) | |
| /// ``` | |
| pub fn read_only_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Writer + Send + Sync + 'static, | |
| { | |
| let file_ops = &<T as ReadFile<_>>::FILE_OPS; | |
| self.create_file(name, data, file_ops) | |
| } | |
| /// Creates a read-only binary file in this directory. | |
| /// | |
| /// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`] on the value | |
| /// initialized by `data`. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ``` | |
| /// # use kernel::debugfs::Dir; | |
| /// # use kernel::prelude::*; | |
| /// # let dir = Dir::new(c"my_debugfs_dir"); | |
| /// let file = KBox::pin_init(dir.read_binary_file(c"foo", [0x1, 0x2]), GFP_KERNEL)?; | |
| /// # Ok::<(), Error>(()) | |
| /// ``` | |
| pub fn read_binary_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: BinaryWriter + Send + Sync + 'static, | |
| { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a read-only file in this directory, with contents from a callback. | |
| /// | |
| /// `f` must be a function item or a non-capturing closure. | |
| /// This is statically asserted and not a safety requirement. | |
| /// | |
| /// # Examples | |
| /// | |
| /// ``` | |
| /// # use kernel::{ | |
| /// # debugfs::Dir, | |
| /// # prelude::*, | |
| /// # sync::atomic::{ | |
| /// # Atomic, | |
| /// # Relaxed, | |
| /// # }, | |
| /// # }; | |
| /// # let dir = Dir::new(c"foo"); | |
| /// let file = KBox::pin_init( | |
| /// dir.read_callback_file(c"bar", | |
| /// Atomic::<u32>::new(3), | |
| /// &|val, f| { | |
| /// let out = val.load(Relaxed); | |
| /// writeln!(f, "{out:#010x}") | |
| /// }), | |
| /// GFP_KERNEL)?; | |
| /// // Reading "foo/bar" will show "0x00000003". | |
| /// file.store(10, Relaxed); | |
| /// // Reading "foo/bar" will now show "0x0000000a". | |
| /// # Ok::<(), Error>(()) | |
| /// ``` | |
| pub fn read_callback_file<'a, T, E: 'a, F>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| _f: &'static F, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Send + Sync + 'static, | |
| F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, | |
| { | |
| let file_ops = <FormatAdapter<T, F>>::FILE_OPS.adapt(); | |
| self.create_file(name, data, file_ops) | |
| } | |
| /// Creates a read-write file in this directory. | |
| /// | |
| /// Reading the file uses the [`Writer`] implementation. | |
| /// Writing to the file uses the [`Reader`] implementation. | |
| pub fn read_write_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Writer + Reader + Send + Sync + 'static, | |
| { | |
| let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS; | |
| self.create_file(name, data, file_ops) | |
| } | |
| /// Creates a read-write binary file in this directory. | |
| /// | |
| /// Reading the file uses the [`BinaryWriter`] implementation. | |
| /// Writing to the file uses the [`BinaryReader`] implementation. | |
| pub fn read_write_binary_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: BinaryWriter + BinaryReader + Send + Sync + 'static, | |
| { | |
| let file_ops = &<T as BinaryReadWriteFile<_>>::FILE_OPS; | |
| self.create_file(name, data, file_ops) | |
| } | |
| /// Creates a read-write file in this directory, with logic from callbacks. | |
| /// | |
| /// Reading from the file is handled by `f`. Writing to the file is handled by `w`. | |
| /// | |
| /// `f` and `w` must be function items or non-capturing closures. | |
| /// This is statically asserted and not a safety requirement. | |
| pub fn read_write_callback_file<'a, T, E: 'a, F, W>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| _f: &'static F, | |
| _w: &'static W, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Send + Sync + 'static, | |
| F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, | |
| W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync, | |
| { | |
| let file_ops = | |
| <WritableAdapter<FormatAdapter<T, F>, W> as file_ops::ReadWriteFile<_>>::FILE_OPS | |
| .adapt() | |
| .adapt(); | |
| self.create_file(name, data, file_ops) | |
| } | |
| /// Creates a write-only file in this directory. | |
| /// | |
| /// The file owns its backing data. Writing to the file uses the [`Reader`] | |
| /// implementation. | |
| /// | |
| /// The file is removed when the returned [`File`] is dropped. | |
| pub fn write_only_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Reader + Send + Sync + 'static, | |
| { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a write-only binary file in this directory. | |
| /// | |
| /// The file owns its backing data. Writing to the file uses the [`BinaryReader`] | |
| /// implementation. | |
| /// | |
| /// The file is removed when the returned [`File`] is dropped. | |
| pub fn write_binary_file<'a, T, E: 'a>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: BinaryReader + Send + Sync + 'static, | |
| { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a write-only file in this directory, with write logic from a callback. | |
| /// | |
| /// `w` must be a function item or a non-capturing closure. | |
| /// This is statically asserted and not a safety requirement. | |
| pub fn write_callback_file<'a, T, E: 'a, W>( | |
| &'a self, | |
| name: &'a CStr, | |
| data: impl PinInit<T, E> + 'a, | |
| _w: &'static W, | |
| ) -> impl PinInit<File<T>, E> + 'a | |
| where | |
| T: Send + Sync + 'static, | |
| W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync, | |
| { | |
| let file_ops = <WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS | |
| .adapt() | |
| .adapt(); | |
| self.create_file(name, data, file_ops) | |
| } | |
| // While this function is safe, it is intentionally not public because it's a bit of a | |
| // footgun. | |
| // | |
| // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate | |
| // time, a `ScopedDir` with a `Dir` parent will never be deleted. | |
| fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| { | |
| let parent_entry = match &self.0 { | |
| None => return ScopedDir::empty(), | |
| Some(entry) => entry.clone(), | |
| }; | |
| ScopedDir { | |
| entry: ManuallyDrop::new(Entry::dynamic_dir(name, Some(parent_entry))), | |
| _phantom: PhantomData, | |
| } | |
| } | |
| #[cfg(not(CONFIG_DEBUG_FS))] | |
| ScopedDir::empty() | |
| } | |
| /// Creates a new scope, which is a directory associated with some data `T`. | |
| /// | |
| /// The created directory will be a subdirectory of `self`. The `init` closure is called to | |
| /// populate the directory with files and subdirectories. These files can reference the data | |
| /// stored in the scope. | |
| /// | |
| /// The entire directory tree created within the scope will be removed when the returned | |
| /// `Scope` handle is dropped. | |
| pub fn scope<'a, T: 'a, E: 'a, F>( | |
| &'a self, | |
| data: impl PinInit<T, E> + 'a, | |
| name: &'a CStr, | |
| init: F, | |
| ) -> impl PinInit<Scope<T>, E> + 'a | |
| where | |
| F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a, | |
| { | |
| Scope::new(data, |data| { | |
| let scoped = self.scoped_dir(name); | |
| init(data, &scoped); | |
| scoped.into_entry() | |
| }) | |
| } | |
| } | |
| #[pin_data] | |
| /// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry | |
| /// without moving. | |
| /// | |
| /// This is internally used to back [`File`], and used in the API to represent the attachment | |
| /// of a directory lifetime to a data structure which may be jointly accessed by a number of | |
| /// different files. | |
| /// | |
| /// When dropped, a `Scope` will remove all directories and files in the filesystem backed by the | |
| /// attached data structure prior to releasing the attached data. | |
| pub struct Scope<T> { | |
| // This order is load-bearing for drops - `_entry` must be dropped before `data`. | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| _entry: Entry<'static>, | |
| #[pin] | |
| data: T, | |
| // Even if `T` is `Unpin`, we still can't allow it to be moved. | |
| #[pin] | |
| _pin: PhantomPinned, | |
| } | |
| #[pin_data] | |
| /// Handle to a DebugFS file, owning its backing data. | |
| /// | |
| /// When dropped, the DebugFS file will be removed and the attached data will be dropped. | |
| pub struct File<T> { | |
| #[pin] | |
| scope: Scope<T>, | |
| } | |
| #[cfg(not(CONFIG_DEBUG_FS))] | |
| impl<'b, T: 'b> Scope<T> { | |
| fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b | |
| where | |
| F: for<'a> FnOnce(&'a T) + 'b, | |
| { | |
| try_pin_init! { | |
| Self { | |
| data <- data, | |
| _pin: PhantomPinned | |
| } ? E | |
| } | |
| .pin_chain(|scope| { | |
| init(&scope.data); | |
| Ok(()) | |
| }) | |
| } | |
| } | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| impl<'b, T: 'b> Scope<T> { | |
| fn entry_mut(self: Pin<&mut Self>) -> &mut Entry<'static> { | |
| // SAFETY: _entry is not structurally pinned. | |
| unsafe { &mut Pin::into_inner_unchecked(self)._entry } | |
| } | |
| fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b | |
| where | |
| F: for<'a> FnOnce(&'a T) -> Entry<'static> + 'b, | |
| { | |
| try_pin_init! { | |
| Self { | |
| _entry: Entry::empty(), | |
| data <- data, | |
| _pin: PhantomPinned | |
| } ? E | |
| } | |
| .pin_chain(|scope| { | |
| *scope.entry_mut() = init(&scope.data); | |
| Ok(()) | |
| }) | |
| } | |
| } | |
| impl<'a, T: 'a> Scope<T> { | |
| /// Creates a new scope, which is a directory at the root of the debugfs filesystem, | |
| /// associated with some data `T`. | |
| /// | |
| /// The `init` closure is called to populate the directory with files and subdirectories. These | |
| /// files can reference the data stored in the scope. | |
| /// | |
| /// The entire directory tree created within the scope will be removed when the returned | |
| /// `Scope` handle is dropped. | |
| pub fn dir<E: 'a, F>( | |
| data: impl PinInit<T, E> + 'a, | |
| name: &'a CStr, | |
| init: F, | |
| ) -> impl PinInit<Self, E> + 'a | |
| where | |
| F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a, | |
| { | |
| Scope::new(data, |data| { | |
| let scoped = ScopedDir::new(name); | |
| init(data, &scoped); | |
| scoped.into_entry() | |
| }) | |
| } | |
| } | |
| impl<T> Deref for Scope<T> { | |
| type Target = T; | |
| fn deref(&self) -> &T { | |
| &self.data | |
| } | |
| } | |
| impl<T> Deref for File<T> { | |
| type Target = T; | |
| fn deref(&self) -> &T { | |
| &self.scope | |
| } | |
| } | |
| /// A handle to a directory which will live at most `'dir`, accessing data that will live for at | |
| /// least `'data`. | |
| /// | |
| /// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping | |
| /// the `Scope` that created it. | |
| pub struct ScopedDir<'data, 'dir> { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| entry: ManuallyDrop<Entry<'dir>>, | |
| _phantom: PhantomData<fn(&'data ()) -> &'dir ()>, | |
| } | |
| impl<'data, 'dir> ScopedDir<'data, 'dir> { | |
| /// Creates a subdirectory inside this `ScopedDir`. | |
| /// | |
| /// The returned directory handle cannot outlive this one. | |
| pub fn dir<'dir2>(&'dir2 self, name: &CStr) -> ScopedDir<'data, 'dir2> { | |
| #[cfg(not(CONFIG_DEBUG_FS))] | |
| let _ = name; | |
| ScopedDir { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| entry: ManuallyDrop::new(Entry::dir(name, Some(&*self.entry))), | |
| _phantom: PhantomData, | |
| } | |
| } | |
| fn create_file<T: Sync>(&self, name: &CStr, data: &'data T, vtable: &'static FileOps<T>) { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| core::mem::forget(Entry::file(name, &self.entry, data, vtable)); | |
| } | |
| /// Creates a read-only file in this directory. | |
| /// | |
| /// The file's contents are produced by invoking [`Writer::write`]. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn read_only_file<T: Writer + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a read-only binary file in this directory. | |
| /// | |
| /// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`]. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created file is removed | |
| /// when the [`Scope`] that this directory belongs to is dropped. | |
| pub fn read_binary_file<T: BinaryWriter + Send + Sync + 'static>( | |
| &self, | |
| name: &CStr, | |
| data: &'data T, | |
| ) { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a read-only file in this directory, with contents from a callback. | |
| /// | |
| /// The file contents are generated by calling `f` with `data`. | |
| /// | |
| /// | |
| /// `f` must be a function item or a non-capturing closure. | |
| /// This is statically asserted and not a safety requirement. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn read_callback_file<T, F>(&self, name: &CStr, data: &'data T, _f: &'static F) | |
| where | |
| T: Send + Sync + 'static, | |
| F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, | |
| { | |
| let vtable = <FormatAdapter<T, F> as ReadFile<_>>::FILE_OPS.adapt(); | |
| self.create_file(name, data, vtable) | |
| } | |
| /// Creates a read-write file in this directory. | |
| /// | |
| /// Reading the file uses the [`Writer`] implementation on `data`. Writing to the file uses | |
| /// the [`Reader`] implementation on `data`. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn read_write_file<T: Writer + Reader + Send + Sync + 'static>( | |
| &self, | |
| name: &CStr, | |
| data: &'data T, | |
| ) { | |
| let vtable = &<T as ReadWriteFile<_>>::FILE_OPS; | |
| self.create_file(name, data, vtable) | |
| } | |
| /// Creates a read-write binary file in this directory. | |
| /// | |
| /// Reading the file uses the [`BinaryWriter`] implementation on `data`. Writing to the file | |
| /// uses the [`BinaryReader`] implementation on `data`. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created file is removed | |
| /// when the [`Scope`] that this directory belongs to is dropped. | |
| pub fn read_write_binary_file<T: BinaryWriter + BinaryReader + Send + Sync + 'static>( | |
| &self, | |
| name: &CStr, | |
| data: &'data T, | |
| ) { | |
| let vtable = &<T as BinaryReadWriteFile<_>>::FILE_OPS; | |
| self.create_file(name, data, vtable) | |
| } | |
| /// Creates a read-write file in this directory, with logic from callbacks. | |
| /// | |
| /// Reading from the file is handled by `f`. Writing to the file is handled by `w`. | |
| /// | |
| /// `f` and `w` must be function items or non-capturing closures. | |
| /// This is statically asserted and not a safety requirement. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn read_write_callback_file<T, F, W>( | |
| &self, | |
| name: &CStr, | |
| data: &'data T, | |
| _f: &'static F, | |
| _w: &'static W, | |
| ) where | |
| T: Send + Sync + 'static, | |
| F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync, | |
| W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync, | |
| { | |
| let vtable = <WritableAdapter<FormatAdapter<T, F>, W> as ReadWriteFile<_>>::FILE_OPS | |
| .adapt() | |
| .adapt(); | |
| self.create_file(name, data, vtable) | |
| } | |
| /// Creates a write-only file in this directory. | |
| /// | |
| /// Writing to the file uses the [`Reader`] implementation on `data`. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn write_only_file<T: Reader + Send + Sync + 'static>(&self, name: &CStr, data: &'data T) { | |
| let vtable = &<T as WriteFile<_>>::FILE_OPS; | |
| self.create_file(name, data, vtable) | |
| } | |
| /// Creates a write-only binary file in this directory. | |
| /// | |
| /// Writing to the file uses the [`BinaryReader`] implementation on `data`. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created file is removed | |
| /// when the [`Scope`] that this directory belongs to is dropped. | |
| pub fn write_binary_file<T: BinaryReader + Send + Sync + 'static>( | |
| &self, | |
| name: &CStr, | |
| data: &'data T, | |
| ) { | |
| self.create_file(name, data, &T::FILE_OPS) | |
| } | |
| /// Creates a write-only file in this directory, with write logic from a callback. | |
| /// | |
| /// Writing to the file is handled by `w`. | |
| /// | |
| /// `w` must be a function item or a non-capturing closure. | |
| /// This is statically asserted and not a safety requirement. | |
| /// | |
| /// This function does not produce an owning handle to the file. The created | |
| /// file is removed when the [`Scope`] that this directory belongs | |
| /// to is dropped. | |
| pub fn write_only_callback_file<T, W>(&self, name: &CStr, data: &'data T, _w: &'static W) | |
| where | |
| T: Send + Sync + 'static, | |
| W: Fn(&T, &mut UserSliceReader) -> Result + Send + Sync, | |
| { | |
| let vtable = &<WritableAdapter<NoWriter<T>, W> as WriteFile<_>>::FILE_OPS | |
| .adapt() | |
| .adapt(); | |
| self.create_file(name, data, vtable) | |
| } | |
| fn empty() -> Self { | |
| ScopedDir { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| entry: ManuallyDrop::new(Entry::empty()), | |
| _phantom: PhantomData, | |
| } | |
| } | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| fn into_entry(self) -> Entry<'dir> { | |
| ManuallyDrop::into_inner(self.entry) | |
| } | |
| #[cfg(not(CONFIG_DEBUG_FS))] | |
| fn into_entry(self) {} | |
| } | |
| impl<'data> ScopedDir<'data, 'static> { | |
| // This is safe, but intentionally not exported due to footgun status. A ScopedDir with no | |
| // parent will never be released by default, and needs to have its entry extracted and used | |
| // somewhere. | |
| fn new(name: &CStr) -> ScopedDir<'data, 'static> { | |
| ScopedDir { | |
| #[cfg(CONFIG_DEBUG_FS)] | |
| entry: ManuallyDrop::new(Entry::dir(name, None)), | |
| _phantom: PhantomData, | |
| } | |
| } | |
| } |