Skip to content

Commit 7490029

Browse files
Make the fs Backend dyn-compatible
1 parent 95b1ac2 commit 7490029

4 files changed

Lines changed: 214 additions & 98 deletions

File tree

litebox/src/fs/backend.rs

Lines changed: 142 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33

44
//! [`Backend`] for filesystems supported by [`super::resolver`]
55
6+
use alloc::boxed::Box;
67
use alloc::vec::Vec;
8+
use core::any::{Any, TypeId};
9+
use core::marker::PhantomData;
10+
11+
use crate::utilities::anymap::AnyCloneSendSync;
712

813
use super::errors::{
914
ChmodError, ChownError, FileStatusError, MkdirError, OpenError, ReadDirError, ReadError,
@@ -38,18 +43,9 @@ pub(super) mod private {
3843
}
3944

4045
/// A backend that can be used to support a (full or subset of) a LiteBox filesystem.
41-
pub trait Backend: private::Sealed + Send + Sync + 'static {
42-
/// Supporting walk through the backend
43-
type WalkingDirHandle<'a>: 'a;
44-
45-
/// An owned handle to an open file
46-
type FileHandle: Clone + Send + Sync + 'static;
47-
48-
/// An owned handle to an open directory
49-
type DirHandle: Clone + Send + Sync + 'static;
50-
46+
pub trait Backend: private::Sealed + Send + Sync + Any {
5147
/// Obtain access to the root directory of the backend.
52-
fn root(&self) -> Self::WalkingDirHandle<'_>;
48+
fn root(&self) -> WalkingDirHandle<'_>;
5349

5450
/// Walk one or more `components` starting from the `from` handle.
5551
///
@@ -60,12 +56,12 @@ pub trait Backend: private::Sealed + Send + Sync + 'static {
6056
/// `WalkStopReason::StoppedAtNonDirectory`.
6157
fn walk_directories<'a>(
6258
&'a self,
63-
from: Self::WalkingDirHandle<'a>,
59+
from: WalkingDirHandle<'a>,
6460
components: &[&str],
65-
) -> Result<WalkOutcome<Self::WalkingDirHandle<'a>>, WalkError>;
61+
) -> Result<WalkOutcome<WalkingDirHandle<'a>>, WalkError>;
6662

6763
/// Take an owned handle to a `dir` found via a walk.
68-
fn owned_dir_at(&self, dir: Self::WalkingDirHandle<'_>) -> Self::DirHandle;
64+
fn owned_dir_at(&self, dir: WalkingDirHandle<'_>) -> DirHandle;
6965

7066
/// Obtain a walking handle to an existing owned dir.
7167
///
@@ -74,28 +70,27 @@ pub trait Backend: private::Sealed + Send + Sync + 'static {
7470
///
7571
/// XXX(jayb): We will likely migrate away from `Option` here when we do a bit of an overhaul of
7672
/// the `errors` module in order to more consistently support stale errors everywhere.
77-
fn walking_dir_at<'a>(&'a self, dir: &Self::DirHandle) -> Option<Self::WalkingDirHandle<'a>>;
73+
fn walking_dir_at<'a>(&'a self, dir: &DirHandle) -> Option<WalkingDirHandle<'a>>;
7874

7975
/// Open an (existing) file at `dir`.
8076
///
8177
/// To create a file, you need [`Self::create_file_at`].
8278
fn open_file_at(
8379
&self,
84-
dir: Self::WalkingDirHandle<'_>,
80+
dir: WalkingDirHandle<'_>,
8581
name: &str,
8682
flags: OFlags,
87-
) -> Result<Permissioned<Self::FileHandle>, OpenError>;
83+
) -> Result<Permissioned<FileHandle>, OpenError>;
8884

8985
/// Read directory entries at `dir`.
90-
fn list_dir_at(&self, handle: Self::DirHandle) -> Result<Vec<DirEntry>, ReadDirError>;
86+
fn list_dir_at(&self, handle: DirHandle) -> Result<Vec<DirEntry>, ReadDirError>;
9187

9288
/// Read at `offset` into `buf`, returning the number of bytes read.
9389
///
9490
/// Backends do not have an internal notion of offsets; instead the resolver maintains offsets
9591
/// as needed. For files with non-position-based [`SeekBehavior`], such as `stdin`, the resolver
9692
/// passes zero and the backend should ignore the offset.
97-
fn read(&self, h: &Self::FileHandle, buf: &mut [u8], offset: usize)
98-
-> Result<usize, ReadError>;
93+
fn read(&self, h: &FileHandle, buf: &mut [u8], offset: usize) -> Result<usize, ReadError>;
9994

10095
/// Optional performance hook: get static backing data for a file, if available and supported.
10196
///
@@ -104,7 +99,7 @@ pub trait Backend: private::Sealed + Send + Sync + 'static {
10499
///
105100
/// Returns `None` if indicating no static backing data is available/supported.
106101
#[expect(unused_variables, reason = "default body, non-underscored param names")]
107-
fn get_static_backing_data(&self, h: &Self::FileHandle) -> Option<&'static [u8]> {
102+
fn get_static_backing_data(&self, h: &FileHandle) -> Option<&'static [u8]> {
108103
None
109104
}
110105

@@ -116,59 +111,156 @@ pub trait Backend: private::Sealed + Send + Sync + 'static {
116111
// it ugly on the interface side here. It would be very ugly for us to pass in extra flags, or
117112
// indeed even need to maintain/handle seeking on every backend; mostly we need some sort of
118113
// nicer locking discipline, but I don't want to block the MVP for this just yet.
119-
fn write(&self, h: &Self::FileHandle, buf: &[u8], offset: usize) -> Result<usize, WriteError>;
114+
fn write(&self, h: &FileHandle, buf: &[u8], offset: usize) -> Result<usize, WriteError>;
120115

121116
/// Truncate the file to the specified length.
122117
///
123118
/// If shorter than existing size, extra data is lost. If longer than existing size, resize by
124119
/// adding `\0`s.
125-
fn truncate(&self, h: &Self::FileHandle, length: usize) -> Result<(), TruncateError>;
120+
fn truncate(&self, h: &FileHandle, length: usize) -> Result<(), TruncateError>;
126121

127122
/// Describe seek behavior for an open file handle.
128-
fn seek_behavior(&self, h: &Self::FileHandle) -> SeekBehavior;
123+
fn seek_behavior(&self, h: &FileHandle) -> SeekBehavior;
129124

130125
/// Status of an open file handle.
131-
fn file_status(&self, h: &Self::FileHandle) -> Result<FileStatus, FileStatusError>;
126+
fn file_status(&self, h: &FileHandle) -> Result<FileStatus, FileStatusError>;
132127

133128
/// Status of an open directory handle.
134-
fn dir_status(&self, h: &Self::DirHandle) -> Result<FileStatus, FileStatusError>;
129+
fn dir_status(&self, h: &DirHandle) -> Result<FileStatus, FileStatusError>;
135130

136131
/// Create a new file at `parent` with the given `name` and `mode`.
137132
fn create_file_at(
138133
&self,
139-
dir: Self::DirHandle,
134+
dir: DirHandle,
140135
name: &str,
141136
mode: Mode,
142-
) -> Result<Self::FileHandle, OpenError>;
137+
) -> Result<FileHandle, OpenError>;
143138

144139
/// Create a new directory at `parent` with the given `name` and `mode`.
145-
fn mkdir_at(
146-
&self,
147-
dir: Self::DirHandle,
148-
name: &str,
149-
mode: Mode,
150-
) -> Result<Self::DirHandle, MkdirError>;
140+
fn mkdir_at(&self, dir: DirHandle, name: &str, mode: Mode) -> Result<DirHandle, MkdirError>;
151141

152142
/// Remove the file `name` at `parent`.
153-
fn unlink_at(&self, dir: Self::DirHandle, name: &str) -> Result<(), UnlinkError>;
143+
fn unlink_at(&self, dir: DirHandle, name: &str) -> Result<(), UnlinkError>;
154144

155145
/// Remove the directory `name` at `parent`.
156146
// XXX(jayb): I don't like that unlink and rmdir exist separately, we should probably merge them.
157-
fn rmdir_at(&self, dir: Self::DirHandle, name: &str) -> Result<(), RmdirError>;
147+
fn rmdir_at(&self, dir: DirHandle, name: &str) -> Result<(), RmdirError>;
158148

159149
/// Update the permissions for the file/dir `name` at `parent`.
160-
fn chmod_at(&self, dir: Self::DirHandle, name: &str, mode: Mode) -> Result<(), ChmodError>;
150+
fn chmod_at(&self, dir: DirHandle, name: &str, mode: Mode) -> Result<(), ChmodError>;
161151

162152
/// Update the owner/group for the file/dir `name` at `parent`.
163153
fn chown_at(
164154
&self,
165-
dir: Self::DirHandle,
155+
dir: DirHandle,
166156
name: &str,
167157
user: Option<u16>,
168158
group: Option<u16>,
169159
) -> Result<(), ChownError>;
170160
}
171161

162+
/// Concrete handle types used by a backend.
163+
///
164+
/// This trait is intentionally separate from [`Backend`]: the dyn-safe [`Backend`] interface keeps
165+
/// using erased handle wrappers, while concrete backend implementations can use these associated
166+
/// types at their own boundaries instead of spelling out manual erased-handle downcasts.
167+
pub(crate) trait BackendHandles {
168+
/// Supporting walk through the backend
169+
type WalkingDirHandle<'a>: 'a;
170+
/// An owned handle to an open file
171+
type FileHandle: Clone + Send + Sync + 'static;
172+
/// An owned handle to an open directory
173+
type DirHandle: Clone + Send + Sync + 'static;
174+
}
175+
176+
/// Supporting walk through the backend
177+
pub struct WalkingDirHandle<'a> {
178+
backend_type: TypeId,
179+
raw: Box<dyn ErasedWalkingDirHandle + 'a>,
180+
_invariant: PhantomData<fn(&'a ()) -> &'a ()>,
181+
}
182+
183+
/// An owned handle to an open file
184+
#[derive(Clone)]
185+
pub struct FileHandle {
186+
raw: Box<dyn AnyCloneSendSync>,
187+
}
188+
189+
/// An owned handle to an open directory
190+
#[derive(Clone)]
191+
pub struct DirHandle {
192+
raw: Box<dyn AnyCloneSendSync>,
193+
}
194+
195+
trait ErasedWalkingDirHandle {
196+
fn into_raw(self: Box<Self>) -> *mut ();
197+
}
198+
199+
impl<H> ErasedWalkingDirHandle for H {
200+
fn into_raw(self: Box<Self>) -> *mut () {
201+
Box::into_raw(self).cast()
202+
}
203+
}
204+
205+
impl<'a> WalkingDirHandle<'a> {
206+
pub(super) fn from_typed<B: BackendHandles + 'static>(handle: B::WalkingDirHandle<'a>) -> Self {
207+
Self {
208+
backend_type: TypeId::of::<B>(),
209+
raw: Box::new(handle),
210+
_invariant: PhantomData,
211+
}
212+
}
213+
214+
/// Recover the concrete walking handle stored in this erased handle for `B`.
215+
pub(super) fn into_typed<B: BackendHandles + 'static>(self) -> B::WalkingDirHandle<'a> {
216+
assert_eq!(
217+
self.backend_type,
218+
TypeId::of::<B>(),
219+
"backend walking directory handle type mismatch"
220+
);
221+
// SAFETY: `from_typed::<B>` records `TypeId::of::<B>()` and stores a
222+
// `B::WalkingDirHandle<'a>`. `WalkingDirHandle<'a>` is invariant in `'a`, so the lifetime
223+
// parameter cannot have been changed since construction. The assertion above confirms that
224+
// this handle is being recovered for the same backend type, and together these guarantee
225+
// that the allocation has the expected concrete type.
226+
unsafe { *Box::from_raw(self.raw.into_raw().cast::<B::WalkingDirHandle<'a>>()) }
227+
}
228+
}
229+
230+
impl FileHandle {
231+
pub(super) fn from_typed<B: BackendHandles>(handle: B::FileHandle) -> Self {
232+
Self {
233+
raw: Box::new(handle),
234+
}
235+
}
236+
237+
pub(super) fn get_typed<B: BackendHandles>(&self) -> &B::FileHandle {
238+
(&*self.raw as &dyn Any)
239+
.downcast_ref::<B::FileHandle>()
240+
.expect("backend file handle type mismatch")
241+
}
242+
}
243+
244+
impl DirHandle {
245+
pub(super) fn from_typed<B: BackendHandles>(handle: B::DirHandle) -> Self {
246+
Self {
247+
raw: Box::new(handle),
248+
}
249+
}
250+
251+
pub(super) fn get_typed<B: BackendHandles>(&self) -> &B::DirHandle {
252+
(&*self.raw as &dyn Any)
253+
.downcast_ref::<B::DirHandle>()
254+
.expect("backend directory handle type mismatch")
255+
}
256+
257+
pub(super) fn into_typed<B: BackendHandles>(self) -> B::DirHandle {
258+
let raw: Box<dyn Any> = self.raw;
259+
*raw.downcast::<B::DirHandle>()
260+
.expect("backend directory handle type mismatch")
261+
}
262+
}
263+
172264
/// A successful walk of directories through the backend
173265
pub struct WalkOutcome<Walking> {
174266
/// A component per walked element.
@@ -232,3 +324,15 @@ pub(super) struct PermissionInfo {
232324
pub(super) mode: Mode,
233325
pub(super) owner: UserInfo,
234326
}
327+
328+
#[cfg(test)]
329+
mod tests {
330+
use super::Backend;
331+
332+
#[test]
333+
fn backend_is_dyn_safe() {
334+
fn assert_dyn_safe(_: Option<&dyn Backend>) {}
335+
336+
assert_dyn_safe(None);
337+
}
338+
}

0 commit comments

Comments
 (0)