1use std::io::{Read, Seek, Write};
5
6use server::{permission_set, CheckedPermissions, MessageAllowed};
7
8use crate::{messages::*, DirEntry, Error, FileSystem, Location, Metadata, OpenFlags};
9
10permission_set!(
11 pub trait BasicFsPermissions {
14 OpenDirMessage, OpenFileMessage, CloseFile, CloseDir, CreateDirMessage, ReadFile, SeekFile,
15 WriteFile, TruncateFile, SetLen, GetMetadata, NextEntry, Flush, FlushFs, Remove, Rename,
16 AtomicCopy, AsyncRead, AsyncWrite, AsyncCopyBlock, SubscribeFilesystemEvent
17 }
18);
19
20pub trait FsAdapter {
25 type File: FileAdapter<Self::Permissions>;
26 type Permissions: CheckedPermissions;
27 type DirIter: Iterator<Item = Result<DirEntry, Error>>;
28
29 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error>
30 where
31 Self::Permissions: MessageAllowed<CreateDirMessage>,
32 Self::Permissions: MessageAllowed<CloseDir>;
33
34 fn remove(&self, path: &str, location: Location) -> Result<(), Error>
35 where
36 Self::Permissions: MessageAllowed<Remove>;
37
38 fn atomic_copy(
39 &self,
40 src: &str,
41 dest: &str,
42 rename: Option<String>,
43 location: Location,
44 ) -> Result<(), Error>
45 where
46 Self::Permissions: MessageAllowed<AtomicCopy>;
47
48 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error>
49 where
50 Self::Permissions: MessageAllowed<OpenFileMessage>,
51 Self::Permissions: MessageAllowed<CloseFile>;
52
53 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error>
54 where
55 Self::Permissions: MessageAllowed<OpenDirMessage>,
56 Self::Permissions: MessageAllowed<CloseDir>,
57 Self::Permissions: MessageAllowed<NextEntry>;
58
59 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
60 where
61 Self::Permissions: MessageAllowed<GetMetadata>;
62
63 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error>
64 where
65 Self::Permissions: MessageAllowed<Rename>;
66
67 fn flush(&mut self, location: Location) -> Result<(), Error>
68 where
69 Self::Permissions: MessageAllowed<FlushFs>;
70
71 fn walk_dir(&self, path: &str, location: Location) -> Result<DirWalker<Self>, Error>
72 where
73 Self: Clone,
74 Self::Permissions: MessageAllowed<OpenDirMessage>,
75 Self::Permissions: MessageAllowed<CloseDir>,
76 Self::Permissions: MessageAllowed<NextEntry>,
77 {
78 DirWalker::new(self.clone(), path, location)
79 }
80
81 fn ensure_parent_dir_exists(&self, path: &str, location: Location) -> Result<(), Error>
82 where
83 Self::Permissions: MessageAllowed<CreateDirMessage>,
84 Self::Permissions: MessageAllowed<CloseDir>,
85 {
86 crate::ensure_parent_dir_exists_impl(|dir| self.create_dir(dir, location), path)
87 }
88
89 fn remove_if_exists(&self, path: &str, location: Location) -> Result<(), Error>
90 where
91 Self::Permissions: MessageAllowed<Remove>,
92 {
93 match self.remove(path, location) {
94 Ok(_) => Ok(()),
95 Err(Error::FileNotFound) => Ok(()),
96 Err(e) => Err(e),
97 }
98 }
99}
100
101impl<P> FsAdapter for FileSystem<P>
102where
103 P: CheckedPermissions
104 + MessageAllowed<CloseFile>
105 + MessageAllowed<CloseDir>
106 + MessageAllowed<NextEntry>
107 + MessageAllowed<ReadFile>
108 + MessageAllowed<WriteFile>
109 + MessageAllowed<Flush>
110 + MessageAllowed<SeekFile>,
111{
112 type DirIter = DirIterator<P>;
113 type File = crate::File<P>;
114 type Permissions = P;
115
116 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error>
117 where
118 P: MessageAllowed<CreateDirMessage>,
119 P: MessageAllowed<CloseDir>,
120 {
121 Ok(self.create_dir(path, location).map(|_| ())?)
122 }
123
124 fn remove(&self, path: &str, location: Location) -> Result<(), Error>
125 where
126 P: MessageAllowed<Remove>,
127 {
128 Ok(self.remove(path, location)?)
129 }
130
131 fn atomic_copy(
132 &self,
133 src: &str,
134 dest: &str,
135 rename: Option<String>,
136 location: Location,
137 ) -> Result<(), Error>
138 where
139 P: MessageAllowed<AtomicCopy>,
140 {
141 Ok(self.atomic_copy(src, dest, rename, location)?)
142 }
143
144 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error>
145 where
146 P: MessageAllowed<OpenFileMessage>,
147 P: MessageAllowed<CloseFile>,
148 {
149 Ok(self.open_file(path, location, flags)?)
150 }
151
152 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error>
153 where
154 P: MessageAllowed<OpenDirMessage>,
155 P: MessageAllowed<CloseDir>,
156 P: MessageAllowed<NextEntry>,
157 {
158 let dir = self.open_dir(path, location)?;
159 Ok(DirIterator { dir })
160 }
161
162 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
163 where
164 Self::Permissions: MessageAllowed<GetMetadata>,
165 {
166 Ok(self.metadata(path, location)?)
167 }
168
169 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error>
170 where
171 P: MessageAllowed<Rename>,
172 {
173 Ok(self.rename(src, dest, location)?)
174 }
175
176 fn flush(&mut self, location: Location) -> Result<(), Error>
177 where
178 P: MessageAllowed<FlushFs>,
179 {
180 Ok(FileSystem::flush(self, location)?)
181 }
182}
183
184pub trait FileAdapter<P: CheckedPermissions>: Read + Write + Seek {
185 fn metadata(&self) -> Result<Metadata, Error>
186 where
187 P: MessageAllowed<GetMetadata>;
188
189 fn truncate(&mut self) -> Result<(), Error>
190 where
191 P: MessageAllowed<TruncateFile>;
192
193 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
194 where
195 P: MessageAllowed<SetMtime>;
196
197 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
198 where
199 P: MessageAllowed<AsyncCopyBlock>;
200}
201
202impl<P> FileAdapter<P> for crate::File<P>
203where
204 P: CheckedPermissions
205 + MessageAllowed<CloseFile>
206 + MessageAllowed<ReadFile>
207 + MessageAllowed<WriteFile>
208 + MessageAllowed<Flush>
209 + MessageAllowed<SeekFile>,
210{
211 fn metadata(&self) -> Result<Metadata, Error>
212 where
213 P: MessageAllowed<GetMetadata>,
214 {
215 self.metadata()
216 }
217
218 fn truncate(&mut self) -> Result<(), Error>
219 where
220 P: MessageAllowed<TruncateFile>,
221 {
222 self.truncate()
223 }
224
225 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
226 where
227 P: MessageAllowed<SetMtime>,
228 {
229 self.set_mtime(datetime)
230 }
231
232 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
233 where
234 P: MessageAllowed<AsyncCopyBlock>,
235 {
236 self.copy_block_to(to, len)
237 }
238}
239
240pub struct DirIterator<P: CheckedPermissions + MessageAllowed<CloseDir>> {
241 dir: crate::Dir<P>,
242}
243
244impl<P: CheckedPermissions + MessageAllowed<CloseDir>> Iterator for DirIterator<P>
245where
246 P: MessageAllowed<NextEntry>,
247{
248 type Item = Result<DirEntry, Error>;
249
250 fn next(&mut self) -> Option<Self::Item> {
251 match self.dir.next_entry() {
252 Ok(Some(entry)) => Some(Ok(entry)),
253 Ok(None) => None,
254 Err(e) => Some(Err(e)),
255 }
256 }
257}
258
259pub struct DirWalker<F>
260where
261 F: FsAdapter,
262 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
263{
264 fs: F,
265 current_iter: Option<F::DirIter>,
267 current_path: String,
269 stack: Vec<String>,
271 location: Location,
272}
273
274impl<F> DirWalker<F>
275where
276 F: FsAdapter,
277 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
278{
279 pub fn new(fs: F, path: impl Into<String>, location: Location) -> Result<Self, Error> {
280 let path = path.into();
281 let current_iter = fs.open_dir(&path, location)?;
282
283 Ok(Self { fs, current_iter: Some(current_iter), current_path: path, stack: Vec::new(), location })
284 }
285}
286
287impl<F> Iterator for DirWalker<F>
288where
289 F: FsAdapter,
290 F::Permissions: MessageAllowed<CloseDir> + MessageAllowed<OpenDirMessage> + MessageAllowed<NextEntry>,
291{
292 type Item = Result<(String, DirEntry), Error>;
293
294 fn next(&mut self) -> Option<Self::Item> {
295 loop {
296 if let Some(iter) = &mut self.current_iter {
297 match iter.next() {
298 Some(Ok(entry)) => {
299 if entry.name == "." || entry.name == ".." {
300 continue;
301 }
302
303 let full_path = if self.current_path.is_empty() || self.current_path == "/" {
304 entry.name.clone()
305 } else {
306 format!("{}/{}", self.current_path.trim_end_matches('/'), entry.name)
307 };
308
309 if entry.is_dir {
310 self.stack.push(full_path.clone());
311 }
312
313 return Some(Ok((full_path, entry)));
314 }
315 Some(Err(e)) => {
316 return Some(Err(e));
317 }
318 None => {
319 self.current_iter = None;
320 }
321 }
322 }
323
324 if let Some(next_path) = self.stack.pop() {
326 match self.fs.open_dir(&next_path, self.location) {
327 Ok(iter) => {
328 self.current_path = next_path;
329 self.current_iter = Some(iter);
330 }
331 Err(e) => {
332 return Some(Err(e));
333 }
334 }
335 } else {
336 return None;
337 }
338 }
339 }
340}
341
342#[cfg(feature = "test")]
343pub mod test_utils {
344 use std::collections::HashMap;
345 use std::marker::PhantomData;
346 use std::path::PathBuf;
347 use std::sync::Arc;
348
349 use chrono::{DateTime, Datelike, Local, Timelike};
350 use server::AllPermissions;
351
352 use super::*;
353
354 pub struct TestFile<P: CheckedPermissions> {
356 file: std::fs::File,
357 _phantom: PhantomData<P>,
358 }
359
360 impl<P: CheckedPermissions> TestFile<P> {
361 pub fn new(file: std::fs::File) -> Self { Self { file, _phantom: PhantomData } }
362 }
363
364 impl<P: CheckedPermissions> Read for TestFile<P> {
365 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.file.read(buf) }
366 }
367
368 impl<P: CheckedPermissions> Write for TestFile<P> {
369 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.file.write(buf) }
370
371 fn flush(&mut self) -> std::io::Result<()> { self.file.flush() }
372 }
373
374 impl<P: CheckedPermissions> Seek for TestFile<P> {
375 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> { self.file.seek(pos) }
376 }
377
378 impl<P: CheckedPermissions> FileAdapter<P> for TestFile<P> {
379 fn metadata(&self) -> Result<Metadata, Error>
380 where
381 P: MessageAllowed<GetMetadata>,
382 {
383 let metadata: Metadata = self.file.metadata()?.try_into()?;
384 Ok(metadata)
385 }
386
387 fn truncate(&mut self) -> Result<(), Error>
388 where
389 P: MessageAllowed<TruncateFile>,
390 {
391 let pos = self.file.stream_position()?;
392 self.file.set_len(pos)?;
393 Ok(())
394 }
395
396 fn set_mtime(&mut self, datetime: crate::DateTime) -> Result<(), Error>
397 where
398 P: MessageAllowed<SetMtime>,
399 {
400 use chrono::{Local, TimeZone};
401
402 let datetime_local = Local
403 .with_ymd_and_hms(
404 datetime.date.year as i32,
405 datetime.date.month as u32,
406 datetime.date.day as u32,
407 datetime.time.hour as u32,
408 datetime.time.min as u32,
409 datetime.time.sec as u32,
410 )
411 .single()
412 .ok_or(Error::Io)?;
413 let system_time: std::time::SystemTime = datetime_local.into();
414
415 self.file.set_modified(system_time)?;
416 Ok(())
417 }
418
419 fn copy_block_to(&mut self, to: &mut Self, len: usize) -> Result<usize, Error>
420 where
421 P: MessageAllowed<AsyncCopyBlock>,
422 {
423 use std::io::{Read, Write};
424
425 let mut buf = vec![0u8; len];
426 let bytes_read = self.file.read(&mut buf)?;
427 to.file.write_all(&buf[..bytes_read])?;
428 Ok(bytes_read)
429 }
430 }
431
432 pub struct TestDirIterator {
433 entries: std::vec::IntoIter<std::result::Result<std::fs::DirEntry, std::io::Error>>,
434 }
435
436 impl Iterator for TestDirIterator {
437 type Item = Result<DirEntry, Error>;
438
439 fn next(&mut self) -> Option<Self::Item> {
440 use chrono::Local;
441
442 self.entries.next().map(|entry| {
443 let entry = entry?;
444 let metadata = entry.metadata()?;
445 let modified: chrono::DateTime<Local> = metadata.modified()?.into();
446 let modified = modified.into();
447
448 Ok(DirEntry {
449 name: entry.file_name().to_string_lossy().to_string(),
450 modified,
451 len: metadata.len(),
452 is_dir: metadata.is_dir(),
453 is_file: metadata.is_file(),
454 })
455 })
456 }
457 }
458
459 #[derive(Clone)]
461 pub struct FsTest {
462 _temp_dir: Arc<tempfile::TempDir>,
463 roots: Arc<HashMap<Location, PathBuf>>,
464 }
465
466 impl Default for FsTest {
467 fn default() -> Self {
468 let temp_dir = tempfile::TempDir::new().unwrap();
469 let base = temp_dir.path();
470
471 let mut roots = HashMap::new();
472 roots.insert(Location::EncryptedRoot, base.join("encrypted"));
473 roots.insert(Location::System, base.join("system"));
474 roots.insert(Location::SystemAppData, base.join(crate::SYSTEM_STATE_ROOT));
475 roots.insert(Location::CommonAssets, base.join("common"));
476 roots.insert(Location::AppData, base.join("appdata"));
477 roots.insert(Location::Usb, base.join("usb"));
478 roots.insert(Location::User, base.join("user"));
479 roots.insert(Location::Boot, base.join("boot"));
480 roots.insert(Location::AppResources, base.join("app-resources"));
481
482 for path in roots.values() {
483 std::fs::create_dir_all(path).unwrap();
484 }
485
486 Self { _temp_dir: Arc::new(temp_dir), roots: Arc::new(roots) }
487 }
488 }
489
490 impl FsTest {
491 fn root(&self, location: Location) -> &PathBuf { self.roots.get(&location).unwrap() }
492
493 pub fn write_file(&self, path: &str, contents: &[u8], location: Location) {
496 let _ = self.remove(path, location);
497 let parts: Vec<&str> = path.rsplitn(2, '/').collect();
498 if parts.len() == 2 {
499 self.create_dir(parts[1], location).unwrap();
500 }
501 let mut file = self.open_file(path, location, OpenFlags::CREATE).unwrap();
502 file.write_all(contents).unwrap();
503 }
504
505 pub fn read_file_contents(&self, path: &str, location: Location) -> Result<Vec<u8>, Error> {
506 let mut file = self.open_file(path, location, OpenFlags::READ_ONLY)?;
507 let mut contents = Vec::new();
508 file.read_to_end(&mut contents)?;
509 Ok(contents)
510 }
511
512 pub fn print_tree(&self, location: Location) {
515 let root = self.root(location);
516 println!("-----");
517 self.print_tree_recursive(root, 0, None);
518 println!("-----");
519 }
520
521 pub fn print_tree_with_depth(&self, location: Location, max_depth: usize) {
523 let root = self.root(location);
524 println!("-----");
525 self.print_tree_recursive(root, 0, Some(max_depth));
526 println!("-----");
527 }
528
529 fn print_tree_recursive(&self, dir_path: &std::path::Path, depth: usize, max_depth: Option<usize>) {
530 if let Some(max) = max_depth {
531 if depth >= max {
532 return;
533 }
534 }
535
536 let Ok(entries) = std::fs::read_dir(dir_path) else {
537 return;
538 };
539
540 for entry in entries.flatten() {
541 let name = entry.file_name();
542 let name_str = name.to_string_lossy();
543
544 for _ in 0..depth {
545 print!("\t");
546 }
547 println!("{name_str}");
548
549 if entry.path().is_dir() {
550 self.print_tree_recursive(&entry.path(), depth + 1, max_depth);
551 }
552 }
553 }
554 }
555
556 impl TryFrom<std::fs::Metadata> for Metadata {
557 type Error = std::io::Error;
558
559 fn try_from(metadata: std::fs::Metadata) -> Result<Self, Self::Error> {
560 let created: DateTime<Local> = metadata.created()?.into();
561 let accessed: DateTime<Local> = metadata.accessed()?.into();
562 let modified: DateTime<Local> = metadata.modified()?.into();
563
564 let accessed_date = accessed.date_naive();
565 Ok(crate::Metadata {
566 is_dir: metadata.is_dir(),
567 size: metadata.len(),
568 created: created.into(),
569 accessed: crate::Date {
570 year: accessed_date.year() as u16,
571 month: accessed_date.month() as u16,
572 day: accessed_date.day() as u16,
573 },
574 modified: modified.into(),
575 })
576 }
577 }
578
579 impl FsAdapter for FsTest {
580 type DirIter = TestDirIterator;
581 type File = TestFile<AllPermissions>;
582 type Permissions = AllPermissions;
583
584 fn create_dir(&self, path: &str, location: Location) -> Result<(), Error> {
585 let root = self.root(location);
586 std::fs::create_dir_all(root.join(path.trim_start_matches('/')))?;
587 Ok(())
588 }
589
590 fn remove(&self, path: &str, location: Location) -> Result<(), Error> {
591 let root = self.root(location);
592 let full_path = root.join(path.trim_start_matches('/'));
593 if full_path.is_dir() {
594 std::fs::remove_dir_all(&full_path)?;
595 } else {
596 std::fs::remove_file(&full_path)?;
597 }
598 Ok(())
599 }
600
601 fn atomic_copy(
602 &self,
603 src: &str,
604 dest: &str,
605 rename: Option<String>,
606 location: Location,
607 ) -> Result<(), Error> {
608 fn copy_recursive(src: &std::path::Path, dest: &std::path::Path) -> Result<(), Error> {
609 if src.is_dir() {
610 std::fs::create_dir(dest)?;
611 for entry in std::fs::read_dir(src)? {
612 let entry = entry?;
613 copy_recursive(&entry.path(), &dest.join(entry.file_name()))?;
614 }
615 } else {
616 std::fs::copy(src, dest)?;
617 }
618 Ok(())
619 }
620 let root = self.root(location);
621 let src_path = root.join(src.trim_start_matches('/'));
622 let dest_path = root.join(dest.trim_start_matches('/'));
623
624 if !dest_path.exists() {
625 return Err(Error::FileNotFound);
626 }
627
628 let final_dest = if let Some(new_name) = rename {
629 dest_path.join(new_name)
630 } else {
631 dest_path.join(src_path.file_name().unwrap())
632 };
633
634 copy_recursive(&src_path, &final_dest)
635 }
636
637 fn open_file(&self, path: &str, location: Location, flags: OpenFlags) -> Result<Self::File, Error> {
638 let root = self.root(location);
639 let full_path = root.join(path.trim_start_matches('/'));
640
641 let file = std::fs::OpenOptions::new()
642 .read(flags.read)
643 .write(flags.write)
644 .create(flags.create)
645 .truncate(false)
646 .open(&full_path)?;
647
648 Ok(TestFile::new(file))
649 }
650
651 fn open_dir(&self, path: &str, location: Location) -> Result<Self::DirIter, Error> {
652 let root = self.root(location);
653 let full_path = root.join(path.trim_start_matches('/'));
654
655 let entries: Vec<_> = std::fs::read_dir(&full_path)?.collect();
656
657 Ok(TestDirIterator { entries: entries.into_iter() })
658 }
659
660 fn rename(&self, src: &str, dest: &str, location: Location) -> Result<(), Error> {
661 let root = self.root(location);
662 let src_path = root.join(src.trim_start_matches('/'));
663 let dest_path = root.join(dest.trim_start_matches('/'));
664 std::fs::rename(&src_path, &dest_path)?;
665 Ok(())
666 }
667
668 fn metadata(&self, path: &str, location: Location) -> Result<Metadata, Error>
669 where
670 Self::Permissions: MessageAllowed<GetMetadata>,
671 {
672 let root = self.root(location);
673 Ok(std::fs::metadata(root.join(path.trim_start_matches('/')))?.try_into()?)
674 }
675
676 fn flush(&mut self, _location: Location) -> Result<(), Error> { Ok(()) }
677 }
678
679 impl From<chrono::DateTime<Local>> for crate::DateTime {
680 fn from(dt: chrono::DateTime<Local>) -> Self {
681 crate::DateTime {
682 date: crate::Date { year: dt.year() as u16, month: dt.month() as u16, day: dt.day() as u16 },
683 time: crate::Time {
684 hour: dt.hour() as u16,
685 min: dt.minute() as u16,
686 sec: dt.second() as u16,
687 millis: (dt.nanosecond() / 1_000_000) as u16,
688 },
689 }
690 }
691 }
692
693 #[cfg(test)]
694 mod tests {
695 use super::*;
696
697 #[test]
698 fn test_dir_walker() {
699 let fs = FsTest::default();
700 let location = Location::AppData;
701
702 fs.write_file("root.txt", b"root", location);
703 fs.write_file("dir1/file1.txt", b"file1", location);
704 fs.write_file("dir1/file2.txt", b"file2", location);
705 fs.write_file("dir1/subdir/file3.txt", b"file3", location);
706 fs.write_file("dir2/file4.txt", b"file4", location);
707
708 let walker = fs.walk_dir("/", location).unwrap();
709 let mut paths: Vec<String> = walker.map(|r| r.unwrap().0).collect();
710 paths.sort();
711
712 let expected = vec![
713 "dir1",
714 "dir1/file1.txt",
715 "dir1/file2.txt",
716 "dir1/subdir",
717 "dir1/subdir/file3.txt",
718 "dir2",
719 "dir2/file4.txt",
720 "root.txt",
721 ];
722
723 assert_eq!(paths, expected);
724 }
725 }
726}