Skip to main content

admin_app/
migrations.rs

1use littlefs2_core::DynFilesystem;
2
3#[derive(Debug)]
4pub struct Migrator {
5    /// The function performing the migration
6    ///
7    /// First argument is the Internal Filesystem, second argument is the External
8    pub migrate: fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), littlefs2_core::Error>,
9
10    /// The version of the storage for which the migration needs to be run
11    pub version: u32,
12}
13
14#[cfg(feature = "migration-tests")]
15pub mod test_utils {
16
17    // Copyright (C) Nitrokey GmbH
18    // SPDX-License-Identifier: Apache-2.0 or MIT
19
20    use littlefs2::{
21        fs::Filesystem, io::Error, object_safe::DynFilesystem, path, path::Path, ram_storage,
22    };
23
24    /// Represent a directory of data
25    pub enum FsValues {
26        Dir(&'static [(&'static Path, FsValues)]),
27        File(usize),
28    }
29
30    type Result<T, E = Error> = core::result::Result<T, E>;
31
32    /// Prepare the filesystem for a given tests values
33    pub fn prepare_fs(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) {
34        match value {
35            FsValues::File(f_data_len) => {
36                fs.create_file_and_then(path, &mut |f| {
37                    f.set_len(*f_data_len).unwrap();
38                    Ok(())
39                })
40                .unwrap();
41            }
42            FsValues::Dir(d) => {
43                if path != path!("/") {
44                    fs.create_dir(path).unwrap();
45                }
46                for (p, v) in *d {
47                    prepare_fs(fs, v, &path.join(p));
48                }
49            }
50        }
51    }
52
53    /// Test equality between the filesystem and the expected values
54    pub fn test_fs_equality(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) {
55        match value {
56            FsValues::Dir(d) => {
57                let mut expected_iter = d.iter();
58                fs.read_dir_and_then(path, &mut |dir| {
59                    // skip . and ..
60                    dir.next().unwrap().unwrap();
61                    dir.next().unwrap().unwrap();
62                    for (expected_path, expected_values) in expected_iter.by_ref() {
63                        let entry = dir.next().unwrap().unwrap();
64                        assert_eq!(entry.file_name(), *expected_path);
65                        test_fs_equality(fs, expected_values, &path.join(expected_path));
66                    }
67                    assert!(dir.next().is_none());
68                    Ok(())
69                })
70                .unwrap();
71            }
72            FsValues::File(f_data_len) => {
73                fs.open_file_and_then(path, &mut |f| {
74                    let mut buf = [0; 512];
75                    let data = f.read(&mut buf).unwrap();
76                    assert_eq!(data, *f_data_len);
77                    Ok(())
78                })
79                .unwrap();
80            }
81        }
82    }
83
84    ram_storage!(
85        name = NoBackendStorage,
86        backend = RamDirect,
87        erase_value = 0xff,
88        read_size = 16,
89        write_size = 16,
90        cache_size_ty = littlefs2::consts::U512,
91        block_size = 512,
92        block_count = 128,
93        lookahead_size_ty = littlefs2::consts::U8,
94    );
95
96    pub fn test_migration_one(
97        before: &FsValues,
98        after: &FsValues,
99        migrate: impl Fn(&dyn DynFilesystem) -> Result<(), Error>,
100    ) {
101        test_migration(
102            before,
103            after,
104            &FsValues::Dir(&[]),
105            &FsValues::Dir(&[]),
106            |ifs, _efs| migrate(ifs),
107        );
108    }
109
110    pub fn test_migration(
111        before_ifs: &FsValues,
112        after_ifs: &FsValues,
113        before_efs: &FsValues,
114        after_efs: &FsValues,
115        migrate: impl Fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), Error>,
116    ) {
117        let mut storage_ifs = RamDirect::default();
118        let mut storage_efs = RamDirect::default();
119
120        let backend_efs = &mut NoBackendStorage::new(&mut storage_efs);
121        let backend_ifs = &mut NoBackendStorage::new(&mut storage_ifs);
122
123        Filesystem::format(backend_ifs).unwrap();
124        Filesystem::format(backend_efs).unwrap();
125
126        Filesystem::mount_and_then(backend_ifs, |ifs| {
127            Filesystem::mount_and_then(backend_efs, |efs| {
128                prepare_fs(ifs, before_ifs, path!("/"));
129                prepare_fs(efs, before_efs, path!("/"));
130
131                test_fs_equality(ifs, before_ifs, path!("/"));
132                test_fs_equality(efs, before_efs, path!("/"));
133
134                migrate(ifs, efs).unwrap();
135                test_fs_equality(efs, after_efs, path!("/"));
136                test_fs_equality(ifs, after_ifs, path!("/"));
137                Ok(())
138            })
139            .unwrap();
140            Ok(())
141        })
142        .unwrap();
143    }
144}