⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/
tmpfs.rs

1//! tmpfs -- Memory-Backed Filesystem
2//!
3//! A temporary in-memory filesystem with configurable size limits.
4//! Data is stored in heap-backed buffers and is lost on unmount.
5//! Supports all standard VFS operations including symlinks.
6
7use alloc::{collections::BTreeMap, string::String, sync::Arc, vec, vec::Vec};
8use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
9
10#[cfg(not(target_arch = "aarch64"))]
11use spin::RwLock;
12
13#[cfg(target_arch = "aarch64")]
14use super::bare_lock::RwLock;
15use super::{DirEntry, Filesystem, Metadata, NodeType, Permissions, VfsNode};
16use crate::error::{FsError, KernelError};
17
18/// Global inode counter for tmpfs (separate from RamFS)
19static TMPFS_NEXT_INODE: AtomicU64 = AtomicU64::new(1);
20
21/// tmpfs node
22struct TmpNode {
23    node_type: NodeType,
24    data: RwLock<Vec<u8>>,
25    children: RwLock<BTreeMap<String, Arc<TmpNode>>>,
26    metadata: RwLock<Metadata>,
27    inode: u64,
28    parent_inode: u64,
29    /// Shared reference to the filesystem's total bytes used counter
30    bytes_used: Arc<AtomicUsize>,
31    /// Shared reference to the filesystem's size limit
32    size_limit: usize,
33}
34
35impl TmpNode {
36    fn new_file(
37        inode: u64,
38        parent_inode: u64,
39        permissions: Permissions,
40        bytes_used: Arc<AtomicUsize>,
41        size_limit: usize,
42    ) -> Self {
43        Self {
44            node_type: NodeType::File,
45            data: RwLock::new(Vec::new()),
46            children: RwLock::new(BTreeMap::new()),
47            metadata: RwLock::new(Metadata {
48                node_type: NodeType::File,
49                size: 0,
50                permissions,
51                uid: 0,
52                gid: 0,
53                created: crate::arch::timer::get_timestamp_secs(),
54                modified: crate::arch::timer::get_timestamp_secs(),
55                accessed: crate::arch::timer::get_timestamp_secs(),
56                inode,
57            }),
58            inode,
59            parent_inode,
60            bytes_used,
61            size_limit,
62        }
63    }
64
65    fn new_directory(
66        inode: u64,
67        parent_inode: u64,
68        permissions: Permissions,
69        bytes_used: Arc<AtomicUsize>,
70        size_limit: usize,
71    ) -> Self {
72        Self {
73            node_type: NodeType::Directory,
74            data: RwLock::new(Vec::new()),
75            children: RwLock::new(BTreeMap::new()),
76            metadata: RwLock::new(Metadata {
77                node_type: NodeType::Directory,
78                size: 0,
79                permissions,
80                uid: 0,
81                gid: 0,
82                created: crate::arch::timer::get_timestamp_secs(),
83                modified: crate::arch::timer::get_timestamp_secs(),
84                accessed: crate::arch::timer::get_timestamp_secs(),
85                inode,
86            }),
87            inode,
88            parent_inode,
89            bytes_used,
90            size_limit,
91        }
92    }
93
94    fn new_symlink(
95        inode: u64,
96        parent_inode: u64,
97        target: &str,
98        bytes_used: Arc<AtomicUsize>,
99        size_limit: usize,
100    ) -> Self {
101        let target_bytes = Vec::from(target.as_bytes());
102        let size = target_bytes.len();
103        Self {
104            node_type: NodeType::Symlink,
105            data: RwLock::new(target_bytes),
106            children: RwLock::new(BTreeMap::new()),
107            metadata: RwLock::new(Metadata {
108                node_type: NodeType::Symlink,
109                size,
110                permissions: Permissions::from_mode(0o777),
111                uid: 0,
112                gid: 0,
113                created: crate::arch::timer::get_timestamp_secs(),
114                modified: crate::arch::timer::get_timestamp_secs(),
115                accessed: crate::arch::timer::get_timestamp_secs(),
116                inode,
117            }),
118            inode,
119            parent_inode,
120            bytes_used,
121            size_limit,
122        }
123    }
124
125    /// Check if writing `additional` bytes would exceed the size limit.
126    /// Returns Ok(()) if within limits, Err if exceeded.
127    fn check_space(&self, additional: usize) -> Result<(), KernelError> {
128        if self.size_limit == 0 {
129            return Ok(()); // No limit
130        }
131        let current = self.bytes_used.load(Ordering::Relaxed);
132        if current.saturating_add(additional) > self.size_limit {
133            return Err(KernelError::FsError(FsError::NoSpace));
134        }
135        Ok(())
136    }
137}
138
139impl VfsNode for TmpNode {
140    fn node_type(&self) -> NodeType {
141        self.node_type
142    }
143
144    fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
145        if self.node_type != NodeType::File {
146            return Err(KernelError::FsError(FsError::NotAFile));
147        }
148
149        let data = self.data.read();
150        if offset >= data.len() {
151            return Ok(0);
152        }
153
154        let bytes_to_read = core::cmp::min(buffer.len(), data.len() - offset);
155        buffer[..bytes_to_read].copy_from_slice(&data[offset..offset + bytes_to_read]);
156
157        self.metadata.write().accessed = crate::arch::timer::get_timestamp_secs();
158
159        Ok(bytes_to_read)
160    }
161
162    fn write(&self, offset: usize, data: &[u8]) -> Result<usize, KernelError> {
163        if self.node_type != NodeType::File {
164            return Err(KernelError::FsError(FsError::NotAFile));
165        }
166
167        let mut file_data = self.data.write();
168        let old_len = file_data.len();
169
170        // Calculate new size after write
171        let new_len = core::cmp::max(old_len, offset + data.len());
172        let growth = new_len.saturating_sub(old_len);
173
174        // Check space before expanding
175        if growth > 0 {
176            self.check_space(growth)?;
177        }
178
179        // Extend file if necessary
180        if offset > file_data.len() {
181            file_data.resize(offset, 0);
182        }
183        if offset + data.len() > file_data.len() {
184            file_data.resize(offset + data.len(), 0);
185        }
186        file_data[offset..offset + data.len()].copy_from_slice(data);
187
188        // Update bytes used counter
189        if growth > 0 {
190            self.bytes_used.fetch_add(growth, Ordering::Relaxed);
191        }
192
193        let mut metadata = self.metadata.write();
194        metadata.size = file_data.len();
195        metadata.modified = crate::arch::timer::get_timestamp_secs();
196
197        Ok(data.len())
198    }
199
200    fn metadata(&self) -> Result<Metadata, KernelError> {
201        Ok(self.metadata.read().clone())
202    }
203
204    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
205        if self.node_type != NodeType::Directory {
206            return Err(KernelError::FsError(FsError::NotADirectory));
207        }
208
209        let children = self.children.read();
210        let mut entries = Vec::new();
211
212        entries.push(DirEntry {
213            name: String::from("."),
214            node_type: NodeType::Directory,
215            inode: self.inode,
216        });
217        entries.push(DirEntry {
218            name: String::from(".."),
219            node_type: NodeType::Directory,
220            inode: self.parent_inode,
221        });
222
223        for (name, child) in children.iter() {
224            entries.push(DirEntry {
225                name: name.clone(),
226                node_type: child.node_type,
227                inode: child.inode,
228            });
229        }
230
231        Ok(entries)
232    }
233
234    fn lookup(&self, name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
235        if self.node_type != NodeType::Directory {
236            return Err(KernelError::FsError(FsError::NotADirectory));
237        }
238
239        let children = self.children.read();
240        children
241            .get(name)
242            .map(|node| node.clone() as Arc<dyn VfsNode>)
243            .ok_or(KernelError::FsError(FsError::NotFound))
244    }
245
246    fn create(
247        &self,
248        name: &str,
249        permissions: Permissions,
250    ) -> Result<Arc<dyn VfsNode>, KernelError> {
251        if self.node_type != NodeType::Directory {
252            return Err(KernelError::FsError(FsError::NotADirectory));
253        }
254
255        let mut children = self.children.write();
256        if children.contains_key(name) {
257            return Err(KernelError::FsError(FsError::AlreadyExists));
258        }
259
260        let inode = TMPFS_NEXT_INODE.fetch_add(1, Ordering::Relaxed);
261        let new_file = Arc::new(TmpNode::new_file(
262            inode,
263            self.inode,
264            permissions,
265            self.bytes_used.clone(),
266            self.size_limit,
267        ));
268        children.insert(String::from(name), new_file.clone());
269
270        Ok(new_file as Arc<dyn VfsNode>)
271    }
272
273    fn mkdir(&self, name: &str, permissions: Permissions) -> Result<Arc<dyn VfsNode>, KernelError> {
274        if self.node_type != NodeType::Directory {
275            return Err(KernelError::FsError(FsError::NotADirectory));
276        }
277
278        let mut children = self.children.write();
279        if children.contains_key(name) {
280            return Err(KernelError::FsError(FsError::AlreadyExists));
281        }
282
283        let inode = TMPFS_NEXT_INODE.fetch_add(1, Ordering::Relaxed);
284        let new_dir = Arc::new(TmpNode::new_directory(
285            inode,
286            self.inode,
287            permissions,
288            self.bytes_used.clone(),
289            self.size_limit,
290        ));
291        children.insert(String::from(name), new_dir.clone());
292
293        Ok(new_dir as Arc<dyn VfsNode>)
294    }
295
296    fn unlink(&self, name: &str) -> Result<(), KernelError> {
297        if self.node_type != NodeType::Directory {
298            return Err(KernelError::FsError(FsError::NotADirectory));
299        }
300
301        let mut children = self.children.write();
302
303        if let Some(node) = children.get(name) {
304            if node.node_type == NodeType::Directory {
305                let dir_children = node.children.read();
306                if !dir_children.is_empty() {
307                    return Err(KernelError::FsError(FsError::DirectoryNotEmpty));
308                }
309            }
310
311            // Reclaim bytes if it's a file
312            if node.node_type == NodeType::File {
313                let data = node.data.read();
314                let freed = data.len();
315                if freed > 0 {
316                    // Saturating sub to avoid underflow on race
317                    let prev = self.bytes_used.fetch_sub(freed, Ordering::Relaxed);
318                    // Guard against underflow (should not happen but be safe)
319                    if prev < freed {
320                        self.bytes_used.store(0, Ordering::Relaxed);
321                    }
322                }
323            }
324
325            children.remove(name);
326            Ok(())
327        } else {
328            Err(KernelError::FsError(FsError::NotFound))
329        }
330    }
331
332    fn truncate(&self, size: usize) -> Result<(), KernelError> {
333        if self.node_type != NodeType::File {
334            return Err(KernelError::FsError(FsError::NotAFile));
335        }
336
337        let mut data = self.data.write();
338        let old_len = data.len();
339
340        if size > old_len {
341            // Growing: check space
342            let growth = size - old_len;
343            self.check_space(growth)?;
344            data.resize(size, 0);
345            self.bytes_used.fetch_add(growth, Ordering::Relaxed);
346        } else if size < old_len {
347            // Shrinking: reclaim space
348            let freed = old_len - size;
349            data.truncate(size);
350            let prev = self.bytes_used.fetch_sub(freed, Ordering::Relaxed);
351            if prev < freed {
352                self.bytes_used.store(0, Ordering::Relaxed);
353            }
354        }
355
356        let mut metadata = self.metadata.write();
357        metadata.size = size;
358        metadata.modified = crate::arch::timer::get_timestamp_secs();
359
360        Ok(())
361    }
362
363    fn link(&self, name: &str, target: Arc<dyn VfsNode>) -> Result<(), KernelError> {
364        if self.node_type != NodeType::Directory {
365            return Err(KernelError::FsError(FsError::NotADirectory));
366        }
367
368        if target.node_type() == NodeType::Directory {
369            return Err(KernelError::FsError(FsError::IsADirectory));
370        }
371
372        let mut children = self.children.write();
373        if children.contains_key(name) {
374            return Err(KernelError::FsError(FsError::AlreadyExists));
375        }
376
377        // Copy data for hard link (same pattern as RamFS)
378        let target_meta = target.metadata()?;
379        let inode = target_meta.inode;
380
381        let new_node = Arc::new(TmpNode::new_file(
382            inode,
383            self.inode,
384            target_meta.permissions,
385            self.bytes_used.clone(),
386            self.size_limit,
387        ));
388
389        let mut buf = vec![0u8; target_meta.size];
390        if !buf.is_empty() {
391            let bytes_read = target.read(0, &mut buf)?;
392            buf.truncate(bytes_read);
393        }
394        if !buf.is_empty() {
395            new_node.write(0, &buf)?;
396        }
397
398        children.insert(String::from(name), new_node);
399        Ok(())
400    }
401
402    fn symlink(&self, name: &str, target: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
403        if self.node_type != NodeType::Directory {
404            return Err(KernelError::FsError(FsError::NotADirectory));
405        }
406
407        let mut children = self.children.write();
408        if children.contains_key(name) {
409            return Err(KernelError::FsError(FsError::AlreadyExists));
410        }
411
412        let inode = TMPFS_NEXT_INODE.fetch_add(1, Ordering::Relaxed);
413        let new_symlink = Arc::new(TmpNode::new_symlink(
414            inode,
415            self.inode,
416            target,
417            self.bytes_used.clone(),
418            self.size_limit,
419        ));
420        children.insert(String::from(name), new_symlink.clone());
421
422        Ok(new_symlink as Arc<dyn VfsNode>)
423    }
424
425    fn readlink(&self) -> Result<String, KernelError> {
426        if self.node_type != NodeType::Symlink {
427            return Err(KernelError::FsError(FsError::NotASymlink));
428        }
429
430        let data = self.data.read();
431        let s =
432            core::str::from_utf8(&data).map_err(|_| KernelError::FsError(FsError::InvalidPath))?;
433        Ok(String::from(s))
434    }
435
436    fn chmod(&self, permissions: Permissions) -> Result<(), KernelError> {
437        let mut metadata = self.metadata.write();
438        metadata.permissions = permissions;
439        metadata.modified = crate::arch::timer::get_timestamp_secs();
440        Ok(())
441    }
442}
443
444/// tmpfs filesystem instance
445pub struct TmpFs {
446    root: Arc<TmpNode>,
447    /// Total bytes used across all files
448    bytes_used: Arc<AtomicUsize>,
449    /// Maximum bytes allowed (0 = unlimited)
450    size_limit: usize,
451}
452
453impl TmpFs {
454    /// Create a new tmpfs with the given size limit in bytes.
455    /// Pass 0 for unlimited.
456    pub fn new(size_limit: usize) -> Self {
457        let bytes_used = Arc::new(AtomicUsize::new(0));
458        let root_inode = TMPFS_NEXT_INODE.fetch_add(1, Ordering::Relaxed);
459        let root = Arc::new(TmpNode::new_directory(
460            root_inode,
461            root_inode,
462            Permissions::default(),
463            bytes_used.clone(),
464            size_limit,
465        ));
466
467        Self {
468            root,
469            bytes_used,
470            size_limit,
471        }
472    }
473
474    /// Get current bytes used
475    pub fn bytes_used(&self) -> usize {
476        self.bytes_used.load(Ordering::Relaxed)
477    }
478
479    /// Get size limit (0 = unlimited)
480    pub fn size_limit(&self) -> usize {
481        self.size_limit
482    }
483}
484
485impl Default for TmpFs {
486    fn default() -> Self {
487        // Default: 128MB limit
488        Self::new(128 * 1024 * 1024)
489    }
490}
491
492impl Filesystem for TmpFs {
493    fn root(&self) -> Arc<dyn VfsNode> {
494        self.root.clone() as Arc<dyn VfsNode>
495    }
496
497    fn name(&self) -> &str {
498        "tmpfs"
499    }
500
501    fn is_readonly(&self) -> bool {
502        false
503    }
504
505    fn sync(&self) -> Result<(), KernelError> {
506        // tmpfs is memory-backed, nothing to sync
507        Ok(())
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use alloc::vec;
514
515    use super::*;
516
517    #[test]
518    fn test_tmpfs_new() {
519        let fs = TmpFs::new(1024 * 1024);
520        assert_eq!(fs.name(), "tmpfs");
521        assert!(!fs.is_readonly());
522        assert_eq!(fs.size_limit(), 1024 * 1024);
523        assert_eq!(fs.bytes_used(), 0);
524    }
525
526    #[test]
527    fn test_tmpfs_default() {
528        let fs = TmpFs::default();
529        assert_eq!(fs.size_limit(), 128 * 1024 * 1024);
530    }
531
532    #[test]
533    fn test_tmpfs_root_is_directory() {
534        let fs = TmpFs::new(0);
535        let root = fs.root();
536        assert_eq!(root.node_type(), NodeType::Directory);
537    }
538
539    #[test]
540    fn test_tmpfs_sync() {
541        let fs = TmpFs::new(0);
542        assert!(fs.sync().is_ok());
543    }
544
545    #[test]
546    fn test_create_and_read_file() {
547        let fs = TmpFs::new(0);
548        let root = fs.root();
549
550        let file = root.create("test.txt", Permissions::default()).unwrap();
551        assert_eq!(file.node_type(), NodeType::File);
552
553        file.write(0, b"Hello, tmpfs!").unwrap();
554        assert_eq!(fs.bytes_used(), 13);
555
556        let mut buf = vec![0u8; 20];
557        let n = file.read(0, &mut buf).unwrap();
558        assert_eq!(n, 13);
559        assert_eq!(&buf[..13], b"Hello, tmpfs!");
560    }
561
562    #[test]
563    fn test_size_limit_enforcement() {
564        let fs = TmpFs::new(10); // 10 byte limit
565        let root = fs.root();
566
567        let file = root.create("big.txt", Permissions::default()).unwrap();
568
569        // Write 8 bytes -- should succeed
570        assert!(file.write(0, b"12345678").is_ok());
571        assert_eq!(fs.bytes_used(), 8);
572
573        // Write 5 more bytes (would total 13) -- should fail
574        let result = file.write(8, b"abcde");
575        assert!(result.is_err());
576        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NoSpace));
577
578        // Bytes used should still be 8
579        assert_eq!(fs.bytes_used(), 8);
580    }
581
582    #[test]
583    fn test_truncate_reclaims_space() {
584        let fs = TmpFs::new(100);
585        let root = fs.root();
586
587        let file = root.create("shrink.txt", Permissions::default()).unwrap();
588        file.write(0, b"0123456789").unwrap();
589        assert_eq!(fs.bytes_used(), 10);
590
591        file.truncate(3).unwrap();
592        assert_eq!(fs.bytes_used(), 3);
593
594        let meta = file.metadata().unwrap();
595        assert_eq!(meta.size, 3);
596    }
597
598    #[test]
599    fn test_truncate_grow_checks_space() {
600        let fs = TmpFs::new(10);
601        let root = fs.root();
602
603        let file = root.create("grow.txt", Permissions::default()).unwrap();
604        file.write(0, b"12345").unwrap();
605        assert_eq!(fs.bytes_used(), 5);
606
607        // Growing to 10 should work
608        assert!(file.truncate(10).is_ok());
609        assert_eq!(fs.bytes_used(), 10);
610
611        // Growing to 11 should fail
612        let result = file.truncate(11);
613        assert!(result.is_err());
614    }
615
616    #[test]
617    fn test_unlink_reclaims_space() {
618        let fs = TmpFs::new(100);
619        let root = fs.root();
620
621        let file = root.create("temp.txt", Permissions::default()).unwrap();
622        file.write(0, b"some data").unwrap();
623        assert_eq!(fs.bytes_used(), 9);
624
625        root.unlink("temp.txt").unwrap();
626        assert_eq!(fs.bytes_used(), 0);
627    }
628
629    #[test]
630    fn test_mkdir_and_readdir() {
631        let fs = TmpFs::new(0);
632        let root = fs.root();
633
634        root.mkdir("subdir", Permissions::default()).unwrap();
635        root.create("file.txt", Permissions::default()).unwrap();
636
637        let entries = root.readdir().unwrap();
638        assert_eq!(entries.len(), 4); // . .. subdir file.txt
639
640        let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
641        assert!(names.contains(&"."));
642        assert!(names.contains(&".."));
643        assert!(names.contains(&"subdir"));
644        assert!(names.contains(&"file.txt"));
645    }
646
647    #[test]
648    fn test_nested_directories() {
649        let fs = TmpFs::new(0);
650        let root = fs.root();
651
652        let sub = root.mkdir("a", Permissions::default()).unwrap();
653        let subsub = sub.mkdir("b", Permissions::default()).unwrap();
654        let file = subsub.create("c.txt", Permissions::default()).unwrap();
655        file.write(0, b"nested").unwrap();
656
657        // Lookup chain
658        let a = root.lookup("a").unwrap();
659        let b = a.lookup("b").unwrap();
660        let c = b.lookup("c.txt").unwrap();
661
662        let mut buf = vec![0u8; 10];
663        let n = c.read(0, &mut buf).unwrap();
664        assert_eq!(&buf[..n], b"nested");
665    }
666
667    #[test]
668    fn test_symlink() {
669        let fs = TmpFs::new(0);
670        let root = fs.root();
671
672        let link = root.symlink("mylink", "/tmp/target").unwrap();
673        assert_eq!(link.node_type(), NodeType::Symlink);
674
675        let target = link.readlink().unwrap();
676        assert_eq!(target, "/tmp/target");
677    }
678
679    #[test]
680    fn test_unlink_nonempty_dir_fails() {
681        let fs = TmpFs::new(0);
682        let root = fs.root();
683
684        let dir = root.mkdir("occupied", Permissions::default()).unwrap();
685        dir.create("child", Permissions::default()).unwrap();
686
687        let result = root.unlink("occupied");
688        assert!(result.is_err());
689        assert_eq!(
690            result.unwrap_err(),
691            KernelError::FsError(FsError::DirectoryNotEmpty)
692        );
693    }
694
695    #[test]
696    fn test_chmod() {
697        let fs = TmpFs::new(0);
698        let root = fs.root();
699
700        let file = root.create("perm.txt", Permissions::default()).unwrap();
701        let ro = Permissions::read_only();
702        assert!(file.chmod(ro).is_ok());
703
704        let meta = file.metadata().unwrap();
705        assert!(meta.permissions.owner_read);
706        assert!(!meta.permissions.owner_write);
707    }
708
709    #[test]
710    fn test_multiple_files_share_limit() {
711        let fs = TmpFs::new(20);
712        let root = fs.root();
713
714        let f1 = root.create("a.txt", Permissions::default()).unwrap();
715        let f2 = root.create("b.txt", Permissions::default()).unwrap();
716
717        f1.write(0, b"1234567890").unwrap(); // 10 bytes
718        f2.write(0, b"1234567890").unwrap(); // 10 bytes, total 20
719
720        // One more byte should fail
721        let result = f1.write(10, b"x");
722        assert!(result.is_err());
723    }
724}