Skip to content

Commit c25c893

Browse files
committed
Moved to brute-force deorphan without parent pointers
Removing the dependency to the parent pointer solves many issues with non-atomic updates of children's parent pointers with respect to any move operations. However, this comes with an embarrassingly terrible runtime as the only other option is to exhaustively check every dir entry to find a child's parent. Fortunately, deorphaning should be a relatively rare operation.
1 parent 96a4258 commit c25c893

File tree

5 files changed

+126
-67
lines changed

5 files changed

+126
-67
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ size: $(OBJ)
3131
$(SIZE) -t $^
3232

3333
.SUFFIXES:
34-
test: test_format test_dirs test_files test_alloc
34+
test: test_format test_dirs test_files test_alloc test_orphan
3535
test_%: tests/test_%.sh
3636
./$<
3737

lfs.c

Lines changed: 73 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block,
7575

7676
/// Block allocator ///
7777

78-
// predeclared filesystem traversal
79-
static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
80-
int lfs_deorphan(lfs_t *lfs);
81-
8278
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
8379
lfs_t *lfs = p;
8480

@@ -1141,7 +1137,7 @@ int lfs_unmount(lfs_t *lfs) {
11411137
return 0;
11421138
}
11431139

1144-
static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
1140+
int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
11451141
// iterate over metadata pairs
11461142
lfs_dir_t dir;
11471143
lfs_file_t file;
@@ -1199,83 +1195,94 @@ static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
11991195
}
12001196
}
12011197

1202-
int lfs_deorphan(lfs_t *lfs) {
1203-
// iterate over all directories
1204-
lfs_block_t pred[2] = {0, 1};
1205-
lfs_block_t cwd[2] = {lfs->root[0], lfs->root[1]};
1198+
static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2]) {
1199+
// iterate over all directory directory entries
1200+
lfs_dir_t parent = {
1201+
.d.tail[0] = lfs->root[0],
1202+
.d.tail[1] = lfs->root[1],
1203+
};
12061204

1207-
while (true) {
1208-
lfs_dir_t child;
1209-
int err = lfs_dir_fetch(lfs, &child, cwd);
1205+
while (parent.d.tail[0]) {
1206+
lfs_entry_t entry;
1207+
int err = lfs_dir_fetch(lfs, &parent, parent.d.tail);
12101208
if (err) {
12111209
return err;
12121210
}
12131211

1214-
// orphans can only be empty dirs
1215-
// there still might be a dir block with this size that isn't
1216-
// the head of a directory, so we still have to check for '..'
1217-
if (child.d.size == sizeof(child.d) +
1218-
2*sizeof(struct lfs_disk_entry) + 3) {
1219-
lfs_entry_t entry;
1220-
err = lfs_dir_find(lfs, &child, &(const char*){".."}, &entry);
1221-
if (err && err != LFS_ERROR_NO_ENTRY) {
1212+
// skip .. and . entries
1213+
for (int i = 0; i < 2; i++) {
1214+
int err = lfs_dir_next(lfs, &parent, &entry);
1215+
if (err) {
12221216
return err;
12231217
}
1218+
}
12241219

1225-
// only the head of directories can be orphans
1226-
if (err != LFS_ERROR_NO_ENTRY) {
1227-
lfs_dir_t dir;
1228-
int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
1229-
if (err) {
1230-
return err;
1231-
}
1220+
while (true) {
1221+
int err = lfs_dir_next(lfs, &parent, &entry);
1222+
if (err && err != LFS_ERROR_NO_ENTRY) {
1223+
return err;
1224+
}
12321225

1233-
// check if we are any of our parents children
1234-
while (true) {
1235-
int err = lfs_dir_next(lfs, &dir, &entry);
1236-
if (err && err != LFS_ERROR_NO_ENTRY) {
1237-
return err;
1238-
}
1226+
if (err == LFS_ERROR_NO_ENTRY) {
1227+
break;
1228+
}
12391229

1240-
if (err == LFS_ERROR_NO_ENTRY) {
1241-
// we are an orphan
1242-
LFS_INFO("Found orphan %d %d", cwd[0], cwd[1]);
1243-
int err = lfs_dir_fetch(lfs, &dir, pred);
1244-
if (err) {
1245-
return err;
1246-
}
1247-
1248-
dir.d.tail[0] = child.d.tail[0];
1249-
dir.d.tail[1] = child.d.tail[1];
1250-
dir.d.rev += 1;
1251-
1252-
err = lfs_pair_commit(lfs, dir.pair,
1253-
1, (struct lfs_commit_region[]) {
1254-
{0, sizeof(dir.d), &dir.d},
1255-
});
1256-
if (err) {
1257-
return err;
1258-
}
1259-
1260-
break;
1261-
} else if (lfs_paircmp(entry.d.u.dir, cwd) == 0) {
1262-
// has parent
1263-
break;
1264-
}
1265-
}
1230+
if ((0xf & entry.d.type) == LFS_TYPE_DIR &&
1231+
lfs_paircmp(entry.d.u.dir, dir) == 0) {
1232+
return true;
12661233
}
12671234
}
1235+
}
1236+
1237+
return false;
1238+
}
1239+
1240+
int lfs_deorphan(lfs_t *lfs) {
1241+
// iterate over all directories
1242+
lfs_dir_t pdir;
1243+
lfs_dir_t cdir;
1244+
1245+
// skip root
1246+
int err = lfs_dir_fetch(lfs, &pdir, lfs->root);
1247+
if (err) {
1248+
return err;
1249+
}
1250+
1251+
while (pdir.d.tail[0]) {
1252+
int err = lfs_dir_fetch(lfs, &cdir, pdir.d.tail);
1253+
if (err) {
1254+
return err;
1255+
}
12681256

1269-
// to next directory
1270-
pred[0] = cwd[0];
1271-
pred[1] = cwd[1];
1272-
cwd[0] = child.d.tail[0];
1273-
cwd[1] = child.d.tail[1];
1257+
// check if we have a parent
1258+
int parent = lfs_parent(lfs, pdir.d.tail);
1259+
if (parent < 0) {
1260+
return parent;
1261+
}
12741262

1275-
if (!cwd[0]) {
1276-
return 0;
1263+
if (!parent) {
1264+
// we are an orphan
1265+
LFS_INFO("Found orphan %d %d", pdir.d.tail[0], pdir.d.tail[1]);
1266+
1267+
pdir.d.tail[0] = cdir.d.tail[0];
1268+
pdir.d.tail[1] = cdir.d.tail[1];
1269+
pdir.d.rev += 1;
1270+
1271+
err = lfs_pair_commit(lfs, pdir.pair,
1272+
1, (struct lfs_commit_region[]) {
1273+
{0, sizeof(pdir.d), &pdir.d},
1274+
});
1275+
if (err) {
1276+
return err;
1277+
}
1278+
1279+
break;
12771280
}
1281+
1282+
memcpy(&pdir, &cdir, sizeof(pdir));
12781283
}
1284+
1285+
return 0;
12791286
}
12801287

12811288
int lfs_remove(lfs_t *lfs, const char *path) {

lfs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
153153
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
154154
void *buffer, lfs_size_t size);
155155

156+
int lfs_deorphan(lfs_t *lfs);
157+
int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
158+
156159
#endif

tests/template.fmt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ void test_assert(const char *file, unsigned line,
3030
#define test_assert(s, v, e) test_assert(__FILE__, __LINE__, s, v, e)
3131

3232

33+
// utility functions for traversals
34+
int test_count(void *p, lfs_block_t b) {{
35+
unsigned *u = (unsigned*)p;
36+
*u += 1;
37+
return 0;
38+
}}
39+
40+
3341
// lfs declarations
3442
lfs_t lfs;
3543
lfs_emubd_t bd;

tests/test_orphan.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
set -eu
3+
4+
echo "=== Orphan tests ==="
5+
rm -rf blocks
6+
tests/test.py << TEST
7+
lfs_format(&lfs, &config) => 0;
8+
TEST
9+
10+
echo "--- Orphan test ---"
11+
tests/test.py << TEST
12+
lfs_mount(&lfs, &config) => 0;
13+
lfs_mkdir(&lfs, "parent") => 0;
14+
lfs_mkdir(&lfs, "parent/orphan") => 0;
15+
lfs_mkdir(&lfs, "parent/child") => 0;
16+
lfs_remove(&lfs, "parent/orphan") => 0;
17+
TEST
18+
# remove most recent file, this should be the update to the previous
19+
# linked-list entry and should orphan the child
20+
rm -v "blocks/$(ls -t blocks | sed -n '/^[0-9a-f]*$/p' | sed -n '1p')"
21+
tests/test.py << TEST
22+
lfs_mount(&lfs, &config) => 0;
23+
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERROR_NO_ENTRY;
24+
unsigned before = 0;
25+
lfs_traverse(&lfs, test_count, &before) => 0;
26+
test_log("before", before);
27+
28+
lfs_deorphan(&lfs) => 0;
29+
30+
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERROR_NO_ENTRY;
31+
unsigned after = 0;
32+
lfs_traverse(&lfs, test_count, &after) => 0;
33+
test_log("after", after);
34+
35+
int diff = before - after;
36+
diff => 2;
37+
lfs_unmount(&lfs) => 0;
38+
TEST
39+
40+
echo "--- Results ---"
41+
tests/stats.py

0 commit comments

Comments
 (0)