@@ -14,7 +14,10 @@ use crate::str::CStr;
1414use crate :: sync:: Arc ;
1515use crate :: uaccess:: UserSliceReader ;
1616use core:: fmt;
17+ use core:: marker:: PhantomData ;
1718use core:: marker:: PhantomPinned ;
19+ #[ cfg( CONFIG_DEBUG_FS ) ]
20+ use core:: mem:: ManuallyDrop ;
1821use core:: ops:: Deref ;
1922
2023mod traits;
@@ -40,7 +43,7 @@ use entry::Entry;
4043// able to refer to us. In this case, we need to silently fail. All future child directories/files
4144// will silently fail as well.
4245#[ derive( Clone ) ]
43- pub struct Dir ( #[ cfg( CONFIG_DEBUG_FS ) ] Option < Arc < Entry > > ) ;
46+ pub struct Dir ( #[ cfg( CONFIG_DEBUG_FS ) ] Option < Arc < Entry < ' static > > > ) ;
4447
4548impl Dir {
4649 /// Create a new directory in DebugFS. If `parent` is [`None`], it will be created at the root.
@@ -264,17 +267,67 @@ impl Dir {
264267 . adapt ( ) ;
265268 self . create_file ( name, data, file_ops)
266269 }
270+
271+ // While this function is safe, it is intentionally not public because it's a bit of a
272+ // footgun.
273+ //
274+ // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate
275+ // time, a `ScopedDir` with a `Dir` parent will never be deleted.
276+ fn scoped_dir < ' data > ( & self , name : & CStr ) -> ScopedDir < ' data , ' static > {
277+ #[ cfg( CONFIG_DEBUG_FS ) ]
278+ {
279+ let parent_entry = match & self . 0 {
280+ None => return ScopedDir :: empty ( ) ,
281+ Some ( entry) => entry. clone ( ) ,
282+ } ;
283+ ScopedDir {
284+ entry : ManuallyDrop :: new ( Entry :: dynamic_dir ( name, Some ( parent_entry) ) ) ,
285+ _phantom : PhantomData ,
286+ }
287+ }
288+ #[ cfg( not( CONFIG_DEBUG_FS ) ) ]
289+ ScopedDir :: empty ( )
290+ }
291+
292+ /// Creates a new scope, which is a directory associated with some data `T`.
293+ ///
294+ /// The created directory will be a subdirectory of `self`. The `init` closure is called to
295+ /// populate the directory with files and subdirectories. These files can reference the data
296+ /// stored in the scope.
297+ ///
298+ /// The entire directory tree created within the scope will be removed when the returned
299+ /// `Scope` handle is dropped.
300+ pub fn scope < ' a , T : ' a , E : ' a , F > (
301+ & ' a self ,
302+ data : impl PinInit < T , E > + ' a ,
303+ name : & ' a CStr ,
304+ init : F ,
305+ ) -> impl PinInit < Scope < T > , E > + ' a
306+ where
307+ F : for <' data , ' dir > FnOnce ( & ' data T , & ' dir ScopedDir < ' data , ' dir > ) + ' a ,
308+ {
309+ Scope :: new ( data, |data| {
310+ let scoped = self . scoped_dir ( name) ;
311+ init ( data, & scoped) ;
312+ scoped. into_entry ( )
313+ } )
314+ }
267315}
268316
269317#[ pin_data]
270- /// Handle to a DebugFS scope, which ensures that attached `data` will outlive the provided
271- /// [`Entry`] without moving.
272- /// Currently, this is used to back [`File`] so that its `read` and/or `write` implementations
273- /// can assume that their backing data is still alive.
274- struct Scope < T > {
318+ /// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry
319+ /// without moving.
320+ ///
321+ /// This is internally used to back [`File`], and used in the API to represent the attachment
322+ /// of a directory lifetime to a data structure which may be jointly accessed by a number of
323+ /// different files.
324+ ///
325+ /// When dropped, a `Scope` will remove all directories and files in the filesystem backed by the
326+ /// attached data structure prior to releasing the attached data.
327+ pub struct Scope < T > {
275328 // This order is load-bearing for drops - `_entry` must be dropped before `data`.
276329 #[ cfg( CONFIG_DEBUG_FS ) ]
277- _entry : Entry ,
330+ _entry : Entry < ' static > ,
278331 #[ pin]
279332 data : T ,
280333 // Even if `T` is `Unpin`, we still can't allow it to be moved.
@@ -312,14 +365,14 @@ impl<'b, T: 'b> Scope<T> {
312365
313366#[ cfg( CONFIG_DEBUG_FS ) ]
314367impl < ' b , T : ' b > Scope < T > {
315- fn entry_mut ( self : Pin < & mut Self > ) -> & mut Entry {
368+ fn entry_mut ( self : Pin < & mut Self > ) -> & mut Entry < ' static > {
316369 // SAFETY: _entry is not structurally pinned.
317370 unsafe { & mut Pin :: into_inner_unchecked ( self ) . _entry }
318371 }
319372
320373 fn new < E : ' b , F > ( data : impl PinInit < T , E > + ' b , init : F ) -> impl PinInit < Self , E > + ' b
321374 where
322- F : for < ' a > FnOnce ( & ' a T ) -> Entry + ' b ,
375+ F : for < ' a > FnOnce ( & ' a T ) -> Entry < ' static > + ' b ,
323376 {
324377 try_pin_init ! {
325378 Self {
@@ -335,6 +388,31 @@ impl<'b, T: 'b> Scope<T> {
335388 }
336389}
337390
391+ impl < ' a , T : ' a > Scope < T > {
392+ /// Creates a new scope, which is a directory at the root of the debugfs filesystem,
393+ /// associated with some data `T`.
394+ ///
395+ /// The `init` closure is called to populate the directory with files and subdirectories. These
396+ /// files can reference the data stored in the scope.
397+ ///
398+ /// The entire directory tree created within the scope will be removed when the returned
399+ /// `Scope` handle is dropped.
400+ pub fn dir < E : ' a , F > (
401+ data : impl PinInit < T , E > + ' a ,
402+ name : & ' a CStr ,
403+ init : F ,
404+ ) -> impl PinInit < Self , E > + ' a
405+ where
406+ F : for <' data , ' dir > FnOnce ( & ' data T , & ' dir ScopedDir < ' data , ' dir > ) + ' a ,
407+ {
408+ Scope :: new ( data, |data| {
409+ let scoped = ScopedDir :: new ( name) ;
410+ init ( data, & scoped) ;
411+ scoped. into_entry ( )
412+ } )
413+ }
414+ }
415+
338416impl < T > Deref for Scope < T > {
339417 type Target = T ;
340418 fn deref ( & self ) -> & T {
@@ -348,3 +426,169 @@ impl<T> Deref for File<T> {
348426 & self . scope
349427 }
350428}
429+
430+ /// A handle to a directory which will live at most `'dir`, accessing data that will live for at
431+ /// least `'data`.
432+ ///
433+ /// Dropping a ScopedDir will not delete or clean it up, this is expected to occur through dropping
434+ /// the `Scope` that created it.
435+ pub struct ScopedDir < ' data , ' dir > {
436+ #[ cfg( CONFIG_DEBUG_FS ) ]
437+ entry : ManuallyDrop < Entry < ' dir > > ,
438+ _phantom : PhantomData < fn ( & ' data ( ) ) -> & ' dir ( ) > ,
439+ }
440+
441+ impl < ' data , ' dir > ScopedDir < ' data , ' dir > {
442+ /// Creates a subdirectory inside this `ScopedDir`.
443+ ///
444+ /// The returned directory handle cannot outlive this one.
445+ pub fn dir < ' dir2 > ( & ' dir2 self , name : & CStr ) -> ScopedDir < ' data , ' dir2 > {
446+ #[ cfg( not( CONFIG_DEBUG_FS ) ) ]
447+ let _ = name;
448+ ScopedDir {
449+ #[ cfg( CONFIG_DEBUG_FS ) ]
450+ entry : ManuallyDrop :: new ( Entry :: dir ( name, Some ( & * self . entry ) ) ) ,
451+ _phantom : PhantomData ,
452+ }
453+ }
454+
455+ fn create_file < T : Sync > ( & self , name : & CStr , data : & ' data T , vtable : & ' static FileOps < T > ) {
456+ #[ cfg( CONFIG_DEBUG_FS ) ]
457+ core:: mem:: forget ( Entry :: file ( name, & self . entry , data, vtable) ) ;
458+ }
459+
460+ /// Creates a read-only file in this directory.
461+ ///
462+ /// The file's contents are produced by invoking [`Writer::write`].
463+ ///
464+ /// This function does not produce an owning handle to the file. The created
465+ /// file is removed when the [`Scope`] that this directory belongs
466+ /// to is dropped.
467+ pub fn read_only_file < T : Writer + Send + Sync + ' static > ( & self , name : & CStr , data : & ' data T ) {
468+ self . create_file ( name, data, & T :: FILE_OPS )
469+ }
470+
471+ /// Creates a read-only file in this directory, with contents from a callback.
472+ ///
473+ /// The file contents are generated by calling `f` with `data`.
474+ ///
475+ ///
476+ /// `f` must be a function item or a non-capturing closure.
477+ /// This is statically asserted and not a safety requirement.
478+ ///
479+ /// This function does not produce an owning handle to the file. The created
480+ /// file is removed when the [`Scope`] that this directory belongs
481+ /// to is dropped.
482+ pub fn read_callback_file < T , F > ( & self , name : & CStr , data : & ' data T , _f : & ' static F )
483+ where
484+ T : Send + Sync + ' static ,
485+ F : Fn ( & T , & mut fmt:: Formatter < ' _ > ) -> fmt:: Result + Send + Sync ,
486+ {
487+ let vtable = <FormatAdapter < T , F > as ReadFile < _ > >:: FILE_OPS . adapt ( ) ;
488+ self . create_file ( name, data, vtable)
489+ }
490+
491+ /// Creates a read-write file in this directory.
492+ ///
493+ /// Reading the file uses the [`Writer`] implementation on `data`. Writing to the file uses
494+ /// the [`Reader`] implementation on `data`.
495+ ///
496+ /// This function does not produce an owning handle to the file. The created
497+ /// file is removed when the [`Scope`] that this directory belongs
498+ /// to is dropped.
499+ pub fn read_write_file < T : Writer + Reader + Send + Sync + ' static > (
500+ & self ,
501+ name : & CStr ,
502+ data : & ' data T ,
503+ ) {
504+ let vtable = & <T as ReadWriteFile < _ > >:: FILE_OPS ;
505+ self . create_file ( name, data, vtable)
506+ }
507+
508+ /// Creates a read-write file in this directory, with logic from callbacks.
509+ ///
510+ /// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
511+ ///
512+ /// `f` and `w` must be function items or non-capturing closures.
513+ /// This is statically asserted and not a safety requirement.
514+ ///
515+ /// This function does not produce an owning handle to the file. The created
516+ /// file is removed when the [`Scope`] that this directory belongs
517+ /// to is dropped.
518+ pub fn read_write_callback_file < T , F , W > (
519+ & self ,
520+ name : & CStr ,
521+ data : & ' data T ,
522+ _f : & ' static F ,
523+ _w : & ' static W ,
524+ ) where
525+ T : Send + Sync + ' static ,
526+ F : Fn ( & T , & mut fmt:: Formatter < ' _ > ) -> fmt:: Result + Send + Sync ,
527+ W : Fn ( & T , & mut UserSliceReader ) -> Result + Send + Sync ,
528+ {
529+ let vtable = <WritableAdapter < FormatAdapter < T , F > , W > as ReadWriteFile < _ > >:: FILE_OPS
530+ . adapt ( )
531+ . adapt ( ) ;
532+ self . create_file ( name, data, vtable)
533+ }
534+
535+ /// Creates a write-only file in this directory.
536+ ///
537+ /// Writing to the file uses the [`Reader`] implementation on `data`.
538+ ///
539+ /// This function does not produce an owning handle to the file. The created
540+ /// file is removed when the [`Scope`] that this directory belongs
541+ /// to is dropped.
542+ pub fn write_only_file < T : Reader + Send + Sync + ' static > ( & self , name : & CStr , data : & ' data T ) {
543+ let vtable = & <T as WriteFile < _ > >:: FILE_OPS ;
544+ self . create_file ( name, data, vtable)
545+ }
546+
547+ /// Creates a write-only file in this directory, with write logic from a callback.
548+ ///
549+ /// Writing to the file is handled by `w`.
550+ ///
551+ /// `w` must be a function item or a non-capturing closure.
552+ /// This is statically asserted and not a safety requirement.
553+ ///
554+ /// This function does not produce an owning handle to the file. The created
555+ /// file is removed when the [`Scope`] that this directory belongs
556+ /// to is dropped.
557+ pub fn write_only_callback_file < T , W > ( & self , name : & CStr , data : & ' data T , _w : & ' static W )
558+ where
559+ T : Send + Sync + ' static ,
560+ W : Fn ( & T , & mut UserSliceReader ) -> Result + Send + Sync ,
561+ {
562+ let vtable = & <WritableAdapter < NoWriter < T > , W > as WriteFile < _ > >:: FILE_OPS
563+ . adapt ( )
564+ . adapt ( ) ;
565+ self . create_file ( name, data, vtable)
566+ }
567+
568+ fn empty ( ) -> Self {
569+ ScopedDir {
570+ #[ cfg( CONFIG_DEBUG_FS ) ]
571+ entry : ManuallyDrop :: new ( Entry :: empty ( ) ) ,
572+ _phantom : PhantomData ,
573+ }
574+ }
575+ #[ cfg( CONFIG_DEBUG_FS ) ]
576+ fn into_entry ( self ) -> Entry < ' dir > {
577+ ManuallyDrop :: into_inner ( self . entry )
578+ }
579+ #[ cfg( not( CONFIG_DEBUG_FS ) ) ]
580+ fn into_entry ( self ) { }
581+ }
582+
583+ impl < ' data > ScopedDir < ' data , ' static > {
584+ // This is safe, but intentionally not exported due to footgun status. A ScopedDir with no
585+ // parent will never be released by default, and needs to have its entry extracted and used
586+ // somewhere.
587+ fn new ( name : & CStr ) -> ScopedDir < ' data , ' static > {
588+ ScopedDir {
589+ #[ cfg( CONFIG_DEBUG_FS ) ]
590+ entry : ManuallyDrop :: new ( Entry :: dir ( name, None ) ) ,
591+ _phantom : PhantomData ,
592+ }
593+ }
594+ }
0 commit comments