Skip to main content

kaish_kernel/vfs/
traits.rs

1//! Core VFS traits and types.
2
3use async_trait::async_trait;
4use std::io;
5use std::path::{Path, PathBuf};
6
7// DirEntry and DirEntryKind live in kaish-types.
8pub use kaish_types::{DirEntry, DirEntryKind};
9
10/// Abstract filesystem interface.
11///
12/// All operations use paths relative to the filesystem root.
13/// For example, if a `LocalFs` is rooted at `/home/amy/project`,
14/// then `read("src/main.rs")` reads `/home/amy/project/src/main.rs`.
15#[async_trait]
16pub trait Filesystem: Send + Sync {
17    /// Read the entire contents of a file.
18    async fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
19
20    /// Write data to a file, creating it if it doesn't exist.
21    ///
22    /// Returns `Err` if the filesystem is read-only.
23    async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()>;
24
25    /// List entries in a directory.
26    async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>>;
27
28    /// Get metadata for a file or directory.
29    async fn stat(&self, path: &Path) -> io::Result<DirEntry>;
30
31    /// Create a directory (and parent directories if needed).
32    ///
33    /// Returns `Err` if the filesystem is read-only.
34    async fn mkdir(&self, path: &Path) -> io::Result<()>;
35
36    /// Remove a file or empty directory.
37    ///
38    /// Returns `Err` if the filesystem is read-only.
39    async fn remove(&self, path: &Path) -> io::Result<()>;
40
41    /// Returns true if this filesystem is read-only.
42    fn read_only(&self) -> bool;
43
44    /// Check if a path exists.
45    async fn exists(&self, path: &Path) -> bool {
46        self.stat(path).await.is_ok()
47    }
48
49    /// Rename (move) a file or directory.
50    ///
51    /// This is an atomic operation when source and destination are on the same
52    /// filesystem. The default implementation falls back to copy+delete, which
53    /// is not atomic.
54    ///
55    /// Returns `Err` if the filesystem is read-only.
56    async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
57        // Default implementation: copy then delete (not atomic)
58        let entry = self.stat(from).await?;
59        if entry.is_dir() {
60            // For directories, we'd need recursive copy - just error for now
61            return Err(io::Error::new(
62                io::ErrorKind::Unsupported,
63                "rename directories not supported by this filesystem",
64            ));
65        }
66        let data = self.read(from).await?;
67        self.write(to, &data).await?;
68        self.remove(from).await?;
69        Ok(())
70    }
71
72    /// Get the real filesystem path for a VFS path.
73    ///
74    /// Returns `Some(path)` for backends backed by the real filesystem (like LocalFs),
75    /// or `None` for virtual backends (like MemoryFs).
76    ///
77    /// This is needed for tools like `git` that must use real paths with external libraries.
78    fn real_path(&self, path: &Path) -> Option<PathBuf> {
79        let _ = path;
80        None
81    }
82
83    /// Read the target of a symbolic link without following it.
84    ///
85    /// Returns the path the symlink points to. Use `stat` to follow symlinks.
86    async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
87        let _ = path;
88        Err(io::Error::new(
89            io::ErrorKind::InvalidInput,
90            "symlinks not supported by this filesystem",
91        ))
92    }
93
94    /// Create a symbolic link.
95    ///
96    /// Creates a symlink at `link` pointing to `target`. The target path
97    /// is stored as-is (may be relative or absolute).
98    async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
99        let _ = (target, link);
100        Err(io::Error::new(
101            io::ErrorKind::InvalidInput,
102            "symlinks not supported by this filesystem",
103        ))
104    }
105
106    /// Get metadata for a path without following symlinks.
107    ///
108    /// Unlike `stat`, this returns metadata about the symlink itself,
109    /// not the target it points to.
110    async fn lstat(&self, path: &Path) -> io::Result<DirEntry> {
111        // Default: same as stat (for backends that don't support symlinks)
112        self.stat(path).await
113    }
114}