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
19pub struct FileSystem<'a> {
21 pub(crate) fs: fatfs::FileSystem<Cursor<&'a mut [u8]>>,
22 cwd: PathBuf,
23}
24
25impl<'a> FileSystem<'a> {
26 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 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 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 pub fn cwd(&self) -> &Path {
57 &self.cwd
58 }
59
60 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 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 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 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 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 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 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 } 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}