11use super :: fs:: { FileError , FileResult , FileSystem , PathBuf } ;
22use crate :: fs:: Path ;
3- use alloc:: { boxed:: Box , collections :: BTreeMap } ;
3+ use alloc:: { boxed:: Box , vec :: Vec } ;
44use core:: {
55 marker:: PhantomData ,
66 sync:: atomic:: { AtomicI64 , Ordering } ,
77} ;
8+ use hashbrown:: HashMap ;
89use hyperdrive:: locks:: rw:: RwLock ;
910
1011pub trait VfsHelper {
@@ -13,7 +14,7 @@ pub trait VfsHelper {
1314 fn get_current_process_id ( ) -> u64 ;
1415}
1516
16- #[ derive( Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd ) ]
17+ #[ derive( Debug , Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash ) ]
1718pub struct Handle {
1819 id : i64 ,
1920}
@@ -31,14 +32,10 @@ impl Handle {
3132
3233 #[ must_use]
3334 #[ inline]
34- #[ expect( clippy:: missing_panics_doc, reason = "Never panics" ) ]
3535 pub fn new ( ) -> Self {
36- let id = HANDLE_COUNTER . fetch_add ( 1 , Ordering :: Relaxed ) ;
37-
3836 // By opening 1 000 files a second, it would take 3 000 000 centuries to overflow,
3937 // so we can deliberately not handle the overflow.
40- assert ! ( id >= 0 , "Handle ID overflow" ) ;
41-
38+ let id = HANDLE_COUNTER . fetch_add ( 1 , Ordering :: Relaxed ) ;
4239 Self { id }
4340 }
4441
@@ -53,7 +50,7 @@ impl Handle {
5350 ///
5451 /// The given ID should be positive.
5552 pub const unsafe fn from_raw ( id : i64 ) -> Self {
56- debug_assert ! ( id >= 0 ) ;
53+ debug_assert ! ( id >= 1 ) ;
5754 Self { id }
5855 }
5956
@@ -64,12 +61,11 @@ impl Handle {
6461 }
6562}
6663
67- type Mounts = BTreeMap < PathBuf , RwLock < Box < dyn FileSystem + Send + Sync > > > ;
68- type OpenFiles = BTreeMap < Handle , OpenFileInfo > ;
64+ type OpenFiles = HashMap < Handle , OpenFileInfo > ;
6965
7066#[ derive( Default ) ]
7167pub struct Vfs < H : VfsHelper > {
72- mounts : RwLock < Mounts > ,
68+ mounts : RwLock < MountIndex > ,
7369 open_handles : RwLock < OpenFiles > ,
7470 _helper : PhantomData < H > ,
7571}
@@ -79,51 +75,92 @@ struct OpenFileInfo {
7975 path : PathBuf ,
8076}
8177
78+ /// Cached sorted mount list for efficient path matching.
79+ /// Stored as `(path_length, path, filesystem)` sorted by length descending.
80+ #[ derive( Default ) ]
81+ struct MountIndex {
82+ /// Sorted list of mounts by path length (longest first) for prefix matching
83+ sorted_mounts : Vec < ( usize , PathBuf ) > ,
84+ /// `HashMap` for filesystem lookup
85+ filesystems : HashMap < PathBuf , RwLock < Box < dyn FileSystem + Send + Sync > > > ,
86+ }
87+
8288impl < H : VfsHelper > Vfs < H > {
8389 #[ must_use]
84- #[ inline]
8590 /// Creates a new VFS instance.
86- pub const fn new ( ) -> Self {
91+ pub fn new ( ) -> Self {
8792 Self {
88- mounts : RwLock :: new ( BTreeMap :: new ( ) ) ,
89- open_handles : RwLock :: new ( BTreeMap :: new ( ) ) ,
93+ mounts : RwLock :: new ( MountIndex :: default ( ) ) ,
94+ open_handles : RwLock :: new ( HashMap :: new ( ) ) ,
9095 _helper : PhantomData ,
9196 }
9297 }
9398
9499 /// Mounts a filesystem at the given path.
95100 pub fn mount ( & self , path : PathBuf , fs : Box < dyn FileSystem + Send + Sync > ) {
96- self . mounts . write ( ) . insert ( path, RwLock :: new ( fs) ) ;
101+ let mut mounts = self . mounts . write ( ) ;
102+ let path_len = path. as_path ( ) . len ( ) ;
103+
104+ // Insert into hashmap
105+ mounts. filesystems . insert ( path. clone ( ) , RwLock :: new ( fs) ) ;
106+
107+ // Insert into sorted list maintaining descending order by length
108+ match mounts
109+ . sorted_mounts
110+ . binary_search_by ( |& ( len, _) | len. cmp ( & path_len) . reverse ( ) )
111+ {
112+ Ok ( idx) => {
113+ // Find the correct position (may have duplicates)
114+ mounts. sorted_mounts . insert ( idx, ( path_len, path) ) ;
115+ }
116+ Err ( idx) => mounts. sorted_mounts . insert ( idx, ( path_len, path) ) ,
117+ }
97118 }
98119
99120 /// Unmounts the filesystem at the given path.
100- pub fn unmount ( & self , path : & str ) -> FileResult < Box < dyn FileSystem + Send + Sync > > {
101- self . mounts
102- . write ( )
103- . remove ( path)
104- . map ( RwLock :: into_inner)
105- . ok_or ( FileError :: NotFound )
106- }
107-
108- /// Checks if a file is opened.
109- fn check_file_opened ( & self , path : Path ) -> bool {
110- let current_pid = H :: get_current_process_id ( ) ;
111- self . open_handles . read ( ) . values ( ) . any ( |open_file| {
112- open_file. path . as_path ( ) == path && open_file. process_id == current_pid
121+ pub fn unmount ( & self , path : Path ) -> FileResult < Box < dyn FileSystem + Send + Sync > > {
122+ let mut mounts = self . mounts . write ( ) ;
123+
124+ // Remove from sorted list
125+ mounts. sorted_mounts . retain ( |( _, p) | p. as_path ( ) != path) ;
126+
127+ // Remove from hashmap and return the filesystem
128+ // Try to find matching PathBuf by string comparison
129+ let path_buf = mounts
130+ . filesystems
131+ . iter ( )
132+ . find ( |( p, _) | p. as_path ( ) == path)
133+ . map ( |( p, _) | p. clone ( ) ) ;
134+
135+ path_buf. map_or ( Err ( FileError :: NotFound ) , |path_buf| {
136+ mounts
137+ . filesystems
138+ . remove ( & path_buf)
139+ . map ( RwLock :: into_inner)
140+ . ok_or ( FileError :: NotFound )
113141 } )
114142 }
115143
116144 /// Creates a new handle.
117145 ///
118146 /// This function performs checks and adds the handle to the open handles list.
119147 fn new_handle ( & self , path : Path ) -> FileResult < Handle > {
120- if self . check_file_opened ( path) {
121- return Err ( FileError :: PermissionDenied ) ;
148+ let current_pid = H :: get_current_process_id ( ) ;
149+
150+ // Check if already opened by this process
151+ {
152+ let open_handles = self . open_handles . read ( ) ;
153+ if open_handles. values ( ) . any ( |open_file| {
154+ open_file. path . as_path ( ) == path && open_file. process_id == current_pid
155+ } ) {
156+ return Err ( FileError :: PermissionDenied ) ;
157+ }
122158 }
159+
123160 let handle = Handle :: new ( ) ;
124161 let open_file_info = OpenFileInfo {
125162 path : path. to_owned ( ) ,
126- process_id : H :: get_current_process_id ( ) ,
163+ process_id : current_pid ,
127164 } ;
128165 self . open_handles . write ( ) . insert ( handle, open_file_info) ;
129166 Ok ( handle)
@@ -156,37 +193,33 @@ impl<H: VfsHelper> Vfs<H> {
156193 f : impl FnOnce ( & mut ( dyn FileSystem + Send + Sync ) , Path ) -> FileResult < T > ,
157194 ) -> FileResult < T > {
158195 let mounts = self . mounts . read ( ) ;
159-
160- let mut best_match: Option < ( & RwLock < Box < dyn FileSystem + Send + Sync > > , usize ) > = None ;
161- let mut best_len = 0 ;
162-
163196 let path_str = path. as_str ( ) ;
164197
165- for ( mount_path, fs) in mounts. iter ( ) {
166- let mount_len = mount_path. as_path ( ) . len ( ) ;
167- if mount_len <= best_len {
198+ for & ( mount_len, ref mount_path) in & mounts. sorted_mounts {
199+ if mount_len > path_str. len ( ) {
168200 continue ;
169201 }
170202
171203 // Check if path starts with mount point
172- if path_str. len ( ) >= mount_len
173- && & path_str[ ..mount_len] == mount_path. as_path ( ) . as_str ( )
174- {
204+ if & path_str[ ..mount_len] == mount_path. as_path ( ) . as_str ( ) {
175205 // Ensure we match at path boundaries to avoid partial matches
176206 // e.g., /dev should not match /device
177207 if mount_len == path_str. len ( )
178208 || path_str. as_bytes ( ) . get ( mount_len) == Some ( & b'/' )
179209 || mount_path. as_path ( ) . as_str ( ) . ends_with ( '/' )
180210 {
181- best_match = Some ( ( fs, mount_len) ) ;
182- best_len = mount_len;
211+ // Found match - get filesystem from hashmap for O(1) lookup
212+ let fs = mounts
213+ . filesystems
214+ . get ( mount_path)
215+ . ok_or ( FileError :: InvalidPath ) ?;
216+ let rel_path = Path :: from ( & path_str[ mount_len..] ) ;
217+ return f ( & mut * * fs. write ( ) , rel_path) ;
183218 }
184219 }
185220 }
186221
187- let ( fs, mount_len) = best_match. ok_or ( FileError :: InvalidPath ) ?;
188- let rel_path = Path :: from ( & path_str[ mount_len..] ) ;
189- f ( & mut * * fs. write ( ) , rel_path)
222+ Err ( FileError :: InvalidPath )
190223 }
191224
192225 #[ inline]
@@ -220,19 +253,29 @@ impl<H: VfsHelper> Vfs<H> {
220253 self . open_handles . write ( ) . retain ( |_handle, open_file| {
221254 let retained = open_file. process_id != pid;
222255 if !retained {
223- self . path_to_fs ( open_file. path . as_path ( ) , |fs, rel_path| fs. close ( rel_path) )
224- . unwrap ( ) ;
256+ let res =
257+ self . path_to_fs ( open_file. path . as_path ( ) , |fs, rel_path| fs. close ( rel_path) ) ;
258+ debug_assert ! ( res. is_ok( ) , "Failed to close file during process cleanup" ) ;
225259 }
226260 retained
227261 } ) ;
228262 }
229263
230264 /// Deletes a file at the given path.
231265 pub fn delete ( & self , path : Path ) -> FileResult < ( ) > {
232- if self . check_file_opened ( path) {
233- return Err ( FileError :: PermissionDenied ) ;
266+ let current_pid = H :: get_current_process_id ( ) ;
267+
268+ // Check if file is opened with lock to prevent TOCTOU issues
269+ {
270+ let open_handles = self . open_handles . read ( ) ;
271+ if open_handles. values ( ) . any ( |open_file| {
272+ open_file. path . as_path ( ) == path && open_file. process_id == current_pid
273+ } ) {
274+ return Err ( FileError :: PermissionDenied ) ;
275+ }
234276 }
235- // FIXME: TOCTOU vulnerability?
277+
278+ // Delete the file from the filesystem
236279 self . path_to_fs ( path, |fs, rel_path| fs. delete ( rel_path) )
237280 }
238281
@@ -261,7 +304,7 @@ impl<H: VfsHelper> Vfs<H> {
261304 self . path_to_fs ( path, |fs, rel_path| fs. metadata ( rel_path) )
262305 }
263306
264- pub fn read_dir ( & self , path : Path ) -> FileResult < alloc :: vec :: Vec < PathBuf > > {
307+ pub fn read_dir ( & self , path : Path ) -> FileResult < Vec < PathBuf > > {
265308 self . path_to_fs ( path, |fs, rel_path| fs. read_dir ( rel_path) )
266309 }
267310}
0 commit comments