1#![no_std]
2
3extern crate alloc;
4
5mod file;
6mod unique_file;
7
8pub use file::*;
9
10use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec};
11use file_system::{Path, PathOwned};
12use smol_str::SmolStr;
13use synchronization::{blocking_mutex::raw::CriticalSectionRawMutex, rwlock::RwLock};
14use task::TaskIdentifier;
15use task::block_on;
16use unique_file::UniqueFileIdentifier;
17use virtual_file_system::{SynchronousDirectory, SynchronousFile};
18
19pub static CONTEXT: Context = Context::new();
20
21pub fn get_instance() -> &'static Context {
22 &CONTEXT
23}
24
25struct DirectoryEntry {
26 path: SmolStr,
27 parent: Option<FileIdentifier>,
28 directory: SynchronousDirectory,
29}
30
31type FileEntry = SynchronousFile;
32
33struct Inner {
34 task: Option<TaskIdentifier>,
35 directories: BTreeMap<UniqueFileIdentifier, DirectoryEntry>,
36 files: BTreeMap<UniqueFileIdentifier, FileEntry>,
37}
38
39pub struct Context(RwLock<CriticalSectionRawMutex, Inner>);
40
41impl Default for Context {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl Context {
48 pub const fn new() -> Self {
49 Self(RwLock::new(Inner {
50 task: None,
51 directories: BTreeMap::new(),
52 files: BTreeMap::new(),
53 }))
54 }
55
56 pub fn get_current_task_identifier(&self) -> TaskIdentifier {
57 block_on(self.0.read()).task.expect("No current task set")
58 }
59
60 fn get_new_identifier<V>(
61 map: &BTreeMap<UniqueFileIdentifier, V>,
62 task: TaskIdentifier,
63 start: FileIdentifier,
64 end: FileIdentifier,
65 ) -> Option<UniqueFileIdentifier> {
66 let start_raw = start.into_inner();
67 let end_raw = end.into_inner();
68
69 let mut current = start_raw;
71
72 for key in map.keys() {
73 let (key_task, key_file) = key.split();
74 if key_task != task {
75 continue;
76 }
77
78 let key_raw = key_file.into_inner();
79
80 if key_raw < start_raw || key_raw > end_raw {
82 continue;
83 }
84
85 if current < key_raw {
87 return FileIdentifier::new(current).map(|id| UniqueFileIdentifier::new(task, id));
88 }
89
90 current = key_raw.checked_add(1)?;
92 if current > end_raw {
93 break;
94 }
95 }
96
97 if current <= end_raw {
99 return FileIdentifier::new(current).map(|id| UniqueFileIdentifier::new(task, id));
100 }
101
102 None
103 }
104
105 fn get_new_identifier_file(
106 map: &BTreeMap<UniqueFileIdentifier, FileEntry>,
107 task: TaskIdentifier,
108 ) -> Option<UniqueFileIdentifier> {
109 Self::get_new_identifier(
110 map,
111 task,
112 FileIdentifier::MINIMUM_FILE,
113 FileIdentifier::MAXIMUM_FILE,
114 )
115 }
116
117 fn get_new_identifier_directory(
118 map: &BTreeMap<UniqueFileIdentifier, DirectoryEntry>,
119 task: TaskIdentifier,
120 ) -> Option<UniqueFileIdentifier> {
121 Self::get_new_identifier(
122 map,
123 task,
124 FileIdentifier::MINIMUM_DIRECTORY,
125 FileIdentifier::MAXIMUM_DIRECTORY,
126 )
127 }
128
129 pub fn insert_file(
130 &self,
131 task: TaskIdentifier,
132 file: SynchronousFile,
133 custom_file_identifier: Option<FileIdentifier>,
134 ) -> Option<FileIdentifier> {
135 let mut inner = block_on(self.0.write());
136
137 let file_identifier = if let Some(custom_file_identifier) = custom_file_identifier {
138 let file_identifier = UniqueFileIdentifier::new(task, custom_file_identifier);
139 if inner.files.contains_key(&file_identifier) {
140 panic!("File identifier {:?} is already in use", file_identifier);
141 }
142 file_identifier
143 } else {
144 Self::get_new_identifier_file(&inner.files, task).unwrap()
145 };
146
147 inner.files.insert(file_identifier, file);
148
149 Some(file_identifier.get_file())
150 }
151
152 pub fn perform_operation_on_file_or_directory<FF, FD, O>(
153 &self,
154 file_identifier: FileIdentifier,
155 operation_file: FF,
156 operation_directory: FD,
157 ) -> Option<O>
158 where
159 FF: FnOnce(&mut SynchronousFile) -> O,
160 FD: FnOnce(&mut SynchronousDirectory) -> O,
161 {
162 let task = self.get_current_task_identifier();
163 let unique_file = UniqueFileIdentifier::new(task, file_identifier);
164
165 let mut inner = block_on(self.0.write());
166
167 if file_identifier.is_directory() {
168 inner
169 .directories
170 .get_mut(&unique_file)
171 .map(|entry| operation_directory(&mut entry.directory))
172 } else {
173 inner.files.get_mut(&unique_file).map(operation_file)
174 }
175 }
176
177 pub fn perform_operation_on_file<F, O>(
178 &self,
179 file_identifier: FileIdentifier,
180 operation: F,
181 ) -> Option<O>
182 where
183 F: FnOnce(&mut SynchronousFile) -> O,
184 {
185 let task = self.get_current_task_identifier();
186 let file = UniqueFileIdentifier::new(task, file_identifier);
187
188 let mut inner = block_on(self.0.write());
189 let file = inner.files.get_mut(&file)?;
190
191 Some(operation(file))
192 }
193
194 pub fn perform_operation_on_directory<F, O>(
195 &self,
196 file: FileIdentifier,
197 operation: F,
198 ) -> Option<O>
199 where
200 F: FnOnce(&mut SynchronousDirectory) -> O,
201 {
202 let task = self.get_current_task_identifier();
203 let file = UniqueFileIdentifier::new(task, file);
204
205 let mut inner = block_on(self.0.write());
206 inner
207 .directories
208 .get_mut(&file)
209 .map(|entry| operation(&mut entry.directory))
210 }
211
212 pub fn insert_directory(
213 &self,
214 task: TaskIdentifier,
215 parent: Option<FileIdentifier>,
216 path: impl AsRef<Path>,
217 directory: SynchronousDirectory,
218 ) -> Option<FileIdentifier> {
219 let mut inner = block_on(self.0.write());
220
221 let file_identifier = Self::get_new_identifier_directory(&inner.directories, task).unwrap();
222
223 inner.directories.insert(
224 file_identifier,
225 DirectoryEntry {
226 path: SmolStr::new(path.as_ref()),
227 parent,
228 directory,
229 },
230 );
231
232 Some(file_identifier.get_file())
233 }
234
235 pub fn remove_directory(&self, file: FileIdentifier) -> Option<SynchronousDirectory> {
236 let task = self.get_current_task_identifier();
237 let file = UniqueFileIdentifier::new(task, file);
238
239 let mut inner = block_on(self.0.write());
240 inner.directories.remove(&file).map(|entry| entry.directory)
241 }
242
243 pub fn remove_file(&self, file: FileIdentifier) -> Option<SynchronousFile> {
244 let task = self.get_current_task_identifier();
245 let file = UniqueFileIdentifier::new(task, file);
246
247 let mut inner = block_on(self.0.write());
248 inner.files.remove(&file)
249 }
250
251 pub fn resolve_path(
252 &self,
253 task: TaskIdentifier,
254 directory: FileIdentifier,
255 path: impl AsRef<Path>,
256 ) -> Option<PathOwned> {
257 let inner = block_on(self.0.read());
258
259 let mut stack: Vec<&SmolStr> = vec![];
260
261 let mut new_size = path.as_ref().get_length();
262
263 let mut current_file_identifier = directory.into_unique(task);
264
265 loop {
266 let DirectoryEntry { path, parent, .. } =
267 inner.directories.get(¤t_file_identifier)?;
268
269 new_size += path.len() + 1; if let Some(parent) = parent {
272 stack.push(path);
273 current_file_identifier = parent.into_unique(task);
274 } else {
275 break;
276 }
277 }
278
279 let mut new_path = PathOwned::new_with_capacity(new_size);
280
281 while let Some(path) = stack.pop() {
282 new_path = new_path.join(Path::from_str(path))?;
283 }
284
285 let new_path = new_path.join(path)?;
286
287 Some(new_path)
288 }
289
290 pub async fn set_task(&self, task: TaskIdentifier) {
291 loop {
292 let mut inner = self.0.write().await;
293
294 if inner.task.is_none() {
295 inner.task.replace(task);
296 break;
297 }
298 }
299 }
300
301 pub async fn clear_task(&self) {
302 let mut inner = self.0.write().await;
303 inner.task.take();
304 }
305
306 pub async fn call_abi<F, Fut, R>(&self, function: F) -> R
307 where
308 F: FnOnce() -> Fut,
309 Fut: Future<Output = R>,
310 {
311 let task = task::get_instance().get_current_task_identifier().await;
312 self.set_task(task).await;
313 let result = function().await;
314 self.clear_task().await;
315 result
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use core::mem::forget;
322
323 use file_system::{AccessFlags, DummyFileSystem};
324
325 use super::*;
326
327 #[test]
328 fn test_context_new() {
329 let context = Context::new();
330 assert!(block_on(context.0.read()).task.is_none());
331 assert!(block_on(context.0.read()).directories.is_empty());
332 }
333
334 #[test]
335 fn test_get_instance() {
336 let _ = get_instance();
337 }
338
339 fn new_dummy_directory() -> SynchronousDirectory {
340 SynchronousDirectory::new(
341 &DummyFileSystem,
342 AccessFlags::READ_WRITE.into(),
343 file_system::Context::new_empty(),
344 )
345 }
346
347 fn initialize() -> (TaskIdentifier, Context) {
348 let context = Context::new();
349 let task = TaskIdentifier::new(1);
350 (task, context)
351 }
352
353 fn clean_up(context: &Context) {
354 let mut inner = block_on(context.0.write());
355
356 let keys = inner.directories.keys().cloned().collect::<Vec<_>>();
357
358 for key in keys {
359 let directory = inner.directories.remove(&key).unwrap();
360 forget(directory); }
362 }
363
364 #[test]
365 fn test_insert_and_remove_opened_file_identifier_path() {
366 let (task, context) = initialize();
367 let parent_id = FileIdentifier::new_panic(10);
368 let path = Path::from_str("test.txt");
369
370 let file_identifier = context
371 .insert_directory(task, Some(parent_id), path, new_dummy_directory())
372 .unwrap();
373
374 let inner = block_on(context.0.read());
375
376 let unique_file_identifier = UniqueFileIdentifier::new(task, file_identifier);
377
378 assert!(inner.directories.contains_key(&unique_file_identifier));
379 drop(inner);
380
381 block_on(context.set_task(task));
383 forget(context.remove_directory(file_identifier));
384 block_on(context.clear_task());
385
386 let inner = block_on(context.0.read());
387 assert!(!inner.directories.contains_key(&unique_file_identifier));
388 drop(inner);
389
390 clean_up(&context);
391 }
392
393 #[test]
394 fn test_insert_with_none_parent() {
395 let (task, context) = initialize();
396 let path = Path::from_str("test.txt");
397 let directory = new_dummy_directory();
398
399 let file_identifier = context
400 .insert_directory(task, None, path, directory)
401 .unwrap();
402
403 let inner = block_on(context.0.read());
404 let unique_file_identifier = UniqueFileIdentifier::new(task, file_identifier);
405 let DirectoryEntry { parent, .. } = inner.directories.get(&unique_file_identifier).unwrap();
406 assert_eq!(*parent, None);
407 drop(inner);
408
409 clean_up(&context);
410 }
411
412 #[test]
413 fn test_get_full_path_single_level() {
414 let (task, context) = initialize();
415 let path = Path::from_str("base");
416 let directory = new_dummy_directory();
417
418 let base_id = context
419 .insert_directory(task, None, path, directory)
420 .unwrap();
421
422 let result = context.resolve_path(task, base_id, Path::from_str("file.txt"));
423 assert_eq!(result.unwrap().as_str(), "/file.txt");
425
426 clean_up(&context);
427 }
428
429 #[test]
430 fn test_get_full_path_nested() {
431 let (task, context) = initialize();
432
433 let root_id = context
434 .insert_directory(task, None, "root", new_dummy_directory())
435 .unwrap();
436 let dir_id = context
437 .insert_directory(task, Some(root_id), "dir", new_dummy_directory())
438 .unwrap();
439 let sub_dir_id = context
440 .insert_directory(task, Some(dir_id), "subdir", new_dummy_directory())
441 .unwrap();
442
443 let path = context
444 .resolve_path(task, sub_dir_id, Path::from_str("file.txt"))
445 .unwrap();
446 assert_eq!(path.as_str(), "/dir/subdir/file.txt");
449
450 clean_up(&context);
451 }
452
453 #[test]
454 fn test_get_full_path_nonexistent() {
455 let (task, context) = initialize();
456 let file_id = FileIdentifier::new_panic(999);
457
458 let result = context.resolve_path(task, file_id, Path::from_str("file.txt"));
459 assert_eq!(result, None);
460
461 clean_up(&context);
462 }
463
464 #[test]
465 fn test_remove_nonexistent_file() {
466 let (task, context) = initialize();
467 let file_id = FileIdentifier::new_panic(999);
468
469 block_on(context.set_task(task));
470 let result = context.remove_directory(file_id);
471 block_on(context.clear_task());
472
473 assert!(result.is_none());
474 clean_up(&context);
475 }
476
477 #[test]
478 fn test_multiple_tasks() {
479 let (task1, context) = initialize();
480 let task2 = TaskIdentifier::new(2);
481
482 let file_id1 = context
483 .insert_directory(
484 task1,
485 None,
486 Path::from_str("task1.txt"),
487 new_dummy_directory(),
488 )
489 .unwrap();
490 let file_id2 = context
491 .insert_directory(
492 task2,
493 None,
494 Path::from_str("task2.txt"),
495 new_dummy_directory(),
496 )
497 .unwrap();
498
499 let inner = block_on(context.0.read());
500 let unique_id1 = UniqueFileIdentifier::new(task1, file_id1);
501 let unique_id2 = UniqueFileIdentifier::new(task2, file_id2);
502
503 assert!(inner.directories.contains_key(&unique_id1));
504 assert!(inner.directories.contains_key(&unique_id2));
505 drop(inner);
506
507 clean_up(&context);
508 }
509}