Skip to content

Commit 89a7630

Browse files
committed
Fixed issue with lookahead trusting old lookahead blocks
One of the big simplifications in littlefs's implementation is the complete lack of tracking free blocks, allowing operations to simply drop blocks that are no longer in use. However, this means the lookahead buffer can easily contain outdated blocks that were previously deleted. This is usually fine, as littlefs will rescan the storage if it can't find a free block in the lookahead buffer, but after changes that caused littlefs to more conservatively respect the alloc acks (e611cf5), any scanned blocks after an ack would be incorrectly trusted. The fix is to eagerly scan ahead in the lookahead when we allocate so that alloc acks are better able to discredit old lookahead blocks. Since usually alloc acks are tightly coupled to allocations of one or two blocks, this allows littlefs to properly rescan every set of allocations. This may still be a concern if there is a long series of worn out blocks, but in the worst case littlefs will conservatively avoid using blocks it's not sure about. Found by davidefer
1 parent 43eac30 commit 89a7630

File tree

2 files changed

+134
-2
lines changed

2 files changed

+134
-2
lines changed

lfs.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,15 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
290290
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
291291
// found a free block
292292
*block = (lfs->free.begin + off) % lfs->cfg->block_count;
293+
294+
// eagerly find next off so an alloc ack can
295+
// discredit old lookahead blocks
296+
while (lfs->free.off != lfs->free.size &&
297+
(lfs->free.buffer[lfs->free.off / 32] &
298+
(1U << (lfs->free.off % 32)))) {
299+
lfs->free.off += 1;
300+
}
301+
293302
return 0;
294303
}
295304
}
@@ -315,7 +324,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
315324
}
316325

317326
static void lfs_alloc_ack(lfs_t *lfs) {
318-
lfs->free.ack = lfs->free.off-1 + lfs->free.begin + lfs->cfg->block_count;
327+
lfs->free.ack = lfs->free.begin+lfs->free.off + lfs->cfg->block_count;
319328
}
320329

321330

tests/test_alloc.sh

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ tests/test.py << TEST
289289
}
290290
lfs_file_close(&lfs, &file[0]) => 0;
291291
292-
// open whole
292+
// open hole
293293
lfs_remove(&lfs, "bump") => 0;
294294
295295
lfs_mkdir(&lfs, "splitdir") => 0;
@@ -301,5 +301,128 @@ tests/test.py << TEST
301301
lfs_unmount(&lfs) => 0;
302302
TEST
303303

304+
echo "--- Outdated lookahead test ---"
305+
rm -rf blocks
306+
tests/test.py << TEST
307+
lfs_format(&lfs, &cfg) => 0;
308+
309+
lfs_mount(&lfs, &cfg) => 0;
310+
311+
// fill completely with two files
312+
lfs_file_open(&lfs, &file[0], "exhaustion1",
313+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
314+
size = strlen("blahblahblahblah");
315+
memcpy(buffer, "blahblahblahblah", size);
316+
for (lfs_size_t i = 0;
317+
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
318+
i += size) {
319+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
320+
}
321+
lfs_file_close(&lfs, &file[0]) => 0;
322+
323+
lfs_file_open(&lfs, &file[0], "exhaustion2",
324+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
325+
size = strlen("blahblahblahblah");
326+
memcpy(buffer, "blahblahblahblah", size);
327+
for (lfs_size_t i = 0;
328+
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
329+
i += size) {
330+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
331+
}
332+
lfs_file_close(&lfs, &file[0]) => 0;
333+
334+
// remount to force reset of lookahead
335+
lfs_unmount(&lfs) => 0;
336+
337+
lfs_mount(&lfs, &cfg) => 0;
338+
339+
// rewrite one file
340+
lfs_file_open(&lfs, &file[0], "exhaustion1",
341+
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
342+
lfs_file_sync(&lfs, &file[0]) => 0;
343+
size = strlen("blahblahblahblah");
344+
memcpy(buffer, "blahblahblahblah", size);
345+
for (lfs_size_t i = 0;
346+
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
347+
i += size) {
348+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
349+
}
350+
lfs_file_close(&lfs, &file[0]) => 0;
351+
352+
// rewrite second file, this requires lookahead does not
353+
// use old population
354+
lfs_file_open(&lfs, &file[0], "exhaustion2",
355+
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
356+
lfs_file_sync(&lfs, &file[0]) => 0;
357+
size = strlen("blahblahblahblah");
358+
memcpy(buffer, "blahblahblahblah", size);
359+
for (lfs_size_t i = 0;
360+
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
361+
i += size) {
362+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
363+
}
364+
lfs_file_close(&lfs, &file[0]) => 0;
365+
TEST
366+
367+
echo "--- Outdated lookahead and split dir test ---"
368+
rm -rf blocks
369+
tests/test.py << TEST
370+
lfs_format(&lfs, &cfg) => 0;
371+
372+
lfs_mount(&lfs, &cfg) => 0;
373+
374+
// fill completely with two files
375+
lfs_file_open(&lfs, &file[0], "exhaustion1",
376+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
377+
size = strlen("blahblahblahblah");
378+
memcpy(buffer, "blahblahblahblah", size);
379+
for (lfs_size_t i = 0;
380+
i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
381+
i += size) {
382+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
383+
}
384+
lfs_file_close(&lfs, &file[0]) => 0;
385+
386+
lfs_file_open(&lfs, &file[0], "exhaustion2",
387+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
388+
size = strlen("blahblahblahblah");
389+
memcpy(buffer, "blahblahblahblah", size);
390+
for (lfs_size_t i = 0;
391+
i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
392+
i += size) {
393+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
394+
}
395+
lfs_file_close(&lfs, &file[0]) => 0;
396+
397+
// remount to force reset of lookahead
398+
lfs_unmount(&lfs) => 0;
399+
400+
lfs_mount(&lfs, &cfg) => 0;
401+
402+
// rewrite one file with a hole of one block
403+
lfs_file_open(&lfs, &file[0], "exhaustion1",
404+
LFS_O_WRONLY | LFS_O_TRUNC) => 0;
405+
lfs_file_sync(&lfs, &file[0]) => 0;
406+
size = strlen("blahblahblahblah");
407+
memcpy(buffer, "blahblahblahblah", size);
408+
for (lfs_size_t i = 0;
409+
i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8);
410+
i += size) {
411+
lfs_file_write(&lfs, &file[0], buffer, size) => size;
412+
}
413+
lfs_file_close(&lfs, &file[0]) => 0;
414+
415+
// try to allocate a directory, should fail!
416+
lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
417+
418+
// file should not fail
419+
lfs_file_open(&lfs, &file[0], "notasplit",
420+
LFS_O_WRONLY | LFS_O_CREAT) => 0;
421+
lfs_file_write(&lfs, &file[0], "hi", 2) => 2;
422+
lfs_file_close(&lfs, &file[0]) => 0;
423+
424+
lfs_unmount(&lfs) => 0;
425+
TEST
426+
304427
echo "--- Results ---"
305428
tests/stats.py

0 commit comments

Comments
 (0)