mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 08:42:40 +01:00 
			
		
		
		
	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
		
	
		
			
				
	
	
		
			429 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| set -eu
 | |
| 
 | |
| echo "=== Allocator tests ==="
 | |
| rm -rf blocks
 | |
| tests/test.py << TEST
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| TEST
 | |
| 
 | |
| SIZE=15000
 | |
| 
 | |
| lfs_mkdir() {
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "$1") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| }
 | |
| 
 | |
| lfs_remove() {
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_remove(&lfs, "$1/eggs") => 0;
 | |
|     lfs_remove(&lfs, "$1/bacon") => 0;
 | |
|     lfs_remove(&lfs, "$1/pancakes") => 0;
 | |
|     lfs_remove(&lfs, "$1") => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| }
 | |
| 
 | |
| lfs_alloc_singleproc() {
 | |
| tests/test.py << TEST
 | |
|     const char *names[] = {"bacon", "eggs", "pancakes"};
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
 | |
|         sprintf((char*)buffer, "$1/%s", names[n]);
 | |
|         lfs_file_open(&lfs, &file[n], (char*)buffer,
 | |
|                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
 | |
|     }
 | |
|     for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
 | |
|         size = strlen(names[n]);
 | |
|         for (int i = 0; i < $SIZE; i++) {
 | |
|             lfs_file_write(&lfs, &file[n], names[n], size) => size;
 | |
|         }
 | |
|     }
 | |
|     for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
 | |
|         lfs_file_close(&lfs, &file[n]) => 0;
 | |
|     }
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| }
 | |
| 
 | |
| lfs_alloc_multiproc() {
 | |
| for name in bacon eggs pancakes
 | |
| do
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "$1/$name",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
 | |
|     size = strlen("$name");
 | |
|     memcpy(buffer, "$name", size);
 | |
|     for (int i = 0; i < $SIZE; i++) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| done
 | |
| }
 | |
| 
 | |
| lfs_verify() {
 | |
| for name in bacon eggs pancakes
 | |
| do
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "$1/$name", LFS_O_RDONLY) => 0;
 | |
|     size = strlen("$name");
 | |
|     for (int i = 0; i < $SIZE; i++) {
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "$name", size) => 0;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| done
 | |
| }
 | |
| 
 | |
| echo "--- Single-process allocation test ---"
 | |
| lfs_mkdir singleproc
 | |
| lfs_alloc_singleproc singleproc
 | |
| lfs_verify singleproc
 | |
| 
 | |
| echo "--- Multi-process allocation test ---"
 | |
| lfs_mkdir multiproc
 | |
| lfs_alloc_multiproc multiproc
 | |
| lfs_verify multiproc
 | |
| lfs_verify singleproc
 | |
| 
 | |
| echo "--- Single-process reuse test ---"
 | |
| lfs_remove singleproc
 | |
| lfs_mkdir singleprocreuse
 | |
| lfs_alloc_singleproc singleprocreuse
 | |
| lfs_verify singleprocreuse
 | |
| lfs_verify multiproc
 | |
| 
 | |
| echo "--- Multi-process reuse test ---"
 | |
| lfs_remove multiproc
 | |
| lfs_mkdir multiprocreuse
 | |
| lfs_alloc_singleproc multiprocreuse
 | |
| lfs_verify multiprocreuse
 | |
| lfs_verify singleprocreuse
 | |
| 
 | |
| echo "--- Cleanup ---"
 | |
| lfs_remove multiprocreuse
 | |
| lfs_remove singleprocreuse
 | |
| 
 | |
| echo "--- Exhaustion test ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("exhaustion");
 | |
|     memcpy(buffer, "exhaustion", size);
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     lfs_file_sync(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     lfs_ssize_t res;
 | |
|     while (true) {
 | |
|         res = lfs_file_write(&lfs, &file[0], buffer, size);
 | |
|         if (res < 0) {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         res => size;
 | |
|     }
 | |
|     res => LFS_ERR_NOSPC;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
 | |
|     size = strlen("exhaustion");
 | |
|     lfs_file_size(&lfs, &file[0]) => size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "exhaustion", size) => 0;
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Exhaustion wraparound test ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_remove(&lfs, "exhaustion") => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "padding", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("buffering");
 | |
|     memcpy(buffer, "buffering", size);
 | |
|     for (int i = 0; i < $SIZE; i++) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_remove(&lfs, "padding") => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("exhaustion");
 | |
|     memcpy(buffer, "exhaustion", size);
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     lfs_file_sync(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     lfs_ssize_t res;
 | |
|     while (true) {
 | |
|         res = lfs_file_write(&lfs, &file[0], buffer, size);
 | |
|         if (res < 0) {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         res => size;
 | |
|     }
 | |
|     res => LFS_ERR_NOSPC;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
 | |
|     size = strlen("exhaustion");
 | |
|     lfs_file_size(&lfs, &file[0]) => size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "exhaustion", size) => 0;
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Dir exhaustion test ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_remove(&lfs, "exhaustion") => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < (cfg.block_count-6)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_mkdir(&lfs, "exhaustiondir") => 0;
 | |
|     lfs_remove(&lfs, "exhaustiondir") => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND);
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < (cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Chained dir exhaustion test ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_remove(&lfs, "exhaustion") => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < (cfg.block_count-24)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     for (int i = 0; i < 9; i++) {
 | |
|         sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
 | |
|         lfs_mkdir(&lfs, (char*)buffer) => 0;
 | |
|     }
 | |
| 
 | |
|     lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
 | |
| 
 | |
|     lfs_remove(&lfs, "exhaustion") => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < (cfg.block_count-26)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_mkdir(&lfs, "exhaustiondir") => 0;
 | |
|     lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
 | |
| TEST
 | |
| 
 | |
| echo "--- Split dir test ---"
 | |
| rm -rf blocks
 | |
| tests/test.py << TEST
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| TEST
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // create one block whole for half a directory
 | |
|     lfs_file_open(&lfs, &file[0], "bump", LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     lfs_file_write(&lfs, &file[0], (void*)"hi", 2) => 2;
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < (cfg.block_count-6)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     // open hole
 | |
|     lfs_remove(&lfs, "bump") => 0;
 | |
| 
 | |
|     lfs_mkdir(&lfs, "splitdir") => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "splitdir/bump",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => LFS_ERR_NOSPC;
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Outdated lookahead test ---"
 | |
| rm -rf blocks
 | |
| tests/test.py << TEST
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| 
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // fill completely with two files
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion1",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion2",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     // remount to force reset of lookahead
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // rewrite one file
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion1",
 | |
|             LFS_O_WRONLY | LFS_O_TRUNC) => 0;
 | |
|     lfs_file_sync(&lfs, &file[0]) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     // rewrite second file, this requires lookahead does not
 | |
|     // use old population
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion2",
 | |
|             LFS_O_WRONLY | LFS_O_TRUNC) => 0;
 | |
|     lfs_file_sync(&lfs, &file[0]) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Outdated lookahead and split dir test ---"
 | |
| rm -rf blocks
 | |
| tests/test.py << TEST
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
| 
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // fill completely with two files
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion1",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion2",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4+1)/2)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     // remount to force reset of lookahead
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| 
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
| 
 | |
|     // rewrite one file with a hole of one block
 | |
|     lfs_file_open(&lfs, &file[0], "exhaustion1",
 | |
|             LFS_O_WRONLY | LFS_O_TRUNC) => 0;
 | |
|     lfs_file_sync(&lfs, &file[0]) => 0;
 | |
|     size = strlen("blahblahblahblah");
 | |
|     memcpy(buffer, "blahblahblahblah", size);
 | |
|     for (lfs_size_t i = 0;
 | |
|             i < ((cfg.block_count-4)/2 - 1)*(cfg.block_size-8);
 | |
|             i += size) {
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|     }
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     // try to allocate a directory, should fail!
 | |
|     lfs_mkdir(&lfs, "split") => LFS_ERR_NOSPC;
 | |
| 
 | |
|     // file should not fail
 | |
|     lfs_file_open(&lfs, &file[0], "notasplit",
 | |
|             LFS_O_WRONLY | LFS_O_CREAT) => 0;
 | |
|     lfs_file_write(&lfs, &file[0], "hi", 2) => 2;
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
| 
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Results ---"
 | |
| tests/stats.py
 |