Skip to main content

vcon_server/
fs.rs

1//! The filesystem calls Store makes for object files, behind an interface
2//! so tests can simulate a crash. The catalog is SQLite, which does its own
3//! I/O and is durable once a commit returns.
4
5use 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
11/// The mode of every file the server makes: the service account's alone,
12/// whatever the umask of the process. The umask can only take bits away,
13/// and these have none for group or others to lose.
14pub const FILE_MODE: u32 = 0o600;
15
16/// The mode of every directory the server makes, as `FILE_MODE`.
17pub const DIR_MODE: u32 = 0o700;
18
19/// The filesystem calls Store makes.
20pub trait Fs: Send + Sync {
21    /// Creates a file that must not exist yet and writes `data` to it,
22    /// without syncing.
23    ///
24    /// # Errors
25    ///
26    /// `AlreadyExists` if the path is taken, `NotFound` if its directory
27    /// does not exist, or any other I/O error.
28    fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()>;
29    /// Makes a file's data durable.
30    ///
31    /// # Errors
32    ///
33    /// `NotFound` if there is no such file, or any other I/O error.
34    fn sync_file(&self, path: &Path) -> io::Result<()>;
35    /// Renames a file, replacing any file at `to`.
36    ///
37    /// # Errors
38    ///
39    /// The I/O error that stopped the rename.
40    fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
41    /// Renames a file only if nothing has the name `to`, in one step: of
42    /// two callers renaming onto one name, one is refused.
43    ///
44    /// # Errors
45    ///
46    /// `AlreadyExists` if `to` is taken, or the I/O error that stopped the
47    /// rename.
48    fn rename_new(&self, from: &Path, to: &Path) -> io::Result<()>;
49    /// Makes a directory's entries durable: files created, renamed or
50    /// removed in it.
51    ///
52    /// # Errors
53    ///
54    /// `NotFound` if there is no such directory, or any other I/O error.
55    fn sync_dir(&self, dir: &Path) -> io::Result<()>;
56    /// Reads a whole file.
57    ///
58    /// # Errors
59    ///
60    /// `NotFound` if there is no such file, or any other I/O error.
61    fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
62    /// Removes a file.
63    ///
64    /// # Errors
65    ///
66    /// `NotFound` if there is no such file, an error if the path is a
67    /// directory, or any other I/O error.
68    fn remove(&self, path: &Path) -> io::Result<()>;
69    /// The names in a directory, sorted.
70    ///
71    /// # Errors
72    ///
73    /// `NotFound` if there is no such directory, or any other I/O error.
74    fn list(&self, dir: &Path) -> io::Result<Vec<OsString>>;
75    /// Creates a directory and its parents, without syncing. A new
76    /// directory survives a crash once the directory holding it is synced.
77    ///
78    /// # Errors
79    ///
80    /// An error if a file stands in the path, or any other I/O error.
81    fn create_dir_all(&self, dir: &Path) -> io::Result<()>;
82    /// Removes a directory and everything in it, without syncing. The
83    /// removal survives a crash once the directory that held it is synced.
84    ///
85    /// # Errors
86    ///
87    /// `NotFound` if there is no such directory, an error if the path is a
88    /// file, or any other I/O error.
89    fn remove_dir_all(&self, dir: &Path) -> io::Result<()>;
90}
91
92/// The operating system's filesystem. No test can see whether a sync
93/// reached the disk without cutting power, so Store's crash tests use the
94/// crash filesystem's model of syncing instead.
95#[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        // SAFETY: both paths are NUL-terminated strings that live across the
124        // call, and renameat2 reads only them and its flags.
125        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    /// One call, as the crash filesystem logs it.
183    #[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    /// An in-memory filesystem that keeps what was synced apart from what
195    /// was not, so a simulated crash can drop the latter. A file's data
196    /// survives a crash once the file is synced; an entry in a directory, a
197    /// file or a directory made, renamed or removed there, once that
198    /// directory is synced. A directory lost in a crash takes everything
199    /// in it.
200    ///
201    /// The model is rooted at a directory that exists on disk and is
202    /// durable. A directory made in the model is made on disk too, because
203    /// SQLite opens its catalogs there; a crash drops it only from the model.
204    /// A directory is removed only from the model, since a crash may bring
205    /// it back and SQLite's files in it must then still be there.
206    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        /// From this many changes on, every change fails.
216        fail_from: Option<usize>,
217    }
218
219    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
220    enum Entry {
221        /// An index into `State::files`.
222        File(usize),
223        Dir,
224    }
225
226    /// A directory's entries, as they are and as a crash would leave them.
227    #[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    /// A path's directory and name.
243    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        /// Fails once the fail point is reached, as a dead process would.
252        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        /// Whether `dir` is the root or is reached from it through
260        /// entries as they are now.
261        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        /// A model rooted at `root`, an existing directory.
293        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        /// Loses everything not yet synced.
312        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        /// From the `n`th change on (counting from 0 over the whole log),
324        /// every change fails and changes nothing, as for a process that
325        /// has died. `crash` clears it.
326        pub(crate) fn fail_from(&self, n: usize) {
327            self.state().fail_from = Some(n);
328        }
329
330        /// Every change so far, in order.
331        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            // Find what is missing before touching the disk, so a refusal
426            // makes nothing there either.
427            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    /// What every implementation must do, crash or no crash.
479    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    /// A crash filesystem rooted at a temporary directory.
629    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        /// The directory the file tests use, made and synced.
646        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    /// A model with a durable `dir()`, after `steps` on its file `f()`.
656    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    /// A crash may bring a removed directory back, and SQLite's files in
856    /// it must then still be there, so removal touches only the model.
857    #[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    /// SQLite opens its files on disk, so a directory made in the model is
870    /// made on disk too.
871    #[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}