dodot_lib/fs/mod.rs
1mod os;
2
3pub use os::OsFs;
4
5use std::path::{Path, PathBuf};
6
7use crate::Result;
8
9/// Metadata about a filesystem entry.
10#[derive(Debug, Clone)]
11pub struct FsMetadata {
12 pub is_file: bool,
13 pub is_dir: bool,
14 pub is_symlink: bool,
15 pub len: u64,
16}
17
18/// A single directory entry returned by [`Fs::read_dir`].
19#[derive(Debug, Clone)]
20pub struct DirEntry {
21 pub path: PathBuf,
22 pub name: String,
23 pub is_dir: bool,
24 pub is_file: bool,
25 pub is_symlink: bool,
26}
27
28/// Filesystem abstraction.
29///
30/// All dodot code accesses the filesystem through this trait so that:
31/// - Tests can use isolated temp directories with a real implementation
32/// - Every `io::Error` is wrapped with the path that caused it
33///
34/// Use `&dyn Fs` (trait objects) throughout the codebase. The operations
35/// are I/O-bound so dynamic dispatch costs nothing meaningful, and generics
36/// would infect every type signature.
37pub trait Fs: Send + Sync {
38 /// Returns metadata for the path, following symlinks.
39 fn stat(&self, path: &Path) -> Result<FsMetadata>;
40
41 /// Returns metadata for the path without following symlinks.
42 fn lstat(&self, path: &Path) -> Result<FsMetadata>;
43
44 /// Opens the file for reading in a streaming fashion.
45 ///
46 /// Errors that occur while opening the file are returned through this
47 /// method's [`Result`] and include path context. Once opened, the
48 /// returned reader is a raw [`std::io::Read`], so any later `read()`
49 /// errors are reported as plain [`std::io::Error`] values and are not
50 /// automatically wrapped with the path.
51 fn open_read(&self, path: &Path) -> Result<Box<dyn std::io::Read + Send + Sync>>;
52
53 /// Reads the entire file into bytes.
54 fn read_file(&self, path: &Path) -> Result<Vec<u8>>;
55
56 /// Reads the entire file as a UTF-8 string.
57 fn read_to_string(&self, path: &Path) -> Result<String>;
58
59 /// Writes `contents` to `path`, creating or truncating the file.
60 fn write_file(&self, path: &Path, contents: &[u8]) -> Result<()>;
61
62 /// Creates `path` and all parent directories.
63 fn mkdir_all(&self, path: &Path) -> Result<()>;
64
65 /// Creates a symbolic link at `link` pointing to `original`.
66 fn symlink(&self, original: &Path, link: &Path) -> Result<()>;
67
68 /// Reads the target of a symbolic link.
69 fn readlink(&self, path: &Path) -> Result<PathBuf>;
70
71 /// Removes a file or symlink (not a directory).
72 fn remove_file(&self, path: &Path) -> Result<()>;
73
74 /// Removes a directory and all of its contents.
75 fn remove_dir_all(&self, path: &Path) -> Result<()>;
76
77 /// Returns `true` if `path` exists (follows symlinks).
78 fn exists(&self, path: &Path) -> bool;
79
80 /// Returns `true` if `path` is a symlink (does not follow).
81 fn is_symlink(&self, path: &Path) -> bool;
82
83 /// Returns `true` if `path` is a directory (follows symlinks).
84 fn is_dir(&self, path: &Path) -> bool;
85
86 /// Lists entries in a directory, sorted by name.
87 fn read_dir(&self, path: &Path) -> Result<Vec<DirEntry>>;
88
89 /// Renames (moves) `from` to `to`.
90 fn rename(&self, from: &Path, to: &Path) -> Result<()>;
91
92 /// Copies a file from `from` to `to`.
93 fn copy_file(&self, from: &Path, to: &Path) -> Result<()>;
94
95 /// Sets file permissions (Unix mode).
96 fn set_permissions(&self, path: &Path, mode: u32) -> Result<()>;
97}