Skip to main content

fuchsia_fatfs/
refs.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module provides abstractions over the fatfs Dir and File types,
6//! erasing their lifetimes and allowing them to be kept.
7use crate::directory::FatDirectory;
8use crate::filesystem::FatFilesystem;
9use crate::node::Node;
10use crate::types::{Dir, File};
11use scopeguard::defer;
12use std::cell::{Cell, Ref, RefCell, RefMut};
13use std::rc::Rc;
14use std::sync::Arc;
15use zx::Status;
16
17mod opaque {
18    #[repr(transparent)]
19    pub struct Opaque<T>(T);
20}
21pub use opaque::Opaque;
22
23/// Trait for types that can have their lifetime faked to `'static` and restored.
24///
25/// # Safety
26///
27/// Implementer must ensure that `Self` and `Self::Target` have the same memory layout
28/// and size. Typically this is only implemented for `T<'static>` with `Target = T<'a>`.
29pub unsafe trait ErasedLifetime<'a>: Sized {
30    type Target: 'a;
31
32    /// Erases the lifetime by transmute_copying the value.
33    ///
34    /// # Safety
35    ///
36    /// The caller must ensure that the returned value is not used after the lifetime
37    /// of the original value has expired.
38    unsafe fn erase(val: Self::Target) -> Opaque<Self> {
39        // SAFETY: Transmute is safe because the trait guarantees that Self and Self::Target
40        // have the same memory layout.
41        unsafe {
42            let res = std::mem::transmute_copy(&val);
43            std::mem::forget(val);
44            res
45        }
46    }
47
48    /// Restores the lifetime by transmute_copying the value.
49    ///
50    /// # Safety
51    ///
52    /// The caller must ensure that the returned value is bound to the correct lifetime.
53    unsafe fn restore(val: Opaque<Self>) -> Self::Target {
54        // SAFETY: Transmute is safe because the trait guarantees that Self and Self::Target
55        // have the same memory layout.
56        unsafe {
57            let res = std::mem::transmute_copy(&val);
58            std::mem::forget(val);
59            res
60        }
61    }
62
63    /// Restores the reference lifetime by casting the pointer.
64    ///
65    /// # Safety
66    ///
67    /// The caller must ensure that the returned reference is bound to the correct lifetime.
68    unsafe fn restore_ref(val: &Opaque<Self>) -> &Self::Target {
69        // SAFETY: Casting pointer is safe because the trait guarantees that Self and Self::Target
70        // have the same memory layout.
71        unsafe { &*(val as *const Opaque<Self> as *const Self::Target) }
72    }
73
74    /// Restores the mutable reference lifetime by casting the pointer.
75    ///
76    /// # Safety
77    ///
78    /// The caller must ensure that the returned reference is bound to the correct lifetime.
79    unsafe fn restore_mut(val: &mut Opaque<Self>) -> &mut Self::Target {
80        // SAFETY: Casting pointer is safe because the trait guarantees that Self and Self::Target
81        // have the same memory layout.
82        unsafe { &mut *(val as *mut Opaque<Self> as *mut Self::Target) }
83    }
84}
85
86// SAFETY: Dir<'static> and Dir<'a> have the same memory layout and size.
87unsafe impl<'a> ErasedLifetime<'a> for Dir<'static> {
88    type Target = Dir<'a>;
89}
90
91// SAFETY: File<'static> and File<'a> have the same memory layout and size.
92unsafe impl<'a> ErasedLifetime<'a> for File<'static> {
93    type Target = File<'a>;
94}
95
96pub struct FsRef<T> {
97    inner: RefCell<Option<Opaque<T>>>,
98    open_count: Cell<usize>,
99    filesystem: Rc<FatFilesystem>,
100}
101
102impl<T> FsRef<T> {
103    /// Creates an `FsRef` from a value with erased lifetime.
104    ///
105    /// # Safety
106    ///
107    /// The caller must ensure that `val` is a reference associated with the provided `filesystem`.
108    /// Mixing references from different filesystem instances will result in undefined behavior.
109    pub unsafe fn from<'a>(
110        val: <T as ErasedLifetime<'a>>::Target,
111        filesystem: Rc<FatFilesystem>,
112    ) -> Self
113    where
114        T: ErasedLifetime<'a>,
115    {
116        FsRef {
117            // SAFETY: Safe because T::Target (Dir<'a>/File<'a>) is bound to the lifetime of the
118            // filesystem, which is kept alive by `self.filesystem`.
119            inner: RefCell::new(Some(unsafe { T::erase(val) })),
120            open_count: Cell::new(1),
121            filesystem,
122        }
123    }
124
125    pub fn empty(filesystem: Rc<FatFilesystem>) -> Self {
126        FsRef { inner: RefCell::new(None), open_count: Cell::new(0), filesystem }
127    }
128
129    pub fn filesystem(&self) -> &Rc<FatFilesystem> {
130        &self.filesystem
131    }
132
133    pub fn close<'a>(&self)
134    where
135        T: ErasedLifetime<'a>,
136    {
137        let open_count = self.open_count.get();
138        assert!(open_count > 0);
139        self.open_count.set(open_count - 1);
140        if open_count == 1 {
141            self.clear();
142        }
143    }
144
145    pub fn clear<'a>(&self)
146    where
147        T: ErasedLifetime<'a>,
148    {
149        if let Some(f) = self.inner.borrow_mut().take() {
150            // SAFETY: Safe because the restored value is dropped immediately while `self`
151            // (and thus `self.filesystem`) is alive.
152            unsafe {
153                let restored = <T as ErasedLifetime<'a>>::restore(f);
154                drop(restored);
155            }
156        }
157    }
158
159    pub fn get<'a>(&'a self) -> Option<Ref<'a, <T as ErasedLifetime<'a>>::Target>>
160    where
161        T: ErasedLifetime<'a>,
162    {
163        let borrow = self.inner.borrow();
164        if borrow.is_none() {
165            None
166        } else {
167            Some(Ref::map(borrow, |val| {
168                // SAFETY: Safe because the returned reference is bound to the lifetime of
169                // self.inner borrow by Ref::map.
170                unsafe { T::restore_ref(val.as_ref().unwrap()) }
171            }))
172        }
173    }
174
175    pub fn get_mut<'a>(&'a self) -> Option<RefMut<'a, <T as ErasedLifetime<'a>>::Target>>
176    where
177        T: ErasedLifetime<'a>,
178    {
179        let borrow = self.inner.borrow_mut();
180        if borrow.is_none() {
181            None
182        } else {
183            Some(RefMut::map(borrow, |val| {
184                // SAFETY: Safe because the returned reference is bound to the lifetime of
185                // self.inner borrow by RefMut::map.
186                unsafe { T::restore_mut(val.as_mut().unwrap()) }
187            }))
188        }
189    }
190}
191
192impl<T> Drop for FsRef<T> {
193    fn drop(&mut self) {
194        assert_eq!(self.open_count.get(), 0);
195        assert!(self.inner.borrow().is_none());
196    }
197}
198
199impl FsRef<Dir<'static>> {
200    pub fn maybe_reopen(
201        &self,
202        parent: Option<&Arc<FatDirectory>>,
203        name: &str,
204    ) -> Result<(), Status> {
205        if self.open_count.get() == 0 { Ok(()) } else { self.reopen(parent, name) }
206    }
207
208    fn reopen(&self, parent: Option<&Arc<FatDirectory>>, name: &str) -> Result<(), Status> {
209        let dir = if let Some(parent) = parent {
210            parent.open_ref()?;
211            defer! { parent.close_ref() }
212            parent.find_child(name)?.ok_or(Status::NOT_FOUND)?.to_dir()
213        } else {
214            self.filesystem.fatfs_root_dir()
215        };
216        // SAFETY: We hold `self.filesystem` which is Rc<FatFilesystem>, ensuring the
217        // filesystem outlives this FsRef.
218        self.inner.replace(Some(unsafe { <Dir<'static> as ErasedLifetime>::erase(dir) }));
219        Ok(())
220    }
221
222    pub fn open(&self, parent: Option<&Arc<FatDirectory>>, name: &str) -> Result<(), Status> {
223        let open_count = self.open_count.get();
224        if open_count == usize::MAX {
225            Err(Status::UNAVAILABLE)
226        } else {
227            if open_count == 0 {
228                self.reopen(parent, name)?;
229            }
230            self.open_count.set(open_count + 1);
231            Ok(())
232        }
233    }
234}
235
236impl FsRef<File<'static>> {
237    pub fn maybe_reopen(&self, parent: &FatDirectory, name: &str) -> Result<(), Status> {
238        if self.open_count.get() == 0 { Ok(()) } else { self.reopen(parent, name) }
239    }
240
241    fn reopen(&self, parent: &FatDirectory, name: &str) -> Result<(), Status> {
242        let file = parent.find_child(name)?.ok_or(Status::NOT_FOUND)?.to_file();
243        // SAFETY: We hold `self.filesystem` which is Rc<FatFilesystem>, ensuring the
244        // filesystem outlives this FsRef.
245        self.inner.replace(Some(unsafe { <File<'static> as ErasedLifetime>::erase(file) }));
246        Ok(())
247    }
248
249    pub fn open(&self, parent: Option<&FatDirectory>, name: &str) -> Result<(), Status> {
250        let open_count = self.open_count.get();
251        if open_count == usize::MAX {
252            Err(Status::UNAVAILABLE)
253        } else {
254            if open_count == 0 {
255                self.reopen(parent.ok_or(Status::BAD_HANDLE)?, name)?;
256            }
257            self.open_count.set(open_count + 1);
258            Ok(())
259        }
260    }
261}
262
263pub type FatfsDirRef = FsRef<Dir<'static>>;
264pub type FatfsFileRef = FsRef<File<'static>>;