Skip to main content

abi_context/
lib.rs

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