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            root.change_dir(d)
238                .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
239        }
240
241        let mut new = false;
242        let contents = match root.open_file_in_dir(name, Mode::ReadOnly) {
243            Ok(mut f) => {
244                let mut len = f.length() as usize;
245                let mut contents = vec![0u8; len];
246                let mut ofs = 0;
247
248                while len > 0 {
249                    let n = f
250                        .read(&mut contents[ofs..])
251                        .map_err(|_| anyhow::anyhow!("failed to read from `{path}`/{name}"))?;
252
253                    len = len.saturating_sub(n);
254                    ofs = ofs.saturating_add(n);
255                }
256
257                contents
258            }
259            Err(_) => {
260                new = true;
261                vec![]
262            }
263        };
264
265        Ok(File::new(path, contents, new))
266    }
267
268    /// Saves the file into the buffer.
269    pub fn save(&mut self, file: File) -> anyhow::Result<()> {
270        let path = self.path(&file.path);
271        let name = path
272            .file_name()
273            .ok_or_else(|| anyhow::anyhow!("failed to define file name from `{path}`."))?;
274        let parent = normalize_parent(path.clone());
275
276        let mut mgr = self.dev.clone().open();
277        let mut vol = mgr
278            .open_volume(VolumeIdx(0))
279            .map_err(|_| anyhow::anyhow!("failed to open volume"))?;
280        let mut root = vol
281            .open_root_dir()
282            .map_err(|_| anyhow::anyhow!("failed to open root dir"))?;
283
284        for d in parent.into_iter() {
285            root.change_dir(d)
286                .map_err(|_| anyhow::anyhow!("failed to cd into `{d}`"))?;
287        }
288
289        root.open_file_in_dir(name, Mode::ReadWriteCreateOrTruncate)
290            .and_then(|mut f| f.write(&file.contents))
291            .map_err(|_| anyhow::anyhow!("failed to write to `{path}`/{name}"))?;
292
293        Ok(())
294    }
295}
296
297fn normalize_path<P: AsRef<RelativePath>>(path: P) -> RelativePathBuf {
298    let mut absolute = RelativePathBuf::from("/");
299
300    for c in path.as_ref().components() {
301        match c {
302            Component::CurDir => (),
303            Component::ParentDir if absolute == "/" => (),
304            Component::ParentDir => {
305                absolute = absolute
306                    .parent()
307                    .map(RelativePath::to_relative_path_buf)
308                    .unwrap_or(absolute);
309            }
310            Component::Normal(p) => absolute = absolute.join(p.to_uppercase()),
311        }
312    }
313
314    absolute
315}
316
317fn normalize_parent<P: AsRef<RelativePath>>(path: P) -> RelativePathBuf {
318    let path = path
319        .as_ref()
320        .normalize()
321        .parent()
322        .filter(|p| !p.as_str().is_empty())
323        .map(|p| p.to_relative_path_buf())
324        .unwrap_or_else(|| RelativePathBuf::from("/"))
325        .normalize();
326
327    match path.components().next() {
328        Some(Component::Normal("/")) => path,
329        Some(Component::Normal(_)) => RelativePathBuf::from("/").join(path),
330        _ => RelativePathBuf::from("/"),
331    }
332}
333
334#[test]
335fn normalize_path_works() {
336    let cases = vec![
337        ("/", "/"),
338        (".", "/"),
339        ("./", "/"),
340        ("..", "/"),
341        ("../", "/"),
342        ("../..", "/"),
343        ("./../..", "/"),
344        ("/root", "/ROOT"),
345    ];
346
347    for (i, o) in cases {
348        let i = RelativePathBuf::from(i);
349        let i = normalize_path(i);
350
351        assert_eq!(i.as_str(), o);
352        assert_eq!(i, RelativePathBuf::from(o));
353    }
354}
355
356#[test]
357fn normalize_parent_works() {
358    let cases = vec![
359        ("/", "/"),
360        (".", "/"),
361        ("./", "/"),
362        ("..", "/"),
363        ("../", "/"),
364        ("../..", "/"),
365        ("./../..", "/"),
366        ("/root", "/"),
367        ("/root/etc", "/root"),
368        ("/root/etc/", "/root"),
369        ("/root/etc/.", "/root"),
370    ];
371
372    for (i, o) in cases {
373        let i = RelativePathBuf::from(i);
374        let i = normalize_parent(i);
375
376        assert_eq!(i.as_str(), o);
377        assert_eq!(i, RelativePathBuf::from(o));
378    }
379}