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 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 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}