abi_context/
lib.rs

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        // Find first available identifier by checking gaps in existing keys
70        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            // Skip keys outside our range
81            if key_raw < start_raw || key_raw > end_raw {
82                continue;
83            }
84
85            // Found a gap before this key
86            if current < key_raw {
87                return FileIdentifier::new(current).map(|id| UniqueFileIdentifier::new(task, id));
88            }
89
90            // Move past this key
91            current = key_raw.checked_add(1)?;
92            if current > end_raw {
93                break;
94            }
95        }
96
97        // Check if there's space after all existing keys
98        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(&current_file_identifier)?;
268
269            new_size += path.len() + 1; // +1 for the separator
270
271            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); // Do not call drop explicitly since they are invalid
361        }
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        // Set task before calling remove_directory since it calls get_current_task_identifier
382        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        // Since base has no parent (INVALID), it stops and only includes the provided path
424        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        // The algorithm stops when reaching a directory with INVALID parent (root)
447        // So it builds path from children directories only, not including root
448        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}