Skip to main content

strate_fs_ramfs/
lib.rs

1//! RAM Filesystem implementation for Strat9-OS
2//!
3//! Stores all file data and directory structure in memory.
4//! Compliant with VfsFileSystem trait from strate-fs-abstraction.
5
6#![no_std]
7
8extern crate alloc;
9
10use alloc::{
11    collections::BTreeMap,
12    string::{String, ToString},
13    sync::Arc,
14    vec::Vec,
15};
16use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
17use spin::{Mutex, RwLock};
18use strate_fs_abstraction::{
19    FsCapabilities, FsError, FsResult, RenameFlags, VfsDirEntry, VfsFileInfo, VfsFileSystem,
20    VfsFileType, VfsTimestamp, VfsVolumeInfo,
21};
22
23/// Internal node type for RamFS
24enum RamNode {
25    File {
26        data: Vec<u8>,
27        mode: u32,
28    },
29    Directory {
30        entries: BTreeMap<String, u64>, // Maps name to Inode ID
31        mode: u32,
32    },
33}
34
35struct RamInode {
36    node: Mutex<RamNode>,
37    open_count: AtomicU32,
38}
39
40impl RamInode {
41    /// Creates a new instance.
42    fn new(node: RamNode) -> Self {
43        Self {
44            node: Mutex::new(node),
45            open_count: AtomicU32::new(0),
46        }
47    }
48}
49
50pub struct RamFileSystem {
51    inodes: RwLock<BTreeMap<u64, Arc<RamInode>>>,
52    next_inode: AtomicU64,
53    capabilities: FsCapabilities,
54}
55
56impl RamFileSystem {
57    /// Creates a new instance.
58    pub fn new() -> Self {
59        let root = Arc::new(RamInode::new(RamNode::Directory {
60            entries: BTreeMap::new(),
61            mode: 0o040755,
62        }));
63        let mut inodes = BTreeMap::new();
64        inodes.insert(2, root); // Standard root inode is 2
65
66        Self {
67            inodes: RwLock::new(inodes),
68            next_inode: AtomicU64::new(10), // Start user inodes at 10
69            capabilities: FsCapabilities::writable_linux(),
70        }
71    }
72
73    /// Returns node.
74    fn get_node(&self, ino: u64) -> FsResult<Arc<RamInode>> {
75        self.inodes
76            .read()
77            .get(&ino)
78            .cloned()
79            .ok_or(FsError::InodeNotFound)
80    }
81
82    /// Implements allocate inode.
83    fn allocate_inode(&self, node: RamNode) -> u64 {
84        let id = self.next_inode.fetch_add(1, Ordering::SeqCst);
85        self.inodes
86            .write()
87            .insert(id, Arc::new(RamInode::new(node)));
88        id
89    }
90
91    /// Implements lookup child inode.
92    fn lookup_child_inode(&self, parent_ino: u64, name: &str) -> FsResult<u64> {
93        let parent = self.get_node(parent_ino)?;
94        let guard = parent.node.lock();
95        match &*guard {
96            RamNode::Directory { entries, .. } => {
97                entries.get(name).copied().ok_or(FsError::NotFound)
98            }
99            _ => Err(FsError::NotADirectory),
100        }
101    }
102
103    /// Internal helper to resolve path to inode (used by IPC server)
104    pub fn resolve_path_internal(&self, path: &str) -> FsResult<u64> {
105        let mut current_ino = self.root_inode();
106        for part in path.split('/').filter(|s| !s.is_empty()) {
107            current_ino = self.lookup_child_inode(current_ino, part)?;
108        }
109        Ok(current_ino)
110    }
111
112    /// Converts this value to file mode.
113    fn to_file_mode(mode: u32) -> u32 {
114        0o100000 | (mode & 0o7777)
115    }
116
117    /// Converts this value to dir mode.
118    fn to_dir_mode(mode: u32) -> u32 {
119        0o040000 | (mode & 0o7777)
120    }
121
122    /// Implements register open.
123    pub fn register_open(&self, ino: u64) -> FsResult<()> {
124        let inode = self.get_node(ino)?;
125        let mut current = inode.open_count.load(Ordering::Acquire);
126        loop {
127            if current == u32::MAX {
128                return Err(FsError::TooManyOpenFiles);
129            }
130            match inode.open_count.compare_exchange_weak(
131                current,
132                current + 1,
133                Ordering::AcqRel,
134                Ordering::Acquire,
135            ) {
136                Ok(_) => return Ok(()),
137                Err(actual) => current = actual,
138            }
139        }
140    }
141
142    /// Implements unregister open.
143    pub fn unregister_open(&self, ino: u64) -> FsResult<()> {
144        let inode = self.get_node(ino)?;
145        let mut current = inode.open_count.load(Ordering::Acquire);
146        let removed_last = loop {
147            if current == 0 {
148                return Err(FsError::InodeNotFound);
149            }
150            let next = current - 1;
151            match inode.open_count.compare_exchange_weak(
152                current,
153                next,
154                Ordering::AcqRel,
155                Ordering::Acquire,
156            ) {
157                Ok(_) => break next == 0,
158                Err(actual) => current = actual,
159            }
160        };
161
162        if removed_last {
163            self.collect_if_detached(ino)?;
164        }
165        Ok(())
166    }
167
168    /// Returns whether open.
169    fn is_open(&self, ino: u64) -> bool {
170        self.get_node(ino)
171            .map(|inode| inode.open_count.load(Ordering::Acquire) > 0)
172            .unwrap_or(false)
173    }
174
175    /// Implements directory contains inode.
176    fn directory_contains_inode(&self, root_dir_ino: u64, target_ino: u64) -> FsResult<bool> {
177        let mut stack = Vec::new();
178        stack.push(root_dir_ino);
179
180        while let Some(current) = stack.pop() {
181            if current == target_ino {
182                return Ok(true);
183            }
184
185            let node = self.get_node(current)?;
186            let guard = node.node.lock();
187            if let RamNode::Directory { entries, .. } = &*guard {
188                for &child in entries.values() {
189                    stack.push(child);
190                }
191            }
192        }
193
194        Ok(false)
195    }
196
197    /// Implements collect if detached.
198    fn collect_if_detached(&self, ino: u64) -> FsResult<()> {
199        if ino == self.root_inode() || self.is_open(ino) {
200            return Ok(());
201        }
202        if self.directory_contains_inode(self.root_inode(), ino)? {
203            return Ok(());
204        }
205        self.collect_detached_subtree(ino)
206    }
207
208    /// Implements collect detached subtree.
209    fn collect_detached_subtree(&self, ino: u64) -> FsResult<()> {
210        if self.is_open(ino) {
211            return Ok(());
212        }
213
214        let children = match self.get_node(ino) {
215            Ok(node) => {
216                let guard = node.node.lock();
217                match &*guard {
218                    RamNode::Directory { entries, .. } => {
219                        let mut out = Vec::new();
220                        for &child in entries.values() {
221                            out.push(child);
222                        }
223                        out
224                    }
225                    RamNode::File { .. } => Vec::new(),
226                }
227            }
228            Err(_) => return Ok(()),
229        };
230
231        for child in children {
232            self.collect_detached_subtree(child)?;
233        }
234
235        if !self.is_open(ino) {
236            self.inodes.write().remove(&ino);
237        }
238        Ok(())
239    }
240}
241
242impl VfsFileSystem for RamFileSystem {
243    /// Implements fs type.
244    fn fs_type(&self) -> &'static str {
245        "ramfs"
246    }
247
248    /// Implements capabilities.
249    fn capabilities(&self) -> &FsCapabilities {
250        &self.capabilities
251    }
252
253    /// Implements root inode.
254    fn root_inode(&self) -> u64 {
255        2
256    }
257
258    /// Returns volume info.
259    fn get_volume_info(&self) -> FsResult<VfsVolumeInfo> {
260        Ok(VfsVolumeInfo {
261            fs_type: String::from("ramfs"),
262            block_size: 4096,
263            ..VfsVolumeInfo::default()
264        })
265    }
266
267    /// Implements stat.
268    fn stat(&self, ino: u64) -> FsResult<VfsFileInfo> {
269        let node = self.get_node(ino)?;
270        let guard = node.node.lock();
271        let mut info = VfsFileInfo::default();
272        info.ino = ino;
273        match &*guard {
274            RamNode::File { data, mode } => {
275                info.size = data.len() as u64;
276                info.file_type = VfsFileType::RegularFile;
277                info.mode = *mode;
278            }
279            RamNode::Directory { mode, .. } => {
280                info.size = 0;
281                info.file_type = VfsFileType::Directory;
282                info.mode = *mode;
283            }
284        }
285        Ok(info)
286    }
287
288    /// Implements lookup.
289    fn lookup(&self, parent_ino: u64, name: &str) -> FsResult<VfsFileInfo> {
290        let ino = self.lookup_child_inode(parent_ino, name)?;
291        self.stat(ino)
292    }
293
294    /// Implements resolve path.
295    fn resolve_path(&self, path: &str) -> FsResult<u64> {
296        self.resolve_path_internal(path)
297    }
298
299    /// Implements read.
300    fn read(&self, ino: u64, offset: u64, buf: &mut [u8]) -> FsResult<usize> {
301        let node = self.get_node(ino)?;
302        let guard = node.node.lock();
303        match &*guard {
304            RamNode::File { data, .. } => {
305                let start = usize::try_from(offset).map_err(|_| FsError::InvalidArgument)?;
306                if offset >= data.len() as u64 {
307                    return Ok(0);
308                }
309                let end = start
310                    .checked_add(buf.len())
311                    .ok_or(FsError::FileTooLarge)?
312                    .min(data.len());
313                let count = end - start;
314                buf[..count].copy_from_slice(&data[start..end]);
315                Ok(count)
316            }
317            _ => Err(FsError::IsADirectory),
318        }
319    }
320
321    /// Implements write.
322    fn write(&self, ino: u64, offset: u64, data: &[u8]) -> FsResult<usize> {
323        let node = self.get_node(ino)?;
324        let mut guard = node.node.lock();
325        match &mut *guard {
326            RamNode::File {
327                data: file_data, ..
328            } => {
329                let start = usize::try_from(offset).map_err(|_| FsError::InvalidArgument)?;
330                let end = start.checked_add(data.len()).ok_or(FsError::FileTooLarge)?;
331                if end > file_data.len() {
332                    file_data.resize(end, 0);
333                }
334                file_data[start..end].copy_from_slice(data);
335                Ok(data.len())
336            }
337            _ => Err(FsError::IsADirectory),
338        }
339    }
340
341    /// Implements readdir.
342    fn readdir(&self, ino: u64) -> FsResult<Vec<VfsDirEntry>> {
343        let node = self.get_node(ino)?;
344        let guard = node.node.lock();
345        match &*guard {
346            RamNode::Directory { entries, .. } => {
347                let children: Vec<(String, u64)> = entries
348                    .iter()
349                    .map(|(name, &ino)| (name.clone(), ino))
350                    .collect();
351                drop(guard);
352
353                let mut result = Vec::new();
354                for (name, child_ino) in children {
355                    let child_node = self.get_node(child_ino)?;
356                    let child_guard = child_node.node.lock();
357                    let file_type = match &*child_guard {
358                        RamNode::File { .. } => VfsFileType::RegularFile,
359                        RamNode::Directory { .. } => VfsFileType::Directory,
360                    };
361                    result.push(VfsDirEntry {
362                        name,
363                        ino: child_ino,
364                        file_type,
365                        offset: 0,
366                    });
367                }
368                Ok(result)
369            }
370            _ => Err(FsError::NotADirectory),
371        }
372    }
373
374    /// Implements create file.
375    fn create_file(&self, parent_ino: u64, name: &str, mode: u32) -> FsResult<VfsFileInfo> {
376        let parent = self.get_node(parent_ino)?;
377        {
378            let guard = parent.node.lock();
379            match &*guard {
380                RamNode::Directory { entries, .. } => {
381                    if entries.contains_key(name) {
382                        return Err(FsError::AlreadyExists);
383                    }
384                }
385                _ => return Err(FsError::NotADirectory),
386            }
387        }
388
389        let new_ino = self.allocate_inode(RamNode::File {
390            data: Vec::new(),
391            mode: Self::to_file_mode(mode),
392        });
393
394        let mut guard = parent.node.lock();
395        match &mut *guard {
396            RamNode::Directory { entries, .. } => {
397                if entries.contains_key(name) {
398                    self.inodes.write().remove(&new_ino);
399                    return Err(FsError::AlreadyExists);
400                }
401                entries.insert(name.to_string(), new_ino);
402                drop(guard);
403                self.stat(new_ino)
404            }
405            _ => {
406                self.inodes.write().remove(&new_ino);
407                Err(FsError::NotADirectory)
408            }
409        }
410    }
411
412    /// Implements create directory.
413    fn create_directory(&self, parent_ino: u64, name: &str, mode: u32) -> FsResult<VfsFileInfo> {
414        let parent = self.get_node(parent_ino)?;
415        {
416            let guard = parent.node.lock();
417            match &*guard {
418                RamNode::Directory { entries, .. } => {
419                    if entries.contains_key(name) {
420                        return Err(FsError::AlreadyExists);
421                    }
422                }
423                _ => return Err(FsError::NotADirectory),
424            }
425        }
426
427        let new_ino = self.allocate_inode(RamNode::Directory {
428            entries: BTreeMap::new(),
429            mode: Self::to_dir_mode(mode),
430        });
431
432        let mut guard = parent.node.lock();
433        match &mut *guard {
434            RamNode::Directory { entries, .. } => {
435                if entries.contains_key(name) {
436                    self.inodes.write().remove(&new_ino);
437                    return Err(FsError::AlreadyExists);
438                }
439                entries.insert(name.to_string(), new_ino);
440                drop(guard);
441                self.stat(new_ino)
442            }
443            _ => {
444                self.inodes.write().remove(&new_ino);
445                Err(FsError::NotADirectory)
446            }
447        }
448    }
449
450    /// Implements unlink.
451    fn unlink(&self, parent_ino: u64, name: &str, target_ino: u64) -> FsResult<()> {
452        let parent = self.get_node(parent_ino)?;
453        let child_ino = {
454            let guard = parent.node.lock();
455            match &*guard {
456                RamNode::Directory { entries, .. } => {
457                    *entries.get(name).ok_or(FsError::NotFound)?
458                }
459                _ => return Err(FsError::NotADirectory),
460            }
461        };
462
463        if child_ino != target_ino {
464            return Err(FsError::InvalidArgument);
465        }
466
467        let node = self.get_node(child_ino)?;
468        {
469            let node_guard = node.node.lock();
470            if let RamNode::Directory {
471                entries: child_entries,
472                ..
473            } = &*node_guard
474            {
475                if !child_entries.is_empty() {
476                    return Err(FsError::NotEmpty);
477                }
478            }
479        }
480
481        let mut guard = parent.node.lock();
482        match &mut *guard {
483            RamNode::Directory { entries, .. } => {
484                let current = *entries.get(name).ok_or(FsError::NotFound)?;
485                if current != child_ino {
486                    return Err(FsError::InvalidArgument);
487                }
488                entries.remove(name);
489                drop(guard);
490                self.collect_if_detached(child_ino)?;
491                Ok(())
492            }
493            _ => Err(FsError::NotADirectory),
494        }
495    }
496
497    /// Implements rename.
498    fn rename(
499        &self,
500        old_parent: u64,
501        old_name: &str,
502        new_parent: u64,
503        new_name: &str,
504        flags: RenameFlags,
505    ) -> FsResult<()> {
506        if old_parent == new_parent && old_name == new_name {
507            return Ok(());
508        }
509        if flags.exchange {
510            return Err(FsError::NotSupported);
511        }
512        if flags.no_replace && flags.replace_if_exists {
513            return Err(FsError::InvalidArgument);
514        }
515
516        let old_parent_node = self.get_node(old_parent)?;
517        let moved_ino = {
518            let guard = old_parent_node.node.lock();
519            match &*guard {
520                RamNode::Directory { entries, .. } => {
521                    *entries.get(old_name).ok_or(FsError::NotFound)?
522                }
523                _ => return Err(FsError::NotADirectory),
524            }
525        };
526
527        {
528            let moved_node = self.get_node(moved_ino)?;
529            let moved_guard = moved_node.node.lock();
530            if let RamNode::Directory { .. } = &*moved_guard {
531                if self.directory_contains_inode(moved_ino, new_parent)? {
532                    return Err(FsError::InvalidArgument);
533                }
534            }
535        }
536
537        let new_parent_node = self.get_node(new_parent)?;
538        let mut new_guard = new_parent_node.node.lock();
539        let replaced_ino = match &mut *new_guard {
540            RamNode::Directory { entries, .. } => {
541                if let Some(&existing) = entries.get(new_name) {
542                    if flags.no_replace {
543                        return Err(FsError::AlreadyExists);
544                    }
545                    if !flags.replace_if_exists && existing != moved_ino {
546                        return Err(FsError::AlreadyExists);
547                    }
548                    Some(existing)
549                } else {
550                    None
551                }
552            }
553            _ => return Err(FsError::NotADirectory),
554        };
555        drop(new_guard);
556
557        if let Some(existing_ino) = replaced_ino {
558            let existing_node = self.get_node(existing_ino)?;
559            let existing_guard = existing_node.node.lock();
560            if let RamNode::Directory {
561                entries: child_entries,
562                ..
563            } = &*existing_guard
564            {
565                if !child_entries.is_empty() {
566                    return Err(FsError::NotEmpty);
567                }
568            }
569        }
570
571        {
572            let mut guard = old_parent_node.node.lock();
573            match &mut *guard {
574                RamNode::Directory { entries, .. } => {
575                    let current = *entries.get(old_name).ok_or(FsError::NotFound)?;
576                    if current != moved_ino {
577                        return Err(FsError::InvalidArgument);
578                    }
579                    entries.remove(old_name);
580                }
581                _ => return Err(FsError::NotADirectory),
582            }
583        }
584
585        let mut guard = new_parent_node.node.lock();
586        match &mut *guard {
587            RamNode::Directory { entries, .. } => {
588                let mut replaced_for_gc: Option<u64> = None;
589                if let Some(existing_ino) = entries.insert(new_name.to_string(), moved_ino) {
590                    if existing_ino != moved_ino {
591                        replaced_for_gc = Some(existing_ino);
592                    }
593                }
594                drop(guard);
595                if let Some(existing_ino) = replaced_for_gc {
596                    self.collect_if_detached(existing_ino)?;
597                }
598                Ok(())
599            }
600            _ => Err(FsError::NotADirectory),
601        }
602    }
603
604    /// Sets size.
605    fn set_size(&self, ino: u64, size: u64) -> FsResult<()> {
606        let node = self.get_node(ino)?;
607        let mut guard = node.node.lock();
608        match &mut *guard {
609            RamNode::File { data, .. } => {
610                let new_size = usize::try_from(size).map_err(|_| FsError::FileTooLarge)?;
611                data.resize(new_size, 0);
612                Ok(())
613            }
614            _ => Err(FsError::IsADirectory),
615        }
616    }
617
618    /// Sets times.
619    fn set_times(
620        &self,
621        ino: u64,
622        _atime: Option<VfsTimestamp>,
623        _mtime: Option<VfsTimestamp>,
624    ) -> FsResult<()> {
625        let _ = self.get_node(ino)?;
626        Ok(())
627    }
628
629    /// Implements readlink.
630    fn readlink(&self, _ino: u64) -> FsResult<String> {
631        Err(FsError::NotSupported)
632    }
633
634    /// Implements invalidate inode.
635    fn invalidate_inode(&self, _ino: u64) {}
636    /// Implements invalidate all caches.
637    fn invalidate_all_caches(&self) {}
638}
639
640/// Implements split path.
641pub fn split_path(path: &str) -> (&str, &str) {
642    let path = path.trim_end_matches('/');
643    if let Some(idx) = path.rfind('/') {
644        if idx == 0 {
645            ("/", &path[1..])
646        } else {
647            (&path[..idx], &path[idx + 1..])
648        }
649    } else {
650        ("/", path)
651    }
652}