Skip to content

Commit e611cf5

Browse files
committed
Fix incorrect lookahead population before ack
Rather than tracking all in-flight blocks blocks during a lookahead, littlefs uses an ack scheme to mark the first allocated block that hasn't reached the disk yet. littlefs assumes all blocks since the last ack are bad or in-flight, and uses this to know when it's out of storage. However, these unacked allocations were still being populated in the lookahead buffer. If the whole block device fits in the lookahead buffer, _and_ littlefs managed to scan around the whole storage while an unacked block was still in-flight, it would assume the block was free and misallocate it. The fix is to only fill the lookahead buffer up to the last ack. The internal free structure was restructured to simplify the runtime calculation of lookahead size.
1 parent a25743a commit e611cf5

File tree

3 files changed

+54
-21
lines changed

3 files changed

+54
-21
lines changed

lfs.c

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
278278
% (lfs_soff_t)(lfs->cfg->block_count))
279279
+ lfs->cfg->block_count) % lfs->cfg->block_count;
280280

281-
if (off < lfs->cfg->lookahead) {
281+
if (off < lfs->free.size) {
282282
lfs->free.buffer[off / 32] |= 1U << (off % 32);
283283
}
284284

@@ -287,18 +287,7 @@ static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
287287

288288
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
289289
while (true) {
290-
while (true) {
291-
// check if we have looked at all blocks since last ack
292-
if (lfs->free.begin + lfs->free.off == lfs->free.end) {
293-
LFS_WARN("No more free space %d", lfs->free.end);
294-
return LFS_ERR_NOSPC;
295-
}
296-
297-
if (lfs->free.off >= lfs_min(
298-
lfs->cfg->lookahead, lfs->cfg->block_count)) {
299-
break;
300-
}
301-
290+
while (lfs->free.off != lfs->free.size) {
302291
lfs_block_t off = lfs->free.off;
303292
lfs->free.off += 1;
304293

@@ -309,7 +298,15 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
309298
}
310299
}
311300

312-
lfs->free.begin += lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
301+
// check if we have looked at all blocks since last ack
302+
if (lfs->free.off == lfs->free.ack - lfs->free.begin) {
303+
LFS_WARN("No more free space %d", lfs->free.off + lfs->free.begin);
304+
return LFS_ERR_NOSPC;
305+
}
306+
307+
lfs->free.begin += lfs->free.size;
308+
lfs->free.size = lfs_min(lfs->cfg->lookahead,
309+
lfs->free.ack - lfs->free.begin);
313310
lfs->free.off = 0;
314311

315312
// find mask of free blocks from tree
@@ -322,7 +319,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
322319
}
323320

324321
static void lfs_alloc_ack(lfs_t *lfs) {
325-
lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
322+
lfs->free.ack = lfs->free.off-1 + lfs->free.begin + lfs->cfg->block_count;
326323
}
327324

328325

@@ -2035,11 +2032,11 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
20352032
// create free lookahead
20362033
memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
20372034
lfs->free.begin = 0;
2035+
lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
20382036
lfs->free.off = 0;
2039-
lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
2037+
lfs_alloc_ack(lfs);
20402038

20412039
// create superblock dir
2042-
lfs_alloc_ack(lfs);
20432040
lfs_dir_t superdir;
20442041
err = lfs_dir_alloc(lfs, &superdir);
20452042
if (err) {
@@ -2112,9 +2109,10 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
21122109
}
21132110

21142111
// setup free lookahead
2115-
lfs->free.begin = -lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
2116-
lfs->free.off = -lfs->free.begin;
2117-
lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
2112+
lfs->free.begin = 0;
2113+
lfs->free.size = 0;
2114+
lfs->free.off = 0;
2115+
lfs_alloc_ack(lfs);
21182116

21192117
// load superblock
21202118
lfs_dir_t dir;

lfs.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,9 @@ typedef struct lfs_superblock {
260260

261261
typedef struct lfs_free {
262262
lfs_block_t begin;
263-
lfs_block_t end;
263+
lfs_block_t size;
264264
lfs_block_t off;
265+
lfs_block_t ack;
265266
uint32_t *buffer;
266267
} lfs_free_t;
267268

tests/test_alloc.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,40 @@ tests/test.py << TEST
266266
lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
267267
TEST
268268

269+
echo "--- Split dir test ---"
270+
rm -rf blocks
271+
tests/test.py << TEST
272+
lfs_format(&lfs, &cfg) => 0;
273+
TEST
274+
tests/test.py << TEST
275+
lfs_mount(&lfs, &cfg) => 0;
276+
277+
// create one block whole for half a directory
278+
lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
279+
lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2;
280+
lfs_file_close(&lfs, &file[0]) => 0;
281+
282+
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
283+
size = strlen("blahblahblahblah");
284+
memcpy(buffer, "blahblahblahblah", size);
285+
for (lfs_size_t i = 0;
286+
i < (cfg.block_count-6)*(cfg.block_size-8);
287+
i += size) {
288+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
289+
}
290+
lfs_file_close(&lfs, &file[0]) => 0;
291+
292+
// open whole
293+
lfs_remove(&lfs, "bump") => 0;
294+
295+
lfs_mkdir(&lfs, "splitdir") => 0;
296+
lfs_file_open(&lfs, &file[0], "splitdir/bump",
297+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
298+
lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC;
299+
lfs_file_close(&lfs, &file[0]) => 0;
300+
301+
lfs_unmount(&lfs) => 0;
302+
TEST
269303

270304
echo "--- Results ---"
271305
tests/stats.py

0 commit comments

Comments
 (0)