1# Tests for recovering from conditions which shouldn't normally
2# happen during normal operation of littlefs
3
4# invalid pointer tests (outside of block_count)
5
6[[case]] # invalid tail-pointer test
7define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
8define.INVALSET = [0x3, 0x1, 0x2]
9in = "lfs.c"
10code = '''
11    // create littlefs
12    lfs_format(&lfs, &cfg) => 0;
13
14    // change tail-pointer to invalid pointers
15    lfs_init(&lfs, &cfg) => 0;
16    lfs_mdir_t mdir;
17    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
18    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
19            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
20                (lfs_block_t[2]){
21                    (INVALSET & 0x1) ? 0xcccccccc : 0,
22                    (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
23    lfs_deinit(&lfs) => 0;
24
25    // test that mount fails gracefully
26    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
27'''
28
29[[case]] # invalid dir pointer test
30define.INVALSET = [0x3, 0x1, 0x2]
31in = "lfs.c"
32code = '''
33    // create littlefs
34    lfs_format(&lfs, &cfg) => 0;
35    // make a dir
36    lfs_mount(&lfs, &cfg) => 0;
37    lfs_mkdir(&lfs, "dir_here") => 0;
38    lfs_unmount(&lfs) => 0;
39
40    // change the dir pointer to be invalid
41    lfs_init(&lfs, &cfg) => 0;
42    lfs_mdir_t mdir;
43    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
44    // make sure id 1 == our directory
45    lfs_dir_get(&lfs, &mdir,
46            LFS_MKTAG(0x700, 0x3ff, 0),
47            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
48                => LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here"));
49    assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
50    // change dir pointer
51    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
52            {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8),
53                (lfs_block_t[2]){
54                    (INVALSET & 0x1) ? 0xcccccccc : 0,
55                    (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
56    lfs_deinit(&lfs) => 0;
57
58    // test that accessing our bad dir fails, note there's a number
59    // of ways to access the dir, some can fail, but some don't
60    lfs_mount(&lfs, &cfg) => 0;
61    lfs_stat(&lfs, "dir_here", &info) => 0;
62    assert(strcmp(info.name, "dir_here") == 0);
63    assert(info.type == LFS_TYPE_DIR);
64
65    lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
66    lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
67    lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
68    lfs_file_open(&lfs, &file, "dir_here/file_here",
69            LFS_O_RDONLY) => LFS_ERR_CORRUPT;
70    lfs_file_open(&lfs, &file, "dir_here/file_here",
71            LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT;
72    lfs_unmount(&lfs) => 0;
73'''
74
75[[case]] # invalid file pointer test
76in = "lfs.c"
77define.SIZE = [10, 1000, 100000] # faked file size
78code = '''
79    // create littlefs
80    lfs_format(&lfs, &cfg) => 0;
81    // make a file
82    lfs_mount(&lfs, &cfg) => 0;
83    lfs_file_open(&lfs, &file, "file_here",
84            LFS_O_WRONLY | LFS_O_CREAT) => 0;
85    lfs_file_close(&lfs, &file) => 0;
86    lfs_unmount(&lfs) => 0;
87
88    // change the file pointer to be invalid
89    lfs_init(&lfs, &cfg) => 0;
90    lfs_mdir_t mdir;
91    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
92    // make sure id 1 == our file
93    lfs_dir_get(&lfs, &mdir,
94            LFS_MKTAG(0x700, 0x3ff, 0),
95            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
96                => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
97    assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
98    // change file pointer
99    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
100            {LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)),
101                &(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0;
102    lfs_deinit(&lfs) => 0;
103
104    // test that accessing our bad file fails, note there's a number
105    // of ways to access the dir, some can fail, but some don't
106    lfs_mount(&lfs, &cfg) => 0;
107    lfs_stat(&lfs, "file_here", &info) => 0;
108    assert(strcmp(info.name, "file_here") == 0);
109    assert(info.type == LFS_TYPE_REG);
110    assert(info.size == SIZE);
111
112    lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
113    lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
114    lfs_file_close(&lfs, &file) => 0;
115
116    // any allocs that traverse CTZ must unfortunately must fail
117    if (SIZE > 2*LFS_BLOCK_SIZE) {
118        lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
119    }
120    lfs_unmount(&lfs) => 0;
121'''
122
123[[case]] # invalid pointer in CTZ skip-list test
124define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE']
125in = "lfs.c"
126code = '''
127    // create littlefs
128    lfs_format(&lfs, &cfg) => 0;
129    // make a file
130    lfs_mount(&lfs, &cfg) => 0;
131    lfs_file_open(&lfs, &file, "file_here",
132            LFS_O_WRONLY | LFS_O_CREAT) => 0;
133    for (int i = 0; i < SIZE; i++) {
134        char c = 'c';
135        lfs_file_write(&lfs, &file, &c, 1) => 1;
136    }
137    lfs_file_close(&lfs, &file) => 0;
138    lfs_unmount(&lfs) => 0;
139    // change pointer in CTZ skip-list to be invalid
140    lfs_init(&lfs, &cfg) => 0;
141    lfs_mdir_t mdir;
142    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
143    // make sure id 1 == our file and get our CTZ structure
144    lfs_dir_get(&lfs, &mdir,
145            LFS_MKTAG(0x700, 0x3ff, 0),
146            LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
147                => LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
148    assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
149    struct lfs_ctz ctz;
150    lfs_dir_get(&lfs, &mdir,
151            LFS_MKTAG(0x700, 0x3ff, 0),
152            LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz)
153                => LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
154    lfs_ctz_fromle32(&ctz);
155    // rewrite block to contain bad pointer
156    uint8_t bbuffer[LFS_BLOCK_SIZE];
157    cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
158    uint32_t bad = lfs_tole32(0xcccccccc);
159    memcpy(&bbuffer[0], &bad, sizeof(bad));
160    memcpy(&bbuffer[4], &bad, sizeof(bad));
161    cfg.erase(&cfg, ctz.head) => 0;
162    cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
163    lfs_deinit(&lfs) => 0;
164
165    // test that accessing our bad file fails, note there's a number
166    // of ways to access the dir, some can fail, but some don't
167    lfs_mount(&lfs, &cfg) => 0;
168    lfs_stat(&lfs, "file_here", &info) => 0;
169    assert(strcmp(info.name, "file_here") == 0);
170    assert(info.type == LFS_TYPE_REG);
171    assert(info.size == SIZE);
172
173    lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
174    lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
175    lfs_file_close(&lfs, &file) => 0;
176
177    // any allocs that traverse CTZ must unfortunately must fail
178    if (SIZE > 2*LFS_BLOCK_SIZE) {
179        lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
180    }
181    lfs_unmount(&lfs) => 0;
182'''
183
184
185[[case]] # invalid gstate pointer
186define.INVALSET = [0x3, 0x1, 0x2]
187in = "lfs.c"
188code = '''
189    // create littlefs
190    lfs_format(&lfs, &cfg) => 0;
191
192    // create an invalid gstate
193    lfs_init(&lfs, &cfg) => 0;
194    lfs_mdir_t mdir;
195    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
196    lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
197            (INVALSET & 0x1) ? 0xcccccccc : 0,
198            (INVALSET & 0x2) ? 0xcccccccc : 0});
199    lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
200    lfs_deinit(&lfs) => 0;
201
202    // test that mount fails gracefully
203    // mount may not fail, but our first alloc should fail when
204    // we try to fix the gstate
205    lfs_mount(&lfs, &cfg) => 0;
206    lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
207    lfs_unmount(&lfs) => 0;
208'''
209
210# cycle detection/recovery tests
211
212[[case]] # metadata-pair threaded-list loop test
213in = "lfs.c"
214code = '''
215    // create littlefs
216    lfs_format(&lfs, &cfg) => 0;
217
218    // change tail-pointer to point to ourself
219    lfs_init(&lfs, &cfg) => 0;
220    lfs_mdir_t mdir;
221    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
222    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
223            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
224                (lfs_block_t[2]){0, 1}})) => 0;
225    lfs_deinit(&lfs) => 0;
226
227    // test that mount fails gracefully
228    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
229'''
230
231[[case]] # metadata-pair threaded-list 2-length loop test
232in = "lfs.c"
233code = '''
234    // create littlefs with child dir
235    lfs_format(&lfs, &cfg) => 0;
236    lfs_mount(&lfs, &cfg) => 0;
237    lfs_mkdir(&lfs, "child") => 0;
238    lfs_unmount(&lfs) => 0;
239
240    // find child
241    lfs_init(&lfs, &cfg) => 0;
242    lfs_mdir_t mdir;
243    lfs_block_t pair[2];
244    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
245    lfs_dir_get(&lfs, &mdir,
246            LFS_MKTAG(0x7ff, 0x3ff, 0),
247            LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
248                => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
249    lfs_pair_fromle32(pair);
250    // change tail-pointer to point to root
251    lfs_dir_fetch(&lfs, &mdir, pair) => 0;
252    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
253            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
254                (lfs_block_t[2]){0, 1}})) => 0;
255    lfs_deinit(&lfs) => 0;
256
257    // test that mount fails gracefully
258    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
259'''
260
261[[case]] # metadata-pair threaded-list 1-length child loop test
262in = "lfs.c"
263code = '''
264    // create littlefs with child dir
265    lfs_format(&lfs, &cfg) => 0;
266    lfs_mount(&lfs, &cfg) => 0;
267    lfs_mkdir(&lfs, "child") => 0;
268    lfs_unmount(&lfs) => 0;
269
270    // find child
271    lfs_init(&lfs, &cfg) => 0;
272    lfs_mdir_t mdir;
273    lfs_block_t pair[2];
274    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
275    lfs_dir_get(&lfs, &mdir,
276            LFS_MKTAG(0x7ff, 0x3ff, 0),
277            LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
278                => LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
279    lfs_pair_fromle32(pair);
280    // change tail-pointer to point to ourself
281    lfs_dir_fetch(&lfs, &mdir, pair) => 0;
282    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
283            {LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
284    lfs_deinit(&lfs) => 0;
285
286    // test that mount fails gracefully
287    lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
288'''
289