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