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