1use std::ffi::OsString;
6use std::fs::{DirBuilder, File, OpenOptions};
7use std::io::{self, Write};
8use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
9use std::path::Path;
10
11pub const FILE_MODE: u32 = 0o600;
15
16pub const DIR_MODE: u32 = 0o700;
18
19pub trait Fs: Send + Sync {
21 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()>;
29 fn sync_file(&self, path: &Path) -> io::Result<()>;
35 fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
41 fn rename_new(&self, from: &Path, to: &Path) -> io::Result<()>;
49 fn sync_dir(&self, dir: &Path) -> io::Result<()>;
56 fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
62 fn remove(&self, path: &Path) -> io::Result<()>;
69 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>>;
75 fn create_dir_all(&self, dir: &Path) -> io::Result<()>;
82 fn remove_dir_all(&self, dir: &Path) -> io::Result<()>;
90}
91
92#[derive(Debug, Clone, Copy, Default)]
96pub struct OsFs;
97
98impl Fs for OsFs {
99 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()> {
100 OpenOptions::new()
101 .write(true)
102 .create_new(true)
103 .mode(FILE_MODE)
104 .open(path)?
105 .write_all(data)
106 }
107
108 fn sync_file(&self, path: &Path) -> io::Result<()> {
109 File::open(path)?.sync_all()
110 }
111
112 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
113 std::fs::rename(from, to)
114 }
115
116 fn rename_new(&self, from: &Path, to: &Path) -> io::Result<()> {
117 use std::os::unix::ffi::OsStrExt;
118 let c = |p: &Path| {
119 std::ffi::CString::new(p.as_os_str().as_bytes())
120 .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))
121 };
122 let (from, to) = (c(from)?, c(to)?);
123 let done = unsafe {
126 libc::renameat2(
127 libc::AT_FDCWD,
128 from.as_ptr(),
129 libc::AT_FDCWD,
130 to.as_ptr(),
131 libc::RENAME_NOREPLACE,
132 )
133 };
134 if done != 0 {
135 return Err(io::Error::last_os_error());
136 }
137 Ok(())
138 }
139
140 fn sync_dir(&self, dir: &Path) -> io::Result<()> {
141 File::open(dir)?.sync_all()
142 }
143
144 fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
145 std::fs::read(path)
146 }
147
148 fn remove(&self, path: &Path) -> io::Result<()> {
149 std::fs::remove_file(path)
150 }
151
152 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>> {
153 let mut names = std::fs::read_dir(dir)?
154 .map(|entry| entry.map(|e| e.file_name()))
155 .collect::<io::Result<Vec<_>>>()?;
156 names.sort();
157 Ok(names)
158 }
159
160 fn create_dir_all(&self, dir: &Path) -> io::Result<()> {
161 DirBuilder::new().recursive(true).mode(DIR_MODE).create(dir)
162 }
163
164 fn remove_dir_all(&self, dir: &Path) -> io::Result<()> {
165 std::fs::remove_dir_all(dir)
166 }
167}
168
169#[cfg(test)]
170pub(crate) use crash::{CrashFs, Op};
171
172#[cfg(test)]
173mod crash {
174 use std::collections::BTreeMap;
175 use std::ffi::OsString;
176 use std::io;
177 use std::path::{Path, PathBuf};
178 use std::sync::Mutex;
179
180 use super::Fs;
181
182 #[derive(Debug, Clone, PartialEq, Eq)]
184 pub(crate) enum Op {
185 Write(PathBuf),
186 SyncFile(PathBuf),
187 Rename(PathBuf, PathBuf),
188 SyncDir(PathBuf),
189 Remove(PathBuf),
190 CreateDir(PathBuf),
191 RemoveDir(PathBuf),
192 }
193
194 pub(crate) struct CrashFs {
207 state: Mutex<State>,
208 }
209
210 struct State {
211 root: PathBuf,
212 dirs: BTreeMap<PathBuf, Dir>,
213 files: Vec<File>,
214 log: Vec<Op>,
215 fail_from: Option<usize>,
217 }
218
219 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
220 enum Entry {
221 File(usize),
223 Dir,
224 }
225
226 #[derive(Default)]
228 struct Dir {
229 now: BTreeMap<OsString, Entry>,
230 synced: BTreeMap<OsString, Entry>,
231 }
232
233 struct File {
234 now: Vec<u8>,
235 synced: Vec<u8>,
236 }
237
238 fn not_found() -> io::Error {
239 io::Error::from(io::ErrorKind::NotFound)
240 }
241
242 fn split(path: &Path) -> io::Result<(PathBuf, OsString)> {
244 match (path.parent(), path.file_name()) {
245 (Some(dir), Some(name)) => Ok((dir.to_owned(), name.to_owned())),
246 _ => Err(io::Error::from(io::ErrorKind::InvalidInput)),
247 }
248 }
249
250 impl State {
251 fn alive(&self) -> io::Result<()> {
253 match self.fail_from {
254 Some(n) if self.log.len() >= n => Err(io::Error::other("the process has died")),
255 _ => Ok(()),
256 }
257 }
258
259 fn exists(&self, dir: &Path) -> bool {
262 if dir == self.root {
263 return true;
264 }
265 let Ok((parent, name)) = split(dir) else {
266 return false;
267 };
268 self.exists(&parent)
269 && self
270 .dirs
271 .get(&parent)
272 .is_some_and(|p| p.now.get(&name) == Some(&Entry::Dir))
273 }
274
275 fn dir(&mut self, dir: &Path) -> io::Result<&mut Dir> {
276 if !self.exists(dir) {
277 return Err(not_found());
278 }
279 self.dirs.get_mut(dir).ok_or_else(not_found)
280 }
281
282 fn file(&mut self, path: &Path) -> io::Result<usize> {
283 let (dir, name) = split(path)?;
284 match self.dir(&dir)?.now.get(&name) {
285 Some(Entry::File(index)) => Ok(*index),
286 _ => Err(not_found()),
287 }
288 }
289 }
290
291 impl CrashFs {
292 pub(crate) fn new(root: &Path) -> CrashFs {
294 CrashFs {
295 state: Mutex::new(State {
296 root: root.to_owned(),
297 dirs: BTreeMap::from([(root.to_owned(), Dir::default())]),
298 files: Vec::new(),
299 log: Vec::new(),
300 fail_from: None,
301 }),
302 }
303 }
304
305 fn state(&self) -> std::sync::MutexGuard<'_, State> {
306 self.state
307 .lock()
308 .expect("no test panicked holding the lock")
309 }
310
311 pub(crate) fn crash(&self) {
313 let mut state = self.state();
314 state.fail_from = None;
315 for dir in state.dirs.values_mut() {
316 dir.now = dir.synced.clone();
317 }
318 for file in &mut state.files {
319 file.now = file.synced.clone();
320 }
321 }
322
323 pub(crate) fn fail_from(&self, n: usize) {
327 self.state().fail_from = Some(n);
328 }
329
330 pub(crate) fn log(&self) -> Vec<Op> {
332 self.state().log.clone()
333 }
334 }
335
336 impl Fs for CrashFs {
337 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()> {
338 let (dir, name) = split(path)?;
339 let mut state = self.state();
340 state.alive()?;
341 let index = state.files.len();
342 let entries = &mut state.dir(&dir)?.now;
343 if entries.contains_key(&name) {
344 return Err(io::Error::from(io::ErrorKind::AlreadyExists));
345 }
346 entries.insert(name, Entry::File(index));
347 state.files.push(File {
348 now: data.to_vec(),
349 synced: Vec::new(),
350 });
351 state.log.push(Op::Write(path.to_owned()));
352 Ok(())
353 }
354
355 fn sync_file(&self, path: &Path) -> io::Result<()> {
356 let mut state = self.state();
357 state.alive()?;
358 let index = state.file(path)?;
359 let file = &mut state.files[index];
360 file.synced = file.now.clone();
361 state.log.push(Op::SyncFile(path.to_owned()));
362 Ok(())
363 }
364
365 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
366 let ((from_dir, from_name), (to_dir, to_name)) = (split(from)?, split(to)?);
367 let mut state = self.state();
368 state.alive()?;
369 let index = state.file(from)?;
370 state.dir(&to_dir)?;
371 state.dir(&from_dir)?.now.remove(&from_name);
372 state.dir(&to_dir)?.now.insert(to_name, Entry::File(index));
373 state.log.push(Op::Rename(from.to_owned(), to.to_owned()));
374 Ok(())
375 }
376
377 fn rename_new(&self, from: &Path, to: &Path) -> io::Result<()> {
378 let (to_dir, to_name) = split(to)?;
379 {
380 let mut state = self.state();
381 state.alive()?;
382 if state.dir(&to_dir)?.now.contains_key(&to_name) {
383 return Err(io::Error::from(io::ErrorKind::AlreadyExists));
384 }
385 }
386 self.rename(from, to)
387 }
388
389 fn sync_dir(&self, dir: &Path) -> io::Result<()> {
390 let mut state = self.state();
391 state.alive()?;
392 let entries = state.dir(dir)?;
393 entries.synced = entries.now.clone();
394 state.log.push(Op::SyncDir(dir.to_owned()));
395 Ok(())
396 }
397
398 fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
399 let mut state = self.state();
400 let index = state.file(path)?;
401 Ok(state.files[index].now.clone())
402 }
403
404 fn remove(&self, path: &Path) -> io::Result<()> {
405 let (dir, name) = split(path)?;
406 let mut state = self.state();
407 state.alive()?;
408 state.file(path)?;
409 state.dir(&dir)?.now.remove(&name);
410 state.log.push(Op::Remove(path.to_owned()));
411 Ok(())
412 }
413
414 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>> {
415 Ok(self.state().dir(dir)?.now.keys().cloned().collect())
416 }
417
418 fn create_dir_all(&self, dir: &Path) -> io::Result<()> {
419 let mut state = self.state();
420 state.alive()?;
421 let below = dir
422 .strip_prefix(&state.root)
423 .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?
424 .to_owned();
425 let mut path = state.root.clone();
428 let mut missing = Vec::new();
429 for part in &below {
430 let next = path.join(part);
431 let parent = std::mem::replace(&mut path, next);
432 if !missing.is_empty() {
433 missing.push(path.clone());
434 continue;
435 }
436 match state.dir(&parent)?.now.get(part) {
437 Some(Entry::Dir) => {}
438 Some(Entry::File(_)) => {
439 return Err(io::Error::from(io::ErrorKind::AlreadyExists));
440 }
441 None => missing.push(path.clone()),
442 }
443 }
444 if missing.is_empty() {
445 return Ok(());
446 }
447 std::fs::create_dir_all(dir)?;
448 for path in missing {
449 let (parent, name) = split(&path)?;
450 state.dir(&parent)?.now.insert(name, Entry::Dir);
451 state.dirs.insert(path, Dir::default());
452 }
453 state.log.push(Op::CreateDir(dir.to_owned()));
454 Ok(())
455 }
456
457 fn remove_dir_all(&self, dir: &Path) -> io::Result<()> {
458 let (parent, name) = split(dir)?;
459 let mut state = self.state();
460 state.alive()?;
461 if !state.exists(dir) {
462 return Err(not_found());
463 }
464 state.dir(&parent)?.now.remove(&name);
465 state.log.push(Op::RemoveDir(dir.to_owned()));
466 Ok(())
467 }
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use std::io::ErrorKind::{AlreadyExists, NotFound};
474 use std::path::PathBuf;
475
476 use super::*;
477
478 fn behaves_like_a_filesystem(fs: &dyn Fs, root: &Path) {
480 let dir = root.join("objects");
481 let (a, b) = (dir.join("a"), dir.join("b"));
482 assert_eq!(
483 fs.write_new(&a, b"x").map_err(|e| e.kind()),
484 Err(NotFound),
485 "no dir yet"
486 );
487 fs.create_dir_all(&dir).expect("create the dir");
488 fs.create_dir_all(&dir).expect("create it again");
489 fs.write_new(&a, b"one").expect("write");
490 assert_eq!(
491 fs.write_new(&a, b"two").map_err(|e| e.kind()),
492 Err(AlreadyExists),
493 "write_new never replaces a file"
494 );
495 assert!(fs.create_dir_all(&a).is_err(), "a file has the name");
496 assert!(
497 fs.create_dir_all(&a.join("d")).is_err(),
498 "a file stands in the way"
499 );
500 assert!(!a.join("d").exists(), "and nothing is made on disk");
501 assert_eq!(
502 fs.read(&a).expect("read"),
503 b"one",
504 "the refused write left the first one"
505 );
506 fs.sync_file(&a).expect("sync the file");
507 fs.sync_dir(&dir).expect("sync the dir");
508 fs.rename(&a, &b).expect("rename");
509 assert_eq!(
510 fs.read(&a).map_err(|e| e.kind()),
511 Err(NotFound),
512 "rename moves the file away"
513 );
514 assert_eq!(
515 fs.read(&b).expect("read"),
516 b"one",
517 "the file is under its new name"
518 );
519 fs.rename(&b, &b).expect("rename onto itself");
520 assert_eq!(
521 fs.read(&b).expect("read"),
522 b"one",
523 "renaming onto itself changes nothing"
524 );
525 fs.write_new(&a, b"three").expect("write again");
526 fs.rename(&a, &b).expect("rename over");
527 assert_eq!(
528 fs.read(&b).expect("read"),
529 b"three",
530 "rename replaces the target"
531 );
532 fs.write_new(&a, b"four").expect("write");
533 fs.create_dir_all(&dir.join("c"))
534 .expect("a directory inside");
535 assert_eq!(
536 fs.list(&dir).expect("list"),
537 ["a", "b", "c"].map(OsString::from),
538 "files and directories, sorted"
539 );
540 assert_eq!(
541 fs.write_new(&dir.join("c"), b"x").map_err(|e| e.kind()),
542 Err(AlreadyExists),
543 "a directory's name is taken"
544 );
545 assert!(
546 fs.remove(&dir.join("c")).is_err(),
547 "remove takes only a file"
548 );
549 assert!(
550 fs.remove_dir_all(&a).is_err(),
551 "remove_dir_all takes only a directory"
552 );
553 fs.create_dir_all(&dir.join("c/d"))
554 .expect("a directory inside c");
555 fs.write_new(&dir.join("c/f"), b"x")
556 .expect("a file inside c");
557 fs.remove_dir_all(&dir.join("c"))
558 .expect("remove c and all in it");
559 assert_eq!(
560 fs.list(&dir.join("c")).map_err(|e| e.kind()),
561 Err(NotFound),
562 "remove_dir_all removed c itself"
563 );
564 assert_eq!(
565 fs.remove_dir_all(&dir.join("c")).map_err(|e| e.kind()),
566 Err(NotFound),
567 "a directory that is gone cannot be removed again"
568 );
569 fs.create_dir_all(&dir.join("c")).expect("c again");
570 fs.remove(&a).expect("remove");
571 fs.remove(&b).expect("remove");
572 assert_eq!(
573 fs.list(&dir).expect("list"),
574 [OsString::from("c")],
575 "the removed files are gone and c is left"
576 );
577 assert_eq!(
578 fs.remove(&b).map_err(|e| e.kind()),
579 Err(NotFound),
580 "b was removed"
581 );
582 assert_eq!(
583 fs.sync_file(&b).map_err(|e| e.kind()),
584 Err(NotFound),
585 "b was removed"
586 );
587 assert_eq!(
588 fs.sync_dir(&root.join("none")).map_err(|e| e.kind()),
589 Err(NotFound),
590 "no directory has that name"
591 );
592 assert_eq!(
593 fs.list(&root.join("none")).map_err(|e| e.kind()),
594 Err(NotFound),
595 "no directory has that name"
596 );
597 let (x, y, z) = (dir.join("x"), dir.join("y"), dir.join("z"));
598 fs.write_new(&x, b"five").expect("write x");
599 fs.write_new(&y, b"six").expect("write y");
600 assert_eq!(
601 fs.rename_new(&x, &y).map_err(|e| e.kind()),
602 Err(AlreadyExists),
603 "rename_new never replaces a file"
604 );
605 assert_eq!(fs.read(&x).expect("read x"), b"five", "x is left");
606 assert_eq!(fs.read(&y).expect("read y"), b"six", "y is left");
607 fs.rename_new(&x, &z).expect("rename onto a free name");
608 assert_eq!(fs.read(&z).expect("read z"), b"five", "under its new name");
609 assert_eq!(
610 fs.read(&x).map_err(|e| e.kind()),
611 Err(NotFound),
612 "rename_new moves the file away"
613 );
614 }
615
616 #[test]
617 fn the_os_filesystem_behaves_like_a_filesystem() {
618 let root = tempfile::tempdir().expect("temp dir");
619 behaves_like_a_filesystem(&OsFs, root.path());
620 }
621
622 #[test]
623 fn the_crash_filesystem_behaves_like_a_filesystem() {
624 let root = tempfile::tempdir().expect("temp dir");
625 behaves_like_a_filesystem(&CrashFs::new(root.path()), root.path());
626 }
627
628 struct Model {
630 root: tempfile::TempDir,
631 fs: CrashFs,
632 }
633
634 impl Model {
635 fn new() -> Model {
636 let root = tempfile::tempdir().expect("temp dir");
637 let fs = CrashFs::new(root.path());
638 Model { root, fs }
639 }
640
641 fn root(&self) -> &Path {
642 self.root.path()
643 }
644
645 fn dir(&self) -> PathBuf {
647 self.root().join("objects")
648 }
649
650 fn f(&self) -> PathBuf {
651 self.dir().join("f")
652 }
653 }
654
655 fn after(steps: impl FnOnce(&CrashFs, &Path)) -> Model {
657 let m = Model::new();
658 m.fs.create_dir_all(&m.dir()).expect("create the dir");
659 m.fs.sync_dir(m.root()).expect("sync the root");
660 steps(&m.fs, &m.f());
661 m
662 }
663
664 fn synced(fs: &CrashFs, f: &Path) {
665 fs.write_new(f, b"data").expect("write");
666 fs.sync_file(f).expect("sync the file");
667 fs.sync_dir(f.parent().expect("a dir"))
668 .expect("sync the dir");
669 }
670
671 #[test]
672 fn a_crash_drops_a_file_whose_entry_was_not_synced() {
673 for sync_data in [false, true] {
674 let m = after(|fs, f| {
675 fs.write_new(f, b"data").expect("write");
676 if sync_data {
677 fs.sync_file(f).expect("sync the file");
678 }
679 });
680 m.fs.crash();
681 assert_eq!(
682 m.fs.read(&m.f()).map_err(|e| e.kind()),
683 Err(NotFound),
684 "the entry was never synced, so the crash loses the file (data synced: {sync_data})"
685 );
686 }
687 }
688
689 #[test]
690 fn a_crash_empties_a_file_whose_data_was_not_synced() {
691 let m = after(|fs, f| {
692 fs.write_new(f, b"data").expect("write");
693 fs.sync_dir(f.parent().expect("a dir"))
694 .expect("sync the dir");
695 });
696 m.fs.crash();
697 assert_eq!(
698 m.fs.read(&m.f()).expect("the entry survives"),
699 b"",
700 "the entry was synced but the data was not, so the file comes back empty"
701 );
702 }
703
704 #[test]
705 fn a_crash_keeps_a_file_synced_in_both_ways() {
706 let m = after(synced);
707 m.fs.crash();
708 assert_eq!(
709 m.fs.read(&m.f()).expect("survives"),
710 b"data",
711 "data and entry were both synced, so the crash keeps the file whole"
712 );
713 }
714
715 #[test]
716 fn a_rename_or_remove_survives_a_crash_only_once_the_dir_is_synced() {
717 for sync in [false, true] {
718 let renamed = after(|fs, f| {
719 synced(fs, f);
720 fs.rename(f, &f.with_file_name("g")).expect("rename");
721 if sync {
722 fs.sync_dir(f.parent().expect("a dir"))
723 .expect("sync the dir");
724 }
725 });
726 renamed.fs.crash();
727 let names = if sync { ["g"] } else { ["f"] };
728 assert_eq!(
729 renamed.fs.list(&renamed.dir()).expect("list"),
730 names.map(OsString::from),
731 "rename, sync {sync}"
732 );
733 assert_eq!(
734 renamed
735 .fs
736 .read(&renamed.dir().join(names[0]))
737 .expect("read"),
738 b"data",
739 "whichever name survives, the synced data is under it"
740 );
741
742 let removed = after(|fs, f| {
743 synced(fs, f);
744 fs.remove(f).expect("remove");
745 if sync {
746 fs.sync_dir(f.parent().expect("a dir"))
747 .expect("sync the dir");
748 }
749 });
750 removed.fs.crash();
751 let left = removed.fs.list(&removed.dir()).expect("list").len();
752 assert_eq!(left, usize::from(!sync), "remove, sync {sync}");
753 }
754 }
755
756 #[test]
757 fn a_new_directory_survives_a_crash_only_once_its_parent_is_synced() {
758 let m = Model::new();
759 let (a, b) = (m.root().join("a"), m.root().join("a/b"));
760 m.fs.create_dir_all(&b).expect("create a/b");
761 m.fs.crash();
762 assert_eq!(
763 m.fs.list(m.root()).expect("list"),
764 Vec::<OsString>::new(),
765 "nothing was synced, so the crash loses a"
766 );
767 assert_eq!(
768 m.fs.list(&a).map_err(|e| e.kind()),
769 Err(NotFound),
770 "a is gone after the crash"
771 );
772
773 m.fs.create_dir_all(&b).expect("create a/b");
774 m.fs.sync_dir(&a).expect("sync a, not the root");
775 m.fs.crash();
776 assert_eq!(
777 m.fs.list(&b).map_err(|e| e.kind()),
778 Err(NotFound),
779 "b is lost with a"
780 );
781
782 m.fs.create_dir_all(&b).expect("create a/b");
783 m.fs.sync_dir(m.root()).expect("sync the root");
784 m.fs.crash();
785 assert_eq!(
786 m.fs.list(m.root()).expect("list"),
787 [OsString::from("a")],
788 "the root was synced, so its entry for a survives"
789 );
790 assert_eq!(
791 m.fs.list(&a).expect("a survives"),
792 Vec::<OsString>::new(),
793 "b's entry in a was never synced"
794 );
795
796 m.fs.create_dir_all(&b).expect("create b");
797 m.fs.sync_dir(&a).expect("sync a");
798 m.fs.crash();
799 assert_eq!(
800 m.fs.list(&b).expect("b survives"),
801 Vec::<OsString>::new(),
802 "a was synced, so its entry for b survives, and b is empty"
803 );
804 }
805
806 #[test]
807 fn a_lost_directory_takes_its_synced_files_with_it() {
808 let m = Model::new();
809 let a = m.root().join("a");
810 m.fs.create_dir_all(&a).expect("create a");
811 synced(&m.fs, &a.join("f"));
812 m.fs.crash();
813 assert_eq!(
814 m.fs.list(m.root()).expect("list"),
815 Vec::<OsString>::new(),
816 "the root was never synced, so a is lost with its synced file"
817 );
818 m.fs.create_dir_all(&a).expect("create a again");
819 assert_eq!(
820 m.fs.list(&a).expect("list"),
821 Vec::<OsString>::new(),
822 "a new directory starts empty"
823 );
824 }
825
826 #[test]
827 fn a_directory_removal_survives_a_crash_only_once_its_parent_is_synced() {
828 for sync in [false, true] {
829 let m = Model::new();
830 let a = m.root().join("a");
831 m.fs.create_dir_all(&a).expect("create a");
832 synced(&m.fs, &a.join("f"));
833 m.fs.sync_dir(m.root()).expect("sync the root");
834 m.fs.remove_dir_all(&a).expect("remove a");
835 if sync {
836 m.fs.sync_dir(m.root()).expect("sync the root");
837 }
838 m.fs.crash();
839 if sync {
840 assert_eq!(
841 m.fs.list(&a).map_err(|e| e.kind()),
842 Err(NotFound),
843 "the root was synced after the removal, so it holds"
844 );
845 } else {
846 assert_eq!(
847 m.fs.read(&a.join("f")).expect("back"),
848 b"data",
849 "the removal was never synced, so a comes back with its file"
850 );
851 }
852 }
853 }
854
855 #[test]
858 fn a_directory_is_removed_only_from_the_model() {
859 let m = Model::new();
860 let a = m.root().join("a");
861 m.fs.create_dir_all(&a).expect("create a");
862 m.fs.remove_dir_all(&a).expect("remove a");
863 assert!(
864 a.is_dir(),
865 "the directory stays on disk, for a crash that brings it back"
866 );
867 }
868
869 #[test]
872 fn a_directory_is_also_made_on_disk() {
873 let m = Model::new();
874 let b = m.root().join("a/b");
875 m.fs.create_dir_all(&b).expect("create a/b");
876 assert!(
877 b.is_dir(),
878 "the directory is on disk, where SQLite will open its files"
879 );
880 }
881
882 #[test]
883 fn nothing_outside_the_root_can_be_made() {
884 let m = Model::new();
885 let outside = m.root().with_file_name("elsewhere-5c1e");
886 assert!(
887 m.fs.create_dir_all(&outside).is_err(),
888 "the model refuses a path outside its root"
889 );
890 assert!(!outside.exists(), "and makes nothing there on disk");
891 }
892
893 #[test]
894 fn every_call_that_changes_state_is_logged_in_order() {
895 let m = Model::new();
896 let (dir, f, g) = (m.dir(), m.f(), m.dir().join("g"));
897 m.fs.create_dir_all(&dir).expect("create the dir");
898 m.fs.create_dir_all(&dir).expect("already there: no change");
899 synced(&m.fs, &f);
900 m.fs.rename(&f, &g).expect("rename");
901 m.fs.remove(&g).expect("remove");
902 m.fs.remove_dir_all(&dir).expect("remove the dir");
903 assert_eq!(
904 m.fs.log(),
905 [
906 Op::CreateDir(dir.clone()),
907 Op::Write(f.clone()),
908 Op::SyncFile(f.clone()),
909 Op::SyncDir(dir.clone()),
910 Op::Rename(f, g.clone()),
911 Op::Remove(g),
912 Op::RemoveDir(dir),
913 ],
914 "one entry per change, in order; the create that changed nothing is not logged"
915 );
916 }
917
918 #[test]
919 fn from_the_fail_point_on_no_change_happens_until_a_crash() {
920 let m = after(|fs, f| fs.write_new(f, b"data").expect("write"));
921 let ops = m.fs.log().len();
922 m.fs.fail_from(ops);
923 let (f, g) = (m.f(), m.dir().join("g"));
924 assert!(m.fs.sync_file(&f).is_err(), "sync");
925 assert!(m.fs.write_new(&g, b"x").is_err(), "write");
926 assert!(m.fs.rename(&f, &g).is_err(), "rename");
927 assert!(m.fs.sync_dir(&m.dir()).is_err(), "sync dir");
928 assert!(m.fs.remove(&f).is_err(), "remove");
929 assert!(
930 m.fs.create_dir_all(&m.dir().join("d")).is_err(),
931 "create dir"
932 );
933 assert!(m.fs.remove_dir_all(&m.dir()).is_err(), "remove dir");
934 assert!(!m.dir().join("d").exists(), "not made on disk either");
935 assert_eq!(m.fs.log().len(), ops, "nothing changed");
936 assert_eq!(
937 m.fs.read(&f).expect("reads still work"),
938 b"data",
939 "the fail point stops changes, not reads"
940 );
941 m.fs.crash();
942 m.fs.write_new(&g, b"x")
943 .expect("works again after the crash");
944 }
945}