buf_fs/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3#![warn(missing_docs)]
4
5extern crate alloc;
6
7mod device;
8mod mbr;
9mod types;
10
11pub use device::*;
12pub use types::*;
13
14#[cfg(test)]
15mod tests;
16
17use alloc::{string::ToString as _, vec, vec::Vec};
18
19use embedded_sdmmc::{Mode, VolumeIdx};
20use relative_path::{Component, RelativePath, RelativePathBuf};
21
22/// A FAT16, virtual filesystem.
23///
24/// FAT-16 *IS NOT* case sensitive. All the paths will be silently converted to uppercase, as in
25/// any FAT system. It's a TODO to support ext4.
26pub struct FileSystem {
27    dev: Device,
28    cwd: RelativePathBuf,
29}
30
31impl From<Device> for FileSystem {
32    fn from(dev: Device) -> Self {
33        Self {
34            dev,
35            cwd: RelativePathBuf::from("/"),
36        }
37    }
38}
39
40impl FileSystem {
41    /// Creates a new filesystem with the provided partition size.
42    ///
43    /// The memory buffer of the filesystem is lazy loaded; that is, it will allocate bytes as they
44    /// are consumed.
45    pub fn new(size: usize) -> anyhow::Result<Self> {
46        Device::new(size).map(Self::from)
47    }
48
49    /// Serializes the structure into bytes.
50    pub fn try_to_bytes(&self) -> anyhow::Result<Vec<u8>> {
51        self.dev.try_to_bytes()
52    }
53
54    /// Deserializes the structure from bytes.
55    pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
56        Device::try_from_bytes(bytes).map(Self::from)
57    }
58
59    /// Serializes the raw device (MBR + FAT16) into bytes.
60    ///
61    /// Note: this is a compatible filesystem that can be mounted elsewhere.
62    pub fn try_to_raw_device(&self) -> anyhow::Result<Vec<u8>> {
63        self.dev.try_to_raw_bytes()
64    }
65
66    /// Deserializes the raw device (MBR + FAT16) from bytes.
67    pub fn from_raw_device_unchecked(device: Vec<u8>) -> Self {
68        Device::from_raw_bytes_unchecked(device).into()
69    }
70
71    /// Returns the current work directory.
72    pub fn cwd(&self) -> &RelativePath {
73        &self.cwd
74    }
75
76    fn path<P: AsRef<RelativePath>>(&self, path: P) -> RelativePathBuf {
77        let path = if path.as_ref().as_str().starts_with("/") {
78            path.as_ref().to_relative_path_buf()
79        } else {
80            self.cwd.join(path)
81        };
82
83        normalize_path(path)
84    }
85
86    /// Navigates into the provided directory.
87    pub fn cd<P: AsRef<RelativePath>>(&mut self, dir: P) -> anyhow::Result<()> {
88        let cwd = self.path(dir);
89
90        let mut mgr = self.dev.clone().open();
91        let mut vol = mgr
92            .open_volume(VolumeIdx(0))
93            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
94        let mut root = vol
95            .open_root_dir()
96            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
97
98        for d in cwd.into_iter() {
99            root.change_dir(d)
100                .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
101        }
102
103        self.cwd = cwd;
104
105        Ok(())
106    }
107
108    /// List the contents of the path.
109    pub fn ls<P: AsRef<RelativePath>>(&self, path: P) -> anyhow::Result<Vec<DirOrFile>> {
110        let cwd = self.path(path);
111
112        let mut mgr = self.dev.clone().open();
113        let mut vol = mgr
114            .open_volume(VolumeIdx(0))
115            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
116        let mut root = vol
117            .open_root_dir()
118            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
119
120        for d in cwd.into_iter() {
121            if root.change_dir(d).is_err() {
122                let file = root
123                    .open_file_in_dir(d, Mode::ReadOnly)
124                    .map_err(|_| anyhow::anyhow!("failed to open file `{d}`"))?;
125
126                let file = FilePath::new(cwd, file.length() as usize);
127
128                return Ok(vec![file.into()]);
129            }
130        }
131
132        let mut contents = Vec::with_capacity(20);
133
134        root.iterate_dir(|entry| {
135            let path = entry.name.to_string();
136            let path = RelativePathBuf::from(path);
137
138            if entry.attributes.is_directory() {
139                if path.as_str() != "." && path.as_str() != ".." {
140                    contents.push(Dir::new(path).into());
141                }
142            } else {
143                contents.push(FilePath::new(path, entry.size as usize).into())
144            }
145        })
146        .map_err(|_| anyhow::anyhow!("failed to iterate directory {cwd}"))?;
147
148        contents.sort();
149
150        Ok(contents)
151    }
152
153    /// Creates the provided path recursively from the current directory.
154    pub fn mkdir<P: AsRef<RelativePath>>(&mut self, dir: P) -> anyhow::Result<()> {
155        let cwd = self.path(dir);
156
157        let mut mgr = self.dev.clone().open();
158        let mut vol = mgr
159            .open_volume(VolumeIdx(0))
160            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
161        let mut root = vol
162            .open_root_dir()
163            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
164
165        for d in cwd.into_iter() {
166            if root.change_dir(d).is_err() {
167                root.make_dir_in_dir(d)
168                    .map_err(|_| anyhow::anyhow!("failed to mkdir `{d}` in `{cwd}`"))?;
169
170                root.change_dir(d)
171                    .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
172            }
173        }
174
175        Ok(())
176    }
177
178    /// Removes a file or an empty directory.
179    pub fn rm<P: AsRef<RelativePath>>(&mut self, path: P) -> anyhow::Result<()> {
180        let mut cwd = self.path(path);
181        let name = cwd
182            .file_name()
183            .ok_or_else(|| anyhow::anyhow!("failed to extract the base name from `{cwd}`."))?
184            .to_string();
185
186        anyhow::ensure!(cwd.pop(), "failed to extract parent from {cwd}.");
187
188        let mut mgr = self.dev.clone().open();
189        let mut vol = mgr
190            .open_volume(VolumeIdx(0))
191            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
192        let mut root = vol
193            .open_root_dir()
194            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
195
196        for d in cwd.into_iter() {
197            root.change_dir(d)
198                .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
199        }
200
201        if root
202            .find_directory_entry(name.as_str())
203            .map_err(|_| anyhow::anyhow!("failed to read `{name}` from `{cwd}`"))?
204            .attributes
205            .is_directory()
206        {
207            // TODO
208            // https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/186
209            anyhow::bail!("rmdir is currently not implemented");
210        }
211
212        root.delete_file_in_dir(name.as_str())
213            .map_err(|_| anyhow::anyhow!("failed to rm `{name}` from `{cwd}`"))?;
214
215        Ok(())
216    }
217
218    /// Opens the file at the specified path.
219    ///
220    /// If the file doesn't exist, the flag `File.new` will be set to `true`.
221    pub fn open<P: AsRef<RelativePath>>(&mut self, path: P) -> anyhow::Result<File> {
222        let path = self.path(path);
223        let name = path
224            .file_name()
225            .ok_or_else(|| anyhow::anyhow!("failed to define file name from `{path}`."))?;
226        let parent = normalize_parent(path.clone());
227
228        let mut mgr = self.dev.clone().open();
229        let mut vol = mgr
230            .open_volume(VolumeIdx(0))
231            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
232        let mut root = vol
233            .open_root_dir()
234            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
235
236        for d in parent.into_iter() {
237            match root.change_dir(d) {
238                Ok(d) => d,
239                Err(_) => {
240                    let new = true;
241                    let contents = vec![];
242
243                    return Ok(File::new(path, contents, new));
244                }
245            }
246        }
247
248        let mut new = false;
249        let contents = match root.open_file_in_dir(name, Mode::ReadOnly) {
250            Ok(mut f) => {
251                let mut len = f.length() as usize;
252                let mut contents = vec![0u8; len];
253                let mut ofs = 0;
254
255                while len > 0 {
256                    let n = f
257                        .read(&mut contents[ofs..])
258                        .map_err(|_| anyhow::anyhow!("failed to read from `{path}`/{name}"))?;
259
260                    len = len.saturating_sub(n);
261                    ofs = ofs.saturating_add(n);
262                }
263
264                contents
265            }
266            Err(_) => {
267                new = true;
268                vec![]
269            }
270        };
271
272        Ok(File::new(path, contents, new))
273    }
274
275    /// Saves the file into the buffer.
276    ///
277    /// Creates the path recursively, if it doesn't exist.
278    pub fn save(&mut self, file: File) -> anyhow::Result<()> {
279        let path = self.path(&file.path);
280        let name = path
281            .file_name()
282            .ok_or_else(|| anyhow::anyhow!("failed to define file name from `{path}`."))?;
283        let parent = normalize_parent(path.clone());
284
285        let mut mgr = self.dev.clone().open();
286        let mut vol = mgr
287            .open_volume(VolumeIdx(0))
288            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
289        let mut root = vol
290            .open_root_dir()
291            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
292
293        for d in parent.into_iter() {
294            if root.change_dir(d).is_err() {
295                root.make_dir_in_dir(d)
296                    .map_err(|_| anyhow::anyhow!("failed to mkdir `{d}` in `{path}`"))?;
297
298                root.change_dir(d)
299                    .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
300            }
301        }
302
303        root.open_file_in_dir(name, Mode::ReadWriteCreateOrTruncate)
304            .and_then(|mut f| f.write(&file.contents))
305            .map_err(|_| anyhow::anyhow!("failed to write to `{path}`/{name}"))?;
306
307        Ok(())
308    }
309}
310
311fn normalize_path<P: AsRef<RelativePath>>(path: P) -> RelativePathBuf {
312    let mut absolute = RelativePathBuf::from("/");
313
314    for c in path.as_ref().components() {
315        match c {
316            Component::CurDir => (),
317            Component::ParentDir if absolute == "/" => (),
318            Component::ParentDir => {
319                absolute = absolute
320                    .parent()
321                    .map(RelativePath::to_relative_path_buf)
322                    .unwrap_or(absolute);
323            }
324            Component::Normal(p) => absolute = absolute.join(p.to_uppercase()),
325        }
326    }
327
328    absolute
329}
330
331fn normalize_parent<P: AsRef<RelativePath>>(path: P) -> RelativePathBuf {
332    let path = path
333        .as_ref()
334        .normalize()
335        .parent()
336        .filter(|p| !p.as_str().is_empty())
337        .map(|p| p.to_relative_path_buf())
338        .unwrap_or_else(|| RelativePathBuf::from("/"))
339        .normalize();
340
341    match path.components().next() {
342        Some(Component::Normal("/")) => path,
343        Some(Component::Normal(_)) => RelativePathBuf::from("/").join(path),
344        _ => RelativePathBuf::from("/"),
345    }
346}
347
348#[test]
349fn normalize_path_works() {
350    let cases = vec![
351        ("/", "/"),
352        (".", "/"),
353        ("./", "/"),
354        ("..", "/"),
355        ("../", "/"),
356        ("../..", "/"),
357        ("./../..", "/"),
358        ("/root", "/ROOT"),
359    ];
360
361    for (i, o) in cases {
362        let i = RelativePathBuf::from(i);
363        let i = normalize_path(i);
364
365        assert_eq!(i.as_str(), o);
366        assert_eq!(i, RelativePathBuf::from(o));
367    }
368}
369
370#[test]
371fn normalize_parent_works() {
372    let cases = vec![
373        ("/", "/"),
374        (".", "/"),
375        ("./", "/"),
376        ("..", "/"),
377        ("../", "/"),
378        ("../..", "/"),
379        ("./../..", "/"),
380        ("/root", "/"),
381        ("/root/etc", "/root"),
382        ("/root/etc/", "/root"),
383        ("/root/etc/.", "/root"),
384    ];
385
386    for (i, o) in cases {
387        let i = RelativePathBuf::from(i);
388        let i = normalize_parent(i);
389
390        assert_eq!(i.as_str(), o);
391        assert_eq!(i, RelativePathBuf::from(o));
392    }
393}