buf_fs/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::{
5    ffi::OsStr,
6    io::{Cursor, Read, Write},
7    path::{Path, PathBuf},
8};
9
10use fatfs::FormatVolumeOptions;
11
12mod types;
13
14pub use types::*;
15
16#[cfg(test)]
17mod tests;
18
19/// A buffer backed filesystem.
20pub struct FileSystem<'a> {
21    pub(crate) fs: fatfs::FileSystem<Cursor<&'a mut [u8]>>,
22    cwd: PathBuf,
23}
24
25impl<'a> FileSystem<'a> {
26    /// Mount the buffer, assuming it is formatted.
27    pub fn mount(buffer: &'a mut [u8]) -> anyhow::Result<Self> {
28        let buffer = Cursor::new(buffer);
29        let fs = fatfs::FileSystem::new(buffer, fatfs::FsOptions::new())?;
30        let cwd = PathBuf::from("/");
31
32        Ok(FileSystem { fs, cwd })
33    }
34
35    /// Format the buffer.
36    pub fn format(buffer: &mut [u8]) -> anyhow::Result<()> {
37        let mut cursor = Cursor::new(buffer);
38
39        let opts = FormatVolumeOptions::new();
40
41        fatfs::format_volume(&mut cursor, opts)?;
42
43        Ok(())
44    }
45
46    /// Format the buffer if there is no MBR, and then mount it.
47    pub fn mount_or_format(buffer: &'a mut [u8]) -> anyhow::Result<Self> {
48        if let Some(0) = buffer.get(0) {
49            Self::format(buffer)?;
50        }
51
52        Self::mount(buffer)
53    }
54
55    /// Returns the current work directory.
56    pub fn cwd(&self) -> &Path {
57        &self.cwd
58    }
59
60    /// Navigates into the provided directory.
61    pub fn cd<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<()> {
62        let cwd = self.cwd.join(dir);
63        let cwd = make_virtual_path_absolute(cwd);
64
65        if cwd.parent().is_some() {
66            let mut root = self.fs.root_dir();
67
68            for d in cwd.into_iter().skip(1) {
69                root = root.open_dir(node(d)?)?;
70            }
71        }
72
73        self.cwd = cwd;
74
75        Ok(())
76    }
77
78    /// Creates the provided path recursively from the current directory.
79    pub fn mkdir<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<()> {
80        let mut root = self.fs.root_dir();
81
82        for d in dir.as_ref().into_iter() {
83            let d = node(d)?;
84
85            if d != "/" {
86                root = match root.open_dir(d) {
87                    Ok(d) => d,
88                    Err(_) => root.create_dir(d)?,
89                };
90            }
91        }
92
93        Ok(())
94    }
95
96    /// Recursively removes the path and all its contents.
97    pub fn rmdir<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<()> {
98        self._rmdir(dir)?;
99
100        let mut root = self.fs.root_dir();
101        let mut path = PathBuf::from("/");
102
103        for d in self.cwd.into_iter().skip(1) {
104            root = match root.open_dir(node(d)?) {
105                Ok(r) => r,
106                Err(_) => break,
107            };
108
109            path = path.join(d);
110        }
111
112        self.cwd = make_virtual_path_absolute(path);
113
114        Ok(())
115    }
116
117    fn _rmdir<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<()> {
118        for c in self.ls(dir.as_ref())? {
119            match c {
120                DirOrFile::Dir(d) => self._rmdir(&d.path)?,
121                DirOrFile::File(f) => self.rm(f.path)?,
122            }
123        }
124
125        self.rm(dir)
126    }
127
128    /// Opens the file at the specified path.
129    pub fn open<P: AsRef<Path>>(&mut self, path: P) -> anyhow::Result<File> {
130        let path = path.as_ref();
131        let name = path.file_name().ok_or_else(|| {
132            anyhow::anyhow!("failed to define file name from `{}`.", path.display())
133        })?;
134        let name = node(name)?;
135        let dir = path.parent().ok_or_else(|| {
136            anyhow::anyhow!("failed to navigate to parent of `{}`.", path.display())
137        })?;
138
139        let cwd = self.cwd.clone();
140
141        fn try_open<'a>(slf: &mut FileSystem<'a>, dir: &Path, name: &str) -> anyhow::Result<File> {
142            slf.cd(dir)?;
143
144            let mut root = slf.fs.root_dir();
145
146            for d in slf.cwd.into_iter().skip(1) {
147                root = root.open_dir(node(d)?)?;
148            }
149
150            let mut new = false;
151            let contents = match root.create_file(name) {
152                Ok(mut f) => {
153                    let mut contents = Vec::new();
154
155                    f.read_to_end(&mut contents)?;
156
157                    contents
158                }
159                Err(_) => {
160                    new = true;
161                    vec![]
162                }
163            };
164
165            Ok(File {
166                path: dir.join(name),
167                contents,
168                new,
169            })
170        }
171
172        let res = try_open(self, dir, name);
173
174        self.cwd = cwd;
175
176        res
177    }
178
179    /// Saves the file into the buffer.
180    pub fn save(&mut self, file: File) -> anyhow::Result<()> {
181        let name = file.path.file_name().ok_or_else(|| {
182            anyhow::anyhow!("failed to define file name from `{}`.", file.path.display())
183        })?;
184        let name = node(name)?;
185        let dir = file.path.parent().ok_or_else(|| {
186            anyhow::anyhow!("failed to navigate to parent of `{}`.", file.path.display())
187        })?;
188
189        let cwd = self.cwd.clone();
190
191        fn try_save<'a>(
192            slf: &mut FileSystem<'a>,
193            name: &str,
194            dir: &Path,
195            contents: &[u8],
196        ) -> anyhow::Result<()> {
197            slf.cd(dir)?;
198
199            let mut root = slf.fs.root_dir();
200
201            for d in slf.cwd.into_iter().skip(1) {
202                root = root.open_dir(node(d)?)?;
203            }
204
205            root.create_file(name)?.write_all(contents)?;
206
207            Ok(())
208        }
209
210        let res = try_save(self, name, dir, &file.contents);
211
212        self.cwd = cwd;
213
214        res
215    }
216
217    /// List the contents of a directory.
218    pub fn ls<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<Vec<DirOrFile>> {
219        let dir = self.cwd.join(dir);
220        let dir = make_virtual_path_absolute(dir);
221        let mut root = self.fs.root_dir();
222
223        for d in dir.into_iter().skip(1) {
224            root = root.open_dir(node(d)?)?;
225        }
226
227        let mut contents = Vec::with_capacity(20);
228
229        for c in root.iter() {
230            let entry = c?;
231            let name = entry.file_name();
232            let path = dir.join(&name);
233
234            if entry.is_dir() {
235                if name != "." && name != ".." {
236                    contents.push(Dir::new(path).into());
237                }
238            } else {
239                contents.push(FilePath::new(path, entry.len() as usize).into());
240            }
241        }
242
243        contents.sort();
244
245        Ok(contents)
246    }
247
248    /// Removes a file or an empty directory.
249    pub fn rm<P: AsRef<Path>>(&mut self, dir: P) -> anyhow::Result<()> {
250        let dir = self.cwd.join(dir);
251        let mut dir = make_virtual_path_absolute(dir);
252
253        anyhow::ensure!(
254            dir.parent().is_some(),
255            "directory `{}` cannot be removed.",
256            dir.display()
257        );
258
259        let name = dir.file_name().ok_or_else(|| {
260            anyhow::anyhow!("failed to compute the file name from `{}`", dir.display())
261        })?;
262        let name = node(name)?.to_string();
263
264        dir.pop();
265
266        let mut root = self.fs.root_dir();
267
268        for d in dir.into_iter().skip(1) {
269            root = root.open_dir(node(d)?)?;
270        }
271
272        root.remove(&name)?;
273
274        Ok(())
275    }
276}
277
278fn node(node: &OsStr) -> anyhow::Result<&str> {
279    node.to_str()
280        .ok_or_else(|| anyhow::anyhow!("failed to read path node"))
281}
282
283fn make_virtual_path_absolute(path: PathBuf) -> PathBuf {
284    let mut absolute = PathBuf::from("/");
285    let mut nodes = path.into_iter();
286
287    if let Some(d) = nodes.next() {
288        absolute = PathBuf::from("/").join(d);
289    }
290
291    for d in nodes {
292        if d == "." || d == "/" {
293            // nothing to do
294        } else if d == ".." {
295            absolute = absolute
296                .parent()
297                .map(Path::to_path_buf)
298                .unwrap_or_else(|| PathBuf::from("/"));
299        } else {
300            absolute = absolute.join(d);
301        }
302    }
303
304    absolute
305}