mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Instead of storing files in an arbitrary order, we now store files in ascending lexicographical order by filename. Although a big change, this actually has little impact on how littlefs works internally. We need to support file insertion, and compare file names to find our position. But since we already need to scan the entire directory block, this adds relatively little overhead. What this does allow, is the potential to add B-tree support in the future in a backwards compatible manner. How could you add B-trees to littlefs? 1. Add an optional "child" tag with a pointer that allows you to skip to a position in the metadata-pair list that composes the directory 2. When splitting a metadata-pair (sound familiar?), we either insert a second child tag in our parent, or we create a new root containing the child tags. 3. Each layer needs a bit stored in the tail-pointer to indicate if we're going to the next layer. This can be created trivially when we create a new root. 4. During lookup we keep two pointers containing the bounds of our search. We may need to iterate through multiple metadata-pairs in our linked-list, but this gives us a O(log n) lookup cost in a balanced tree. 5. During deletion we also delete any children pointers. Note that children pointers must come before the actual file entry. This gives us a B-tree implementation that is compatible with the current directory layout (assuming the files are ordered). This means that B-trees could be supported by a host PC and ignored on a small device. And during power-loss, we never end up with a broken filesystem, just a less-than-optimal tree. Note that we don't handle removes, so it's possible for a tree to become unbalanced. But worst case that's the same as the current linked-list implementation. All we need to do now is keep directories ordered. If we decide to drop B-tree support in the future or the B-tree implementation turns out inherently flawed, we can just drop the ordered requirement without breaking compatibility and recover the code cost.
		
			
				
	
	
		
			362 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| set -eu
 | |
| 
 | |
| SMALLSIZE=4
 | |
| MEDIUMSIZE=128
 | |
| LARGESIZE=132
 | |
| 
 | |
| echo "=== Seek tests ==="
 | |
| rm -rf blocks
 | |
| tests/test.py << TEST
 | |
|     lfs_format(&lfs, &cfg) => 0;
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_mkdir(&lfs, "hello") => 0;
 | |
|     for (int i = 0; i < $LARGESIZE; i++) {
 | |
|         sprintf((char*)buffer, "hello/kitty%03d", i);
 | |
|         lfs_file_open(&lfs, &file[0], (char*)buffer,
 | |
|                 LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
 | |
| 
 | |
|         size = strlen("kittycatcat");
 | |
|         memcpy(buffer, "kittycatcat", size);
 | |
|         for (int j = 0; j < $LARGESIZE; j++) {
 | |
|             lfs_file_write(&lfs, &file[0], buffer, size);
 | |
|         }
 | |
| 
 | |
|         lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     }
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Simple dir seek ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_dir_open(&lfs, &dir[0], "hello") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, ".") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, "..") => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     int i;
 | |
|     for (i = 0; i < $SMALLSIZE; i++) {
 | |
|         sprintf((char*)buffer, "kitty%03d", i);
 | |
|         lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|         strcmp(info.name, (char*)buffer) => 0;
 | |
|         pos = lfs_dir_tell(&lfs, &dir[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", i);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_rewind(&lfs, &dir[0]) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", 0);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, ".") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, "..") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", i);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_close(&lfs, &dir[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Large dir seek ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_dir_open(&lfs, &dir[0], "hello") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, ".") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, "..") => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     int i;
 | |
|     for (i = 0; i < $MEDIUMSIZE; i++) {
 | |
|         sprintf((char*)buffer, "kitty%03d", i);
 | |
|         lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|         strcmp(info.name, (char*)buffer) => 0;
 | |
|         pos = lfs_dir_tell(&lfs, &dir[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", i);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_rewind(&lfs, &dir[0]) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", 0);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, ".") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, "..") => 0;
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_seek(&lfs, &dir[0], pos) => 0;
 | |
|     sprintf((char*)buffer, "kitty%03d", i);
 | |
|     lfs_dir_read(&lfs, &dir[0], &info) => 1;
 | |
|     strcmp(info.name, (char*)buffer) => 0;
 | |
| 
 | |
|     lfs_dir_close(&lfs, &dir[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Simple file seek ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDONLY) => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     size = strlen("kittycatcat");
 | |
|     for (int i = 0; i < $SMALLSIZE; i++) {
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "kittycatcat", size) => 0;
 | |
|         pos = lfs_file_tell(&lfs, &file[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_rewind(&lfs, &file[0]) => 0;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], size, LFS_SEEK_CUR) => 3*size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     size = lfs_file_size(&lfs, &file[0]);
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Large file seek ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDONLY) => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     size = strlen("kittycatcat");
 | |
|     for (int i = 0; i < $MEDIUMSIZE; i++) {
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "kittycatcat", size) => 0;
 | |
|         pos = lfs_file_tell(&lfs, &file[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_rewind(&lfs, &file[0]) => 0;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], size, LFS_SEEK_CUR) => 3*size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     size = lfs_file_size(&lfs, &file[0]);
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Simple file seek and write ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     size = strlen("kittycatcat");
 | |
|     for (int i = 0; i < $SMALLSIZE; i++) {
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "kittycatcat", size) => 0;
 | |
|         pos = lfs_file_tell(&lfs, &file[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     memcpy(buffer, "doggodogdog", size);
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "doggodogdog", size) => 0;
 | |
| 
 | |
|     lfs_file_rewind(&lfs, &file[0]) => 0;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "doggodogdog", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     size = lfs_file_size(&lfs, &file[0]);
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Large file seek and write ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 | |
| 
 | |
|     lfs_soff_t pos;
 | |
|     size = strlen("kittycatcat");
 | |
|     for (int i = 0; i < $MEDIUMSIZE; i++) {
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         if (i != $SMALLSIZE) {
 | |
|             memcmp(buffer, "kittycatcat", size) => 0;
 | |
|         }
 | |
|         pos = lfs_file_tell(&lfs, &file[0]);
 | |
|     }
 | |
|     pos >= 0 => 1;
 | |
| 
 | |
|     memcpy(buffer, "doggodogdog", size);
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "doggodogdog", size) => 0;
 | |
| 
 | |
|     lfs_file_rewind(&lfs, &file[0]) => 0;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "doggodogdog", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|     size = lfs_file_size(&lfs, &file[0]);
 | |
|     lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Boundary seek and write ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 | |
| 
 | |
|     size = strlen("hedgehoghog");
 | |
|     const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
 | |
| 
 | |
|     for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
 | |
|         lfs_soff_t off = offsets[i];
 | |
|         memcpy(buffer, "hedgehoghog", size);
 | |
|         lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
 | |
|         lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
|         lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "hedgehoghog", size) => 0;
 | |
| 
 | |
|         lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_SET) => 0;
 | |
|         lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|         memcmp(buffer, "kittycatcat", size) => 0;
 | |
| 
 | |
|         lfs_file_sync(&lfs, &file[0]) => 0;
 | |
|     }
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Out-of-bounds seek ---"
 | |
| tests/test.py << TEST
 | |
|     lfs_mount(&lfs, &cfg) => 0;
 | |
|     lfs_file_open(&lfs, &file[0], "hello/kitty042", LFS_O_RDWR) => 0;
 | |
| 
 | |
|     size = strlen("kittycatcat");
 | |
|     lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size;
 | |
|     lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
 | |
|             LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => 0;
 | |
| 
 | |
|     memcpy(buffer, "porcupineee", size);
 | |
|     lfs_file_write(&lfs, &file[0], buffer, size) => size;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
 | |
|             LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "porcupineee", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], $LARGESIZE*size,
 | |
|             LFS_SEEK_SET) => $LARGESIZE*size;
 | |
|     lfs_file_read(&lfs, &file[0], buffer, size) => size;
 | |
|     memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+$SMALLSIZE)*size),
 | |
|             LFS_SEEK_CUR) => LFS_ERR_INVAL;
 | |
|     lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
 | |
| 
 | |
|     lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+2*$SMALLSIZE)*size),
 | |
|             LFS_SEEK_END) => LFS_ERR_INVAL;
 | |
|     lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
 | |
| 
 | |
|     lfs_file_close(&lfs, &file[0]) => 0;
 | |
|     lfs_unmount(&lfs) => 0;
 | |
| TEST
 | |
| 
 | |
| echo "--- Results ---"
 | |
| tests/stats.py
 |