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};
6use std::time::SystemTime;
7
8/// Metadata about a file or directory.
9#[derive(Debug, Clone)]
10pub struct Metadata {
11    /// True if this is a directory.
12    pub is_dir: bool,
13    /// True if this is a file.
14    pub is_file: bool,
15    /// True if this is a symbolic link.
16    pub is_symlink: bool,
17    /// Size in bytes (0 for directories).
18    pub size: u64,
19    /// Last modification time, if available.
20    pub modified: Option<SystemTime>,
21}
22
23/// Type of directory entry.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum EntryType {
26    File,
27    Directory,
28    Symlink,
29}
30
31/// A directory entry returned by `list`.
32#[derive(Debug, Clone)]
33pub struct DirEntry {
34    /// Name of the entry (not full path).
35    pub name: String,
36    /// Type of entry.
37    pub entry_type: EntryType,
38    /// Size in bytes (0 for directories).
39    pub size: u64,
40    /// For symlinks, the target path.
41    pub symlink_target: Option<PathBuf>,
42}
43
44/// Abstract filesystem interface.
45///
46/// All operations use paths relative to the filesystem root.
47/// For example, if a `LocalFs` is rooted at `/home/amy/project`,
48/// then `read("src/main.rs")` reads `/home/amy/project/src/main.rs`.
49#[async_trait]
50pub trait Filesystem: Send + Sync {
51    /// Read the entire contents of a file.
52    async fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
53
54    /// Write data to a file, creating it if it doesn't exist.
55    ///
56    /// Returns `Err` if the filesystem is read-only.
57    async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()>;
58
59    /// List entries in a directory.
60    async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>>;
61
62    /// Get metadata for a file or directory.
63    async fn stat(&self, path: &Path) -> io::Result<Metadata>;
64
65    /// Create a directory (and parent directories if needed).
66    ///
67    /// Returns `Err` if the filesystem is read-only.
68    async fn mkdir(&self, path: &Path) -> io::Result<()>;
69
70    /// Remove a file or empty directory.
71    ///
72    /// Returns `Err` if the filesystem is read-only.
73    async fn remove(&self, path: &Path) -> io::Result<()>;
74
75    /// Returns true if this filesystem is read-only.
76    fn read_only(&self) -> bool;
77
78    /// Check if a path exists.
79    async fn exists(&self, path: &Path) -> bool {
80        self.stat(path).await.is_ok()
81    }
82
83    /// Rename (move) a file or directory.
84    ///
85    /// This is an atomic operation when source and destination are on the same
86    /// filesystem. The default implementation falls back to copy+delete, which
87    /// is not atomic.
88    ///
89    /// Returns `Err` if the filesystem is read-only.
90    async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
91        // Default implementation: copy then delete (not atomic)
92        let meta = self.stat(from).await?;
93        if meta.is_dir {
94            // For directories, we'd need recursive copy - just error for now
95            return Err(io::Error::new(
96                io::ErrorKind::Unsupported,
97                "rename directories not supported by this filesystem",
98            ));
99        }
100        let data = self.read(from).await?;
101        self.write(to, &data).await?;
102        self.remove(from).await?;
103        Ok(())
104    }
105
106    /// Get the real filesystem path for a VFS path.
107    ///
108    /// Returns `Some(path)` for backends backed by the real filesystem (like LocalFs),
109    /// or `None` for virtual backends (like MemoryFs).
110    ///
111    /// This is needed for tools like `git` that must use real paths with external libraries.
112    fn real_path(&self, path: &Path) -> Option<PathBuf> {
113        let _ = path;
114        None
115    }
116
117    /// Read the target of a symbolic link without following it.
118    ///
119    /// Returns the path the symlink points to. Use `stat` to follow symlinks.
120    async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
121        let _ = path;
122        Err(io::Error::new(
123            io::ErrorKind::InvalidInput,
124            "symlinks not supported by this filesystem",
125        ))
126    }
127
128    /// Create a symbolic link.
129    ///
130    /// Creates a symlink at `link` pointing to `target`. The target path
131    /// is stored as-is (may be relative or absolute).
132    async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
133        let _ = (target, link);
134        Err(io::Error::new(
135            io::ErrorKind::InvalidInput,
136            "symlinks not supported by this filesystem",
137        ))
138    }
139
140    /// Get metadata for a path without following symlinks.
141    ///
142    /// Unlike `stat`, this returns metadata about the symlink itself,
143    /// not the target it points to.
144    async fn lstat(&self, path: &Path) -> io::Result<Metadata> {
145        // Default: same as stat (for backends that don't support symlinks)
146        self.stat(path).await
147    }
148}