Expanded inline files up to a limit of 1023 bytes

One of the big benefits of inline files is that small files no longer need to
take up a full block. This opens up an opportunity to provide much better
support for storage devices with only a handful of very large blocks. Such as
the internal flash found on most microcontrollers.

After investigating some use cases for a filesystem on internal flash,
it has become apparent that the 255-byte limit is going to be too
restrictive to be useful in many cases. Most uses I found needed files
~4-64 bytes in size, but it wasn't uncommon to find files ~512 bytes in
length.

To try to remedy this, I've pushed the 255 byte limit up to 1023 bytes,
by stealing some bits from the previously-unused attributes's size.
Unfortunately this limits attributes to 63 bytes in total and has a
minor code cost, but I'm not sure even 1023 bytes will be sufficient for
a lot of cases.

The littlefs will probably never be as efficient with internal flash as
other filesystems such as SPIFFS, it just wasn't designed for this sort of
limited geometry. However, this feature has been heavily requested, even
with limitations, because of the opportunity for code reuse on
microcontrollers with both internal and external flash.
This commit is contained in:
Christopher Haster
2018-04-03 08:28:09 -05:00
parent 6362afa8d0
commit 6774276124
2 changed files with 71 additions and 39 deletions

103
lfs.c
View File

@@ -381,6 +381,26 @@ static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
d->name_size = lfs_tole32(d->name_size); d->name_size = lfs_tole32(d->name_size);
} }
/// Other struct functions ///
static inline lfs_size_t lfs_entry_elen(const lfs_entry_t *entry) {
return (lfs_size_t)(entry->d.elen) |
((lfs_size_t)(entry->d.alen & 0xc0) << 2);
}
static inline lfs_size_t lfs_entry_alen(const lfs_entry_t *entry) {
return entry->d.alen & 0x3f;
}
static inline lfs_size_t lfs_entry_nlen(const lfs_entry_t *entry) {
return entry->d.nlen;
}
static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) {
return 4 + lfs_entry_elen(entry) +
lfs_entry_alen(entry) +
lfs_entry_nlen(entry);
}
/// Metadata pair and directory operations /// /// Metadata pair and directory operations ///
static inline void lfs_pairswap(lfs_block_t pair[2]) { static inline void lfs_pairswap(lfs_block_t pair[2]) {
@@ -573,7 +593,7 @@ static int lfs_commit_region(lfs_t *lfs,
return 0; return 0;
} }
static int lfs_dif_commit(lfs_t *lfs, lfs_dir_t *dir, static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
const struct lfs_region *regions, int count) { const struct lfs_region *regions, int count) {
// state for copying over // state for copying over
const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
@@ -733,7 +753,7 @@ static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
} }
type |= LFS_STRUCT_MOVED; type |= LFS_STRUCT_MOVED;
err = lfs_dif_commit(lfs, &olddir, (struct lfs_region[]){ err = lfs_dir_commit(lfs, &olddir, (struct lfs_region[]){
{LFS_FROM_MEM, oldoff, &type, 1}, {LFS_FROM_MEM, oldoff, &type, 1},
{LFS_FROM_DROP, oldoff, NULL, -1}}, 2); {LFS_FROM_DROP, oldoff, NULL, -1}}, 2);
if (err) { if (err) {
@@ -769,7 +789,7 @@ static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
// writing out new entry // writing out new entry
entry->off = dir->d.size - 4; entry->off = dir->d.size - 4;
entry->size += diff; entry->size += diff;
int err = lfs_dif_commit(lfs, dir, (struct lfs_region[]){ int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
{LFS_FROM_REGION, entry->off, &(struct lfs_region_region){ {LFS_FROM_REGION, entry->off, &(struct lfs_region_region){
olddir.pair[0], oldoff, olddir.pair[0], oldoff,
regions, count}, entry->size}}, 1); regions, count}, entry->size}}, 1);
@@ -783,7 +803,7 @@ static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
pdir.d.tail[0] = dir->pair[0]; pdir.d.tail[0] = dir->pair[0];
pdir.d.tail[1] = dir->pair[1]; pdir.d.tail[1] = dir->pair[1];
err = lfs_dif_commit(lfs, &pdir, NULL, 0); err = lfs_dir_commit(lfs, &pdir, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -818,7 +838,7 @@ static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.size &= dir->d.size | 0x7fffffff;
pdir.d.tail[0] = dir->d.tail[0]; pdir.d.tail[0] = dir->d.tail[0];
pdir.d.tail[1] = dir->d.tail[1]; pdir.d.tail[1] = dir->d.tail[1];
int err = lfs_dif_commit(lfs, &pdir, NULL, 0); int err = lfs_dir_commit(lfs, &pdir, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -830,7 +850,7 @@ static int lfs_dir_set(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry,
regions[i].off += entry->off; regions[i].off += entry->off;
} }
int err = lfs_dif_commit(lfs, dir, regions, count); int err = lfs_dir_commit(lfs, dir, regions, count);
if (err) { if (err) {
return err; return err;
} }
@@ -885,7 +905,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
} }
entry->off = dir->off; entry->off = dir->off;
entry->size = 4 + entry->d.elen + entry->d.alen + entry->d.nlen; entry->size = lfs_entry_size(entry);
dir->off += entry->size; dir->off += entry->size;
dir->pos += entry->size; dir->pos += entry->size;
return 0; return 0;
@@ -1041,7 +1061,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
dir.d.tail[0] = cwd.d.tail[0]; dir.d.tail[0] = cwd.d.tail[0];
dir.d.tail[1] = cwd.d.tail[1]; dir.d.tail[1] = cwd.d.tail[1];
err = lfs_dif_commit(lfs, &dir, NULL, 0); err = lfs_dir_commit(lfs, &dir, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -1166,7 +1186,7 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) {
info->size = entry.d.u.file.size; info->size = entry.d.u.file.size;
} else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { } else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) {
info->size = entry.d.elen; info->size = lfs_entry_elen(&entry);
} }
int err = lfs_dir_get(lfs, dir, int err = lfs_dir_get(lfs, dir,
@@ -1480,29 +1500,22 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
} }
} }
// TODO combine these below?
// setup file struct // setup file struct
file->pair[0] = cwd.pair[0]; file->pair[0] = cwd.pair[0];
file->pair[1] = cwd.pair[1]; file->pair[1] = cwd.pair[1];
file->poff = entry.off; file->poff = entry.off;
file->head = entry.d.u.file.head;
file->size = entry.d.u.file.size;
file->flags = flags; file->flags = flags;
file->pos = 0; file->pos = 0;
if (flags & LFS_O_TRUNC) { // calculate max inline size based on the size of the entry
if (file->size != 0) { file->inline_size = lfs_min(lfs->inline_size,
file->flags |= LFS_F_DIRTY; lfs->cfg->block_size - (sizeof(cwd.d)+4) -
} (lfs_entry_size(&entry) - lfs_entry_elen(&entry)));
entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG;
entry.d.elen = 0;
}
// load inline files
if ((0x70 & entry.d.type) == LFS_STRUCT_INLINE) { if ((0x70 & entry.d.type) == LFS_STRUCT_INLINE) {
// load inline files
file->head = 0xfffffffe; file->head = 0xfffffffe;
file->size = entry.d.elen; file->size = lfs_entry_elen(&entry);
file->flags |= LFS_F_INLINE; file->flags |= LFS_F_INLINE;
file->cache.block = file->head; file->cache.block = file->head;
file->cache.off = 0; file->cache.off = 0;
@@ -1513,6 +1526,23 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
lfs_free(file->cache.buffer); lfs_free(file->cache.buffer);
return err; return err;
} }
} else {
// use ctz list from entry
file->head = entry.d.u.file.head;
file->size = entry.d.u.file.size;
}
// truncate if requested
if (flags & LFS_O_TRUNC) {
if (file->size != 0) {
file->flags |= LFS_F_DIRTY;
}
file->head = 0xfffffffe;
file->size = 0;
file->flags |= LFS_F_INLINE;
file->cache.block = file->head;
file->cache.off = 0;
} }
// add to list of files // add to list of files
@@ -1686,8 +1716,8 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
} }
LFS_ASSERT((0xf & entry.d.type) == LFS_TYPE_REG); LFS_ASSERT((0xf & entry.d.type) == LFS_TYPE_REG);
lfs_size_t oldlen = entry.d.elen; lfs_size_t oldlen = lfs_entry_elen(&entry);
entry.size = 4 + entry.d.elen + entry.d.alen + entry.d.nlen; entry.size = lfs_entry_size(&entry);
// either update the references or inline the whole file // either update the references or inline the whole file
if (!(file->flags & LFS_F_INLINE)) { if (!(file->flags & LFS_F_INLINE)) {
@@ -1704,7 +1734,8 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
} }
} else { } else {
entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG; entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG;
entry.d.elen = file->size; entry.d.elen = file->size & 0xff;
entry.d.alen = (entry.d.alen & 0x3f) | ((file->size >> 2) & 0xc0);
err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){
{LFS_FROM_MEM, 0, &entry.d, 4}, {LFS_FROM_MEM, 0, &entry.d, 4},
@@ -1822,7 +1853,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
// TODO store INLINE_MAX in superblock? // TODO store INLINE_MAX in superblock?
// TODO what if inline files is > block size (ie 128) // TODO what if inline files is > block size (ie 128)
if ((file->flags & LFS_F_INLINE) && if ((file->flags & LFS_F_INLINE) &&
file->pos + nsize >= lfs->inline_size) { file->pos + nsize >= file->inline_size) {
file->block = 0xfffffffe; file->block = 0xfffffffe;
file->off = file->pos; file->off = file->pos;
@@ -2034,7 +2065,7 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) { if (entry.d.type == (LFS_STRUCT_CTZ | LFS_TYPE_REG)) {
info->size = entry.d.u.file.size; info->size = entry.d.u.file.size;
} else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) { } else if (entry.d.type == (LFS_STRUCT_INLINE | LFS_TYPE_REG)) {
info->size = entry.d.elen; info->size = lfs_entry_elen(&entry);
} }
if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) {
@@ -2103,7 +2134,7 @@ int lfs_remove(lfs_t *lfs, const char *path) {
cwd.d.tail[0] = dir.d.tail[0]; cwd.d.tail[0] = dir.d.tail[0];
cwd.d.tail[1] = dir.d.tail[1]; cwd.d.tail[1] = dir.d.tail[1];
err = lfs_dif_commit(lfs, &cwd, NULL, 0); err = lfs_dir_commit(lfs, &cwd, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -2234,7 +2265,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
newcwd.d.tail[0] = dir.d.tail[0]; newcwd.d.tail[0] = dir.d.tail[0];
newcwd.d.tail[1] = dir.d.tail[1]; newcwd.d.tail[1] = dir.d.tail[1];
err = lfs_dif_commit(lfs, &newcwd, NULL, 0); err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -2364,7 +2395,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
return err; return err;
} }
err = lfs_dif_commit(lfs, &root, NULL, 0); err = lfs_dir_commit(lfs, &root, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -2446,14 +2477,14 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
memset(&superblock.d, 0, sizeof(superblock.d)); memset(&superblock.d, 0, sizeof(superblock.d));
err = lfs_dir_get(lfs, &dir, err = lfs_dir_get(lfs, &dir,
sizeof(dir.d)+4, &superblock.d, sizeof(dir.d)+4, &superblock.d,
lfs_min(sizeof(superblock.d), entry.d.elen)); lfs_min(sizeof(superblock.d), lfs_entry_elen(&entry)));
lfs_superblock_fromle32(&superblock.d); lfs_superblock_fromle32(&superblock.d);
if (err) { if (err) {
return err; return err;
} }
err = lfs_dir_get(lfs, &dir, err = lfs_dir_get(lfs, &dir,
sizeof(dir.d)+4+entry.d.elen+entry.d.alen, magic, sizeof(dir.d)+lfs_entry_size(&entry)-entry.d.nlen, magic,
lfs_min(sizeof(magic), entry.d.nlen)); lfs_min(sizeof(magic), entry.d.nlen));
if (err) { if (err) {
return err; return err;
@@ -2546,7 +2577,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
return err; return err;
} }
dir.off += 4 + entry.d.elen + entry.d.alen + entry.d.nlen; dir.off += lfs_entry_size(&entry);
if ((0x70 & entry.d.type) == LFS_STRUCT_CTZ) { if ((0x70 & entry.d.type) == LFS_STRUCT_CTZ) {
err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
entry.d.u.file.head, entry.d.u.file.size, cb, data); entry.d.u.file.head, entry.d.u.file.size, cb, data);
@@ -2730,7 +2761,7 @@ static int lfs_relocate(lfs_t *lfs,
parent.d.tail[0] = newpair[0]; parent.d.tail[0] = newpair[0];
parent.d.tail[1] = newpair[1]; parent.d.tail[1] = newpair[1];
return lfs_dif_commit(lfs, &parent, NULL, 0); return lfs_dir_commit(lfs, &parent, NULL, 0);
} }
// couldn't find dir, must be new // couldn't find dir, must be new
@@ -2772,7 +2803,7 @@ int lfs_deorphan(lfs_t *lfs) {
pdir.d.tail[0] = cwd.d.tail[0]; pdir.d.tail[0] = cwd.d.tail[0];
pdir.d.tail[1] = cwd.d.tail[1]; pdir.d.tail[1] = cwd.d.tail[1];
err = lfs_dif_commit(lfs, &pdir, NULL, 0); err = lfs_dir_commit(lfs, &pdir, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@@ -2788,7 +2819,7 @@ int lfs_deorphan(lfs_t *lfs) {
pdir.d.tail[0] = entry.d.u.dir[0]; pdir.d.tail[0] = entry.d.u.dir[0];
pdir.d.tail[1] = entry.d.u.dir[1]; pdir.d.tail[1] = entry.d.u.dir[1];
err = lfs_dif_commit(lfs, &pdir, NULL, 0); err = lfs_dir_commit(lfs, &pdir, NULL, 0);
if (err) { if (err) {
return err; return err;
} }

7
lfs.h
View File

@@ -52,17 +52,17 @@ typedef uint32_t lfs_block_t;
// Maximum inline file size in bytes // Maximum inline file size in bytes
#ifndef LFS_INLINE_MAX #ifndef LFS_INLINE_MAX
#define LFS_INLINE_MAX 255 #define LFS_INLINE_MAX 0x3ff
#endif #endif
// Maximum size of all attributes per file in bytes // Maximum size of all attributes per file in bytes
#ifndef LFS_ATTRS_MAX #ifndef LFS_ATTRS_MAX
#define LFS_ATTRS_MAX 255 #define LFS_ATTRS_MAX 0x3f
#endif #endif
// Max name size in bytes // Max name size in bytes
#ifndef LFS_NAME_MAX #ifndef LFS_NAME_MAX
#define LFS_NAME_MAX 255 #define LFS_NAME_MAX 0xff
#endif #endif
// Possible error codes, these are negative to allow // Possible error codes, these are negative to allow
@@ -248,6 +248,7 @@ typedef struct lfs_file {
lfs_size_t size; lfs_size_t size;
uint32_t flags; uint32_t flags;
lfs_size_t inline_size;
lfs_off_t pos; lfs_off_t pos;
lfs_block_t block; lfs_block_t block;
lfs_off_t off; lfs_off_t off;