1[cases.test_orphans_normal]
2in = "lfs.c"
3if = 'PROG_SIZE <= 0x3fe' # only works with one crc per commit
4code = '''
5    lfs_t lfs;
6    lfs_format(&lfs, cfg) => 0;
7    lfs_mount(&lfs, cfg) => 0;
8    lfs_mkdir(&lfs, "parent") => 0;
9    lfs_mkdir(&lfs, "parent/orphan") => 0;
10    lfs_mkdir(&lfs, "parent/child") => 0;
11    lfs_remove(&lfs, "parent/orphan") => 0;
12    lfs_unmount(&lfs) => 0;
13
14    // corrupt the child's most recent commit, this should be the update
15    // to the linked-list entry, which should orphan the orphan. Note this
16    // makes a lot of assumptions about the remove operation.
17    lfs_mount(&lfs, cfg) => 0;
18    lfs_dir_t dir;
19    lfs_dir_open(&lfs, &dir, "parent/child") => 0;
20    lfs_block_t block = dir.m.pair[0];
21    lfs_dir_close(&lfs, &dir) => 0;
22    lfs_unmount(&lfs) => 0;
23    uint8_t buffer[BLOCK_SIZE];
24    cfg->read(cfg, block, 0, buffer, BLOCK_SIZE) => 0;
25    int off = BLOCK_SIZE-1;
26    while (off >= 0 && buffer[off] == ERASE_VALUE) {
27        off -= 1;
28    }
29    memset(&buffer[off-3], BLOCK_SIZE, 3);
30    cfg->erase(cfg, block) => 0;
31    cfg->prog(cfg, block, 0, buffer, BLOCK_SIZE) => 0;
32    cfg->sync(cfg) => 0;
33
34    lfs_mount(&lfs, cfg) => 0;
35    struct lfs_info info;
36    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
37    lfs_stat(&lfs, "parent/child", &info) => 0;
38    lfs_fs_size(&lfs) => 8;
39    lfs_unmount(&lfs) => 0;
40
41    lfs_mount(&lfs, cfg) => 0;
42    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
43    lfs_stat(&lfs, "parent/child", &info) => 0;
44    lfs_fs_size(&lfs) => 8;
45    // this mkdir should both create a dir and deorphan, so size
46    // should be unchanged
47    lfs_mkdir(&lfs, "parent/otherchild") => 0;
48    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
49    lfs_stat(&lfs, "parent/child", &info) => 0;
50    lfs_stat(&lfs, "parent/otherchild", &info) => 0;
51    lfs_fs_size(&lfs) => 8;
52    lfs_unmount(&lfs) => 0;
53
54    lfs_mount(&lfs, cfg) => 0;
55    lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
56    lfs_stat(&lfs, "parent/child", &info) => 0;
57    lfs_stat(&lfs, "parent/otherchild", &info) => 0;
58    lfs_fs_size(&lfs) => 8;
59    lfs_unmount(&lfs) => 0;
60'''
61
62# test that we only run deorphan once per power-cycle
63[cases.test_orphans_no_orphans]
64in = 'lfs.c'
65code = '''
66    lfs_t lfs;
67    lfs_format(&lfs, cfg) => 0;
68
69    lfs_mount(&lfs, cfg) => 0;
70    // mark the filesystem as having orphans
71    lfs_fs_preporphans(&lfs, +1) => 0;
72    lfs_mdir_t mdir;
73    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
74    lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
75
76    // we should have orphans at this state
77    assert(lfs_gstate_hasorphans(&lfs.gstate));
78    lfs_unmount(&lfs) => 0;
79
80    // mount
81    lfs_mount(&lfs, cfg) => 0;
82    // we should detect orphans
83    assert(lfs_gstate_hasorphans(&lfs.gstate));
84    // force consistency
85    lfs_fs_forceconsistency(&lfs) => 0;
86    // we should no longer have orphans
87    assert(!lfs_gstate_hasorphans(&lfs.gstate));
88
89    lfs_unmount(&lfs) => 0;
90'''
91
92[cases.test_orphans_one_orphan]
93in = 'lfs.c'
94code = '''
95    lfs_t lfs;
96    lfs_format(&lfs, cfg) => 0;
97
98    lfs_mount(&lfs, cfg) => 0;
99    // create an orphan
100    lfs_mdir_t orphan;
101    lfs_alloc_ack(&lfs);
102    lfs_dir_alloc(&lfs, &orphan) => 0;
103    lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
104
105    // append our orphan and mark the filesystem as having orphans
106    lfs_fs_preporphans(&lfs, +1) => 0;
107    lfs_mdir_t mdir;
108    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
109    lfs_pair_tole32(orphan.pair);
110    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
111            {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0;
112
113    // we should have orphans at this state
114    assert(lfs_gstate_hasorphans(&lfs.gstate));
115    lfs_unmount(&lfs) => 0;
116
117    // mount
118    lfs_mount(&lfs, cfg) => 0;
119    // we should detect orphans
120    assert(lfs_gstate_hasorphans(&lfs.gstate));
121    // force consistency
122    lfs_fs_forceconsistency(&lfs) => 0;
123    // we should no longer have orphans
124    assert(!lfs_gstate_hasorphans(&lfs.gstate));
125
126    lfs_unmount(&lfs) => 0;
127'''
128
129# test that we can persist gstate with lfs_fs_mkconsistent
130[cases.test_orphans_mkconsistent_no_orphans]
131in = 'lfs.c'
132code = '''
133    lfs_t lfs;
134    lfs_format(&lfs, cfg) => 0;
135
136    lfs_mount(&lfs, cfg) => 0;
137    // mark the filesystem as having orphans
138    lfs_fs_preporphans(&lfs, +1) => 0;
139    lfs_mdir_t mdir;
140    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
141    lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
142
143    // we should have orphans at this state
144    assert(lfs_gstate_hasorphans(&lfs.gstate));
145    lfs_unmount(&lfs) => 0;
146
147    // mount
148    lfs_mount(&lfs, cfg) => 0;
149    // we should detect orphans
150    assert(lfs_gstate_hasorphans(&lfs.gstate));
151    // force consistency
152    lfs_fs_mkconsistent(&lfs) => 0;
153    // we should no longer have orphans
154    assert(!lfs_gstate_hasorphans(&lfs.gstate));
155
156    // remount
157    lfs_unmount(&lfs) => 0;
158    lfs_mount(&lfs, cfg) => 0;
159    // we should still have no orphans
160    assert(!lfs_gstate_hasorphans(&lfs.gstate));
161    lfs_unmount(&lfs) => 0;
162'''
163
164[cases.test_orphans_mkconsistent_one_orphan]
165in = 'lfs.c'
166code = '''
167    lfs_t lfs;
168    lfs_format(&lfs, cfg) => 0;
169
170    lfs_mount(&lfs, cfg) => 0;
171    // create an orphan
172    lfs_mdir_t orphan;
173    lfs_alloc_ack(&lfs);
174    lfs_dir_alloc(&lfs, &orphan) => 0;
175    lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
176
177    // append our orphan and mark the filesystem as having orphans
178    lfs_fs_preporphans(&lfs, +1) => 0;
179    lfs_mdir_t mdir;
180    lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
181    lfs_pair_tole32(orphan.pair);
182    lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
183            {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), orphan.pair})) => 0;
184
185    // we should have orphans at this state
186    assert(lfs_gstate_hasorphans(&lfs.gstate));
187    lfs_unmount(&lfs) => 0;
188
189    // mount
190    lfs_mount(&lfs, cfg) => 0;
191    // we should detect orphans
192    assert(lfs_gstate_hasorphans(&lfs.gstate));
193    // force consistency
194    lfs_fs_mkconsistent(&lfs) => 0;
195    // we should no longer have orphans
196    assert(!lfs_gstate_hasorphans(&lfs.gstate));
197
198    // remount
199    lfs_unmount(&lfs) => 0;
200    lfs_mount(&lfs, cfg) => 0;
201    // we should still have no orphans
202    assert(!lfs_gstate_hasorphans(&lfs.gstate));
203    lfs_unmount(&lfs) => 0;
204'''
205
206# reentrant testing for orphans, basically just spam mkdir/remove
207[cases.test_orphans_reentrant]
208reentrant = true
209# TODO fix this case, caused by non-DAG trees
210if = '!(DEPTH == 3 && CACHE_SIZE != 64)'
211defines = [
212    {FILES=6,  DEPTH=1, CYCLES=20},
213    {FILES=26, DEPTH=1, CYCLES=20},
214    {FILES=3,  DEPTH=3, CYCLES=20},
215]
216code = '''
217    lfs_t lfs;
218    int err = lfs_mount(&lfs, cfg);
219    if (err) {
220        lfs_format(&lfs, cfg) => 0;
221        lfs_mount(&lfs, cfg) => 0;
222    }
223
224    uint32_t prng = 1;
225    const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
226    for (unsigned i = 0; i < CYCLES; i++) {
227        // create random path
228        char full_path[256];
229        for (unsigned d = 0; d < DEPTH; d++) {
230            sprintf(&full_path[2*d], "/%c", alpha[TEST_PRNG(&prng) % FILES]);
231        }
232
233        // if it does not exist, we create it, else we destroy
234        struct lfs_info info;
235        int res = lfs_stat(&lfs, full_path, &info);
236        if (res == LFS_ERR_NOENT) {
237            // create each directory in turn, ignore if dir already exists
238            for (unsigned d = 0; d < DEPTH; d++) {
239                char path[1024];
240                strcpy(path, full_path);
241                path[2*d+2] = '\0';
242                err = lfs_mkdir(&lfs, path);
243                assert(!err || err == LFS_ERR_EXIST);
244            }
245
246            for (unsigned d = 0; d < DEPTH; d++) {
247                char path[1024];
248                strcpy(path, full_path);
249                path[2*d+2] = '\0';
250                lfs_stat(&lfs, path, &info) => 0;
251                assert(strcmp(info.name, &path[2*d+1]) == 0);
252                assert(info.type == LFS_TYPE_DIR);
253            }
254        } else {
255            // is valid dir?
256            assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
257            assert(info.type == LFS_TYPE_DIR);
258
259            // try to delete path in reverse order, ignore if dir is not empty
260            for (int d = DEPTH-1; d >= 0; d--) {
261                char path[1024];
262                strcpy(path, full_path);
263                path[2*d+2] = '\0';
264                err = lfs_remove(&lfs, path);
265                assert(!err || err == LFS_ERR_NOTEMPTY);
266            }
267
268            lfs_stat(&lfs, full_path, &info) => LFS_ERR_NOENT;
269        }
270    }
271    lfs_unmount(&lfs) => 0;
272'''
273
274