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
22pub 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 pub fn new(size: usize) -> anyhow::Result<Self> {
46 Device::new(size).map(Self::from)
47 }
48
49 pub fn try_to_bytes(&self) -> anyhow::Result<Vec<u8>> {
51 self.dev.try_to_bytes()
52 }
53
54 pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
56 Device::try_from_bytes(bytes).map(Self::from)
57 }
58
59 pub fn try_to_raw_device(&self) -> anyhow::Result<Vec<u8>> {
63 self.dev.try_to_raw_bytes()
64 }
65
66 pub fn from_raw_device_unchecked(device: Vec<u8>) -> Self {
68 Device::from_raw_bytes_unchecked(device).into()
69 }
70
71 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 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 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 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 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 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 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 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}