Skip to content

Commit a3734ee

Browse files
committed
Added proper handling of orphans
Unfortunately, threading all dir blocks in a linked-list did not come without problems. While it's possible to atomically add a dir to the linked list (by adding the new dir into the linked-list position immediately after it's parent, requiring only one atomic update to the parent block), it is not easy to make sure the linked-list is in a state that always allows atomic removal of dirs. The simple solution is to allow this non-atomic removal, with an additional step to remove any orphans that could have been created by a power-loss. This deorphan step is only run if the normal allocator has failed.
1 parent 8a67452 commit a3734ee

File tree

3 files changed

+216
-40
lines changed

3 files changed

+216
-40
lines changed

emubd/lfs_emubd.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <dirent.h>
1515
#include <sys/stat.h>
1616
#include <unistd.h>
17+
#include <assert.h>
18+
#include <stdbool.h>
1719

1820

1921
// Block device emulated on existing filesystem
@@ -76,12 +78,10 @@ int lfs_emubd_read(lfs_emubd_t *emu, lfs_block_t block,
7678
uint8_t *data = buffer;
7779

7880
// Check if read is valid
79-
if (!(off % emu->info.read_size == 0 &&
80-
size % emu->info.read_size == 0 &&
81-
((uint64_t)block*emu->info.erase_size + off + size
82-
< emu->info.total_size))) {
83-
return -EINVAL;
84-
}
81+
assert(off % emu->info.read_size == 0);
82+
assert(size % emu->info.read_size == 0);
83+
assert((uint64_t)block*emu->info.erase_size + off + size
84+
< emu->info.total_size);
8585

8686
// Zero out buffer for debugging
8787
memset(data, 0, size);
@@ -128,12 +128,10 @@ int lfs_emubd_prog(lfs_emubd_t *emu, lfs_block_t block,
128128
const uint8_t *data = buffer;
129129

130130
// Check if write is valid
131-
if (!(off % emu->info.prog_size == 0 &&
132-
size % emu->info.prog_size == 0 &&
133-
((uint64_t)block*emu->info.erase_size + off + size
134-
< emu->info.total_size))) {
135-
return -EINVAL;
136-
}
131+
assert(off % emu->info.prog_size == 0);
132+
assert(size % emu->info.prog_size == 0);
133+
assert((uint64_t)block*emu->info.erase_size + off + size
134+
< emu->info.total_size);
137135

138136
// Iterate over blocks until enough data is read
139137
while (size > 0) {
@@ -177,12 +175,10 @@ int lfs_emubd_erase(lfs_emubd_t *emu, lfs_block_t block,
177175
lfs_off_t off, lfs_size_t size) {
178176

179177
// Check if erase is valid
180-
if (!(off % emu->info.erase_size == 0 &&
181-
size % emu->info.erase_size == 0 &&
182-
((uint64_t)block*emu->info.erase_size + off + size
183-
< emu->info.total_size))) {
184-
return -EINVAL;
185-
}
178+
assert(off % emu->info.erase_size == 0);
179+
assert(size % emu->info.erase_size == 0);
180+
assert((uint64_t)block*emu->info.erase_size + off + size
181+
< emu->info.total_size);
186182

187183
// Iterate and erase blocks
188184
while (size > 0) {

lfs.c

Lines changed: 145 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block,
7777

7878
// predeclared filesystem traversal
7979
static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
80+
int lfs_deorphan(lfs_t *lfs);
8081

8182
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
8283
lfs_t *lfs = p;
@@ -143,8 +144,24 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
143144
int err = lfs_alloc_scan(lfs);
144145
if (err) {
145146
return err;
146-
} else if (lfs->free.begin == lfs->free.end) {
147-
return LFS_ERROR_NO_SPACE;
147+
}
148+
149+
if (lfs->free.begin == lfs->free.end) {
150+
// Still can't allocate a block? check for orphans
151+
int err = lfs_deorphan(lfs);
152+
if (err) {
153+
return err;
154+
}
155+
156+
err = lfs_alloc_scan(lfs);
157+
if (err) {
158+
return err;
159+
}
160+
161+
if (lfs->free.begin == lfs->free.end) {
162+
// Ok, it's true, we're out of space
163+
return LFS_ERROR_NO_SPACE;
164+
}
148165
}
149166
}
150167

@@ -1146,7 +1163,7 @@ static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
11461163
// skip '.' and '..'
11471164
dir.off += 2*sizeof(struct lfs_disk_entry) + 3;
11481165

1149-
// TODO iterate over files
1166+
// iterate over contents
11501167
while ((0x7fffffff & dir.d.size) >= dir.off + sizeof(file.entry.d)) {
11511168
int err = lfs_bd_read(lfs, dir.pair[0], dir.off,
11521169
sizeof(file.entry.d), &file.entry.d);
@@ -1155,14 +1172,7 @@ static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
11551172
}
11561173

11571174
dir.off += file.entry.d.len;
1158-
if ((0xf & file.entry.d.type) == LFS_TYPE_DIR) {
1159-
for (int i = 0; i < 2; i++) {
1160-
int err = cb(data, file.entry.d.u.dir[i]);
1161-
if (err) {
1162-
return err;
1163-
}
1164-
}
1165-
} else if ((0xf & file.entry.d.type) == LFS_TYPE_REG) {
1175+
if ((0xf & file.entry.d.type) == LFS_TYPE_REG) {
11661176
if (file.entry.d.u.file.size < lfs->block_size) {
11671177
int err = cb(data, file.entry.d.u.file.head);
11681178
if (err) {
@@ -1189,6 +1199,85 @@ static int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
11891199
}
11901200
}
11911201

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]};
1206+
1207+
while (true) {
1208+
lfs_dir_t child;
1209+
int err = lfs_dir_fetch(lfs, &child, cwd);
1210+
if (err) {
1211+
return err;
1212+
}
1213+
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) {
1222+
return err;
1223+
}
1224+
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+
}
1232+
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+
}
1239+
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+
}
1266+
}
1267+
}
1268+
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];
1274+
1275+
if (!cwd[0]) {
1276+
return 0;
1277+
}
1278+
}
1279+
}
1280+
11921281
int lfs_remove(lfs_t *lfs, const char *path) {
11931282
lfs_dir_t cwd;
11941283
int err = lfs_dir_fetch(lfs, &cwd, lfs->cwd);
@@ -1202,18 +1291,61 @@ int lfs_remove(lfs_t *lfs, const char *path) {
12021291
return err;
12031292
}
12041293

1294+
lfs_dir_t dir;
12051295
if (entry.d.type == LFS_TYPE_DIR) {
12061296
// must be empty before removal
1207-
lfs_dir_t dir;
12081297
int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
12091298
if (err) {
12101299
return err;
12111300
} else if (dir.d.size != sizeof(dir.d) +
12121301
2*sizeof(struct lfs_disk_entry) + 3) {
12131302
return LFS_ERROR_INVALID;
12141303
}
1304+
}
1305+
1306+
cwd.d.rev += 1;
1307+
cwd.d.size -= entry.d.len;
1308+
1309+
// either shift out the one entry or remove the whole dir block
1310+
if (cwd.d.size == sizeof(dir.d)) {
1311+
lfs_dir_t pdir;
1312+
int err = lfs_dir_fetch(lfs, &pdir, lfs->cwd);
1313+
if (err) {
1314+
return err;
1315+
}
12151316

1317+
while (lfs_paircmp(pdir.d.tail, cwd.pair) != 0) {
1318+
int err = lfs_dir_fetch(lfs, &pdir, pdir.d.tail);
1319+
if (err) {
1320+
return err;
1321+
}
1322+
}
1323+
1324+
pdir.d.tail[0] = cwd.d.tail[0];
1325+
pdir.d.tail[1] = cwd.d.tail[1];
1326+
pdir.d.rev += 1;
1327+
1328+
err = lfs_pair_commit(lfs, pdir.pair,
1329+
1, (struct lfs_commit_region[]) {
1330+
{0, sizeof(pdir.d), &pdir.d},
1331+
});
1332+
if (err) {
1333+
return err;
1334+
}
1335+
} else {
1336+
int err = lfs_pair_shift(lfs, entry.dir,
1337+
1, (struct lfs_commit_region[]) {
1338+
{0, sizeof(cwd.d), &cwd.d},
1339+
},
1340+
entry.off, entry.d.len);
1341+
if (err) {
1342+
return err;
1343+
}
1344+
}
1345+
1346+
if (entry.d.type == LFS_TYPE_DIR) {
12161347
// remove ourselves from the dir list
1348+
// this may create an orphan, which must be deorphaned
12171349
lfs_dir_t pdir;
12181350
memcpy(&pdir, &cwd, sizeof(pdir));
12191351

@@ -1241,15 +1373,6 @@ int lfs_remove(lfs_t *lfs, const char *path) {
12411373
}
12421374
}
12431375

1244-
cwd.d.rev += 1;
1245-
cwd.d.size -= entry.d.len;
1246-
// TODO remove dir block?
1247-
1248-
// Drop contents on the floor
1249-
return lfs_pair_shift(lfs, entry.dir,
1250-
1, (struct lfs_commit_region[]) {
1251-
{0, sizeof(cwd.d), &cwd.d},
1252-
},
1253-
entry.off, entry.d.len);
1376+
return 0;
12541377
}
12551378

tests/test_dirs.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,62 @@ tests/test.py << TEST
123123
lfs_unmount(&lfs) => 0;
124124
TEST
125125

126+
echo "--- Directory deletion ---"
127+
tests/test.py << TEST
128+
lfs_mount(&lfs, &config) => 0;
129+
lfs_remove(&lfs, "potato") => LFS_ERROR_INVALID;
130+
lfs_remove(&lfs, "potato/sweet") => 0;
131+
lfs_remove(&lfs, "potato/baked") => 0;
132+
lfs_remove(&lfs, "potato/fried") => 0;
133+
134+
lfs_dir_open(&lfs, &dir[0], "potato") => 0;
135+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
136+
strcmp(info.name, ".") => 0;
137+
info.type => LFS_TYPE_DIR;
138+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
139+
strcmp(info.name, "..") => 0;
140+
info.type => LFS_TYPE_DIR;
141+
lfs_dir_read(&lfs, &dir[0], &info) => 0;
142+
lfs_dir_close(&lfs, &dir[0]) => 0;
143+
144+
lfs_remove(&lfs, "potato") => 0;
145+
146+
lfs_dir_open(&lfs, &dir[0], "/") => 0;
147+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
148+
strcmp(info.name, ".") => 0;
149+
info.type => LFS_TYPE_DIR;
150+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
151+
strcmp(info.name, "..") => 0;
152+
info.type => LFS_TYPE_DIR;
153+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
154+
strcmp(info.name, "burito") => 0;
155+
info.type => LFS_TYPE_REG;
156+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
157+
strcmp(info.name, "cactus") => 0;
158+
info.type => LFS_TYPE_DIR;
159+
lfs_dir_read(&lfs, &dir[0], &info) => 0;
160+
lfs_dir_close(&lfs, &dir[0]) => 0;
161+
lfs_unmount(&lfs) => 0;
162+
TEST
163+
tests/test.py << TEST
164+
lfs_mount(&lfs, &config) => 0;
165+
lfs_dir_open(&lfs, &dir[0], "/") => 0;
166+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
167+
strcmp(info.name, ".") => 0;
168+
info.type => LFS_TYPE_DIR;
169+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
170+
strcmp(info.name, "..") => 0;
171+
info.type => LFS_TYPE_DIR;
172+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
173+
strcmp(info.name, "burito") => 0;
174+
info.type => LFS_TYPE_REG;
175+
lfs_dir_read(&lfs, &dir[0], &info) => 1;
176+
strcmp(info.name, "cactus") => 0;
177+
info.type => LFS_TYPE_DIR;
178+
lfs_dir_read(&lfs, &dir[0], &info) => 0;
179+
lfs_dir_close(&lfs, &dir[0]) => 0;
180+
lfs_unmount(&lfs) => 0;
181+
TEST
182+
126183
echo "--- Results ---"
127184
tests/stats.py

0 commit comments

Comments
 (0)