From 6362afa8d06da19f9ce9b1b5b62100ec8a16031d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 1 Apr 2018 15:36:29 -0500 Subject: [PATCH] Added disk-backed limits on the name/attrs/inline sizes Being a portable, microcontroller-scale embedded filesystem, littlefs is presented with a relatively unique challenge. The amount of RAM available is on completely different scales from machine to machine, and what is normally a reasonable RAM assumption may break completely on an embedded system. A great example of this is file names. On almost every PC these days, the limit for a file name is 255 bytes. It's a very convenient limit for a number of reasons. However, on microcontrollers, allocating 255 bytes of RAM to do a file search can be unreasonable. The simplest solution (and one that has existing in littlefs for a while), is to let this limit be redefined to a smaller value on devices that need to save RAM. However, this presents an interesting portability issue. If these devices are plugged into a PC with relatively infinite RAM, nothing stops the PC from writing files with full 255-byte file names, which can't be read on the small device. One solution here is to store this limit on the superblock during format time. When mounting a disk, the filesystem implementation is responsible for checking this limit in the superblock. If it's larger than what can be read, raise an error. If it's smaller, respect the limit on the superblock and raise an error if the user attempts to exceed it. In this commit, this strategy is adopted for file names, inline files, and the size of all attributes, since these could impact the memory consumption of the filesystem. (Recording the attribute's limit is iffy, but is the only other arbitrary limit and could be used for disabling support of custom attributes). Note! This changes makes it very important to configure littlefs correctly at format time. If littlefs is formatted on a PC without changing the limits appropriately, it will be rejected by a smaller device. --- lfs.c | 150 +++++++++++++++++++++++++++++++++++++++++++++------------- lfs.h | 67 +++++++++++++++++--------- 2 files changed, 161 insertions(+), 56 deletions(-) diff --git a/lfs.c b/lfs.c index 8d3017c..ad3ad04 100644 --- a/lfs.c +++ b/lfs.c @@ -365,6 +365,9 @@ static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) { d->block_size = lfs_fromle32(d->block_size); d->block_count = lfs_fromle32(d->block_count); d->version = lfs_fromle32(d->version); + d->inline_size = lfs_fromle32(d->inline_size); + d->attrs_size = lfs_fromle32(d->attrs_size); + d->name_size = lfs_fromle32(d->name_size); } static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { @@ -373,6 +376,9 @@ static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { d->block_size = lfs_tole32(d->block_size); d->block_count = lfs_tole32(d->block_count); d->version = lfs_tole32(d->version); + d->inline_size = lfs_tole32(d->inline_size); + d->attrs_size = lfs_tole32(d->attrs_size); + d->name_size = lfs_tole32(d->name_size); } @@ -1018,6 +1024,12 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { return err ? err : LFS_ERR_EXIST; } + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + // build up new directory lfs_alloc_ack(lfs); @@ -1037,7 +1049,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { entry.d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR; entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; - entry.d.nlen = strlen(path); + entry.d.nlen = nlen; entry.d.u.dir[0] = dir.pair[0]; entry.d.u.dir[1] = dir.pair[1]; entry.size = 0; @@ -1046,7 +1058,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { cwd.d.tail[1] = dir.pair[1]; err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ {LFS_FROM_MEM, 0, &entry.d, sizeof(entry.d)}, - {LFS_FROM_MEM, 0, path, entry.d.nlen}}, 2); + {LFS_FROM_MEM, 0, path, nlen}}, 2); if (err) { return err; } @@ -1427,16 +1439,22 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, return LFS_ERR_NOENT; } + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + // create entry to remember name entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG; entry.d.elen = 0; entry.d.alen = 0; - entry.d.nlen = strlen(path); + entry.d.nlen = nlen; entry.size = 0; err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){ {LFS_FROM_MEM, 0, &entry.d, 4}, - {LFS_FROM_MEM, 0, path, entry.d.nlen}}, 2); + {LFS_FROM_MEM, 0, path, nlen}}, 2); if (err) { return err; } @@ -1571,10 +1589,6 @@ relocate:; static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { if (file->flags & LFS_F_READING) { - if (!(file->flags & LFS_F_INLINE)) { - // just drop read cache - file->cache.block = 0xffffffff; - } file->flags &= ~LFS_F_READING; } @@ -1807,9 +1821,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, // TODO need to move out if no longer fits in block also // TODO store INLINE_MAX in superblock? // TODO what if inline files is > block size (ie 128) - if ((file->flags & LFS_F_INLINE) && ( - (file->pos + nsize >= LFS_INLINE_MAX) || - (file->pos + nsize >= lfs->cfg->read_size))) { + if ((file->flags & LFS_F_INLINE) && + file->pos + nsize >= lfs->inline_size) { file->block = 0xfffffffe; file->off = file->pos; @@ -2137,6 +2150,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { bool prevexists = (err != LFS_ERR_NOENT); bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0); + // check that name fits + lfs_size_t nlen = strlen(newpath); + if (nlen > lfs->name_size) { + return LFS_ERR_NAMETOOLONG; + } + // must have same type if (prevexists && preventry.d.type != oldentry.d.type) { return LFS_ERR_ISDIR; @@ -2174,7 +2193,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { lfs_entry_t newentry = preventry; newentry.d = oldentry.d; newentry.d.type &= ~LFS_STRUCT_MOVED; - newentry.d.nlen = strlen(newpath); + newentry.d.nlen = nlen; if (!prevexists) { newentry.size = 0; } @@ -2185,8 +2204,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { oldcwd.pair[0], oldentry.off, (struct lfs_region[]){ {LFS_FROM_MEM, 0, &newentry.d, 4}, {LFS_FROM_DROP, 0, NULL, -4}, - {LFS_FROM_MEM, newsize - newentry.d.nlen, - newpath, newentry.d.nlen}}, 3}, + {LFS_FROM_MEM, newsize - nlen, newpath, nlen}}, 3}, newsize}, {LFS_FROM_DROP, 0, NULL, -preventry.size}}, prevexists ? 2 : 1); if (err) { @@ -2272,6 +2290,26 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) <= lfs->cfg->block_size); + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->inline_size <= LFS_INLINE_MAX); + LFS_ASSERT(lfs->cfg->inline_size <= lfs->cfg->read_size); + lfs->inline_size = lfs->cfg->inline_size; + if (!lfs->inline_size) { + lfs->inline_size = lfs_min(LFS_INLINE_MAX, lfs->cfg->read_size); + } + + LFS_ASSERT(lfs->cfg->attrs_size <= LFS_ATTRS_MAX); + lfs->attrs_size = lfs->cfg->attrs_size; + if (!lfs->attrs_size) { + lfs->attrs_size = LFS_ATTRS_MAX; + } + + LFS_ASSERT(lfs->cfg->name_size <= LFS_NAME_MAX); + lfs->name_size = lfs->cfg->name_size; + if (!lfs->name_size) { + lfs->name_size = LFS_NAME_MAX; + } + // setup default state lfs->root[0] = 0xffffffff; lfs->root[1] = 0xffffffff; @@ -2336,13 +2374,16 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { superdir.d.tail[0] = lfs->root[0]; superdir.d.tail[1] = lfs->root[1]; - // write one superblocks + // write one superblock lfs_superblock_t superblock; superblock.d.version = LFS_DISK_VERSION, superblock.d.root[0] = lfs->root[0]; superblock.d.root[1] = lfs->root[1]; superblock.d.block_size = lfs->cfg->block_size; superblock.d.block_count = lfs->cfg->block_count; + superblock.d.inline_size = lfs->inline_size; + superblock.d.attrs_size = lfs->attrs_size; + superblock.d.name_size = lfs->name_size; lfs_entry_t superentry; superentry.d.type = LFS_STRUCT_DIR | LFS_TYPE_SUPERBLOCK; @@ -2385,33 +2426,41 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { // load superblock lfs_dir_t dir; + lfs_entry_t entry; lfs_superblock_t superblock; char magic[8]; + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); - if (err && err != LFS_ERR_CORRUPT) { + if (err) { + if (err == LFS_ERR_CORRUPT) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + } return err; } - if (!err) { - err = lfs_dir_get(lfs, &dir, - sizeof(dir.d)+4, &superblock.d, sizeof(superblock.d)); - lfs_superblock_fromle32(&superblock.d); - if (err) { - return err; - } - - err = lfs_dir_get(lfs, &dir, - sizeof(dir.d)+4 + sizeof(superblock.d), magic, sizeof(magic)); - if (err) { - return err; - } - - lfs->root[0] = superblock.d.root[0]; - lfs->root[1] = superblock.d.root[1]; + err = lfs_dir_get(lfs, &dir, sizeof(dir.d), &entry.d, sizeof(entry.d)); + if (err) { + return err; } - if (err || memcmp(magic, "littlefs", 8) != 0) { - LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]); + memset(&superblock.d, 0, sizeof(superblock.d)); + err = lfs_dir_get(lfs, &dir, + sizeof(dir.d)+4, &superblock.d, + lfs_min(sizeof(superblock.d), entry.d.elen)); + lfs_superblock_fromle32(&superblock.d); + if (err) { + return err; + } + + err = lfs_dir_get(lfs, &dir, + sizeof(dir.d)+4+entry.d.elen+entry.d.alen, magic, + lfs_min(sizeof(magic), entry.d.nlen)); + if (err) { + return err; + } + + if (memcmp(magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); return LFS_ERR_CORRUPT; } @@ -2423,6 +2472,39 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { return LFS_ERR_INVAL; } + if (superblock.d.inline_size) { + if (superblock.d.inline_size > lfs->inline_size) { + LFS_ERROR("Unsupported inline size (%d > %d)", + superblock.d.inline_size, lfs->inline_size); + return LFS_ERR_INVAL; + } + + lfs->inline_size = superblock.d.inline_size; + } + + if (superblock.d.attrs_size) { + if (superblock.d.attrs_size > lfs->attrs_size) { + LFS_ERROR("Unsupported attrs size (%d > %d)", + superblock.d.attrs_size, lfs->attrs_size); + return LFS_ERR_INVAL; + } + + lfs->attrs_size = superblock.d.attrs_size; + } + + if (superblock.d.name_size) { + if (superblock.d.name_size > lfs->name_size) { + LFS_ERROR("Unsupported name size (%d > %d)", + superblock.d.name_size, lfs->name_size); + return LFS_ERR_INVAL; + } + + lfs->name_size = superblock.d.name_size; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + return 0; } diff --git a/lfs.h b/lfs.h index 6acf5e7..9a7b8c3 100644 --- a/lfs.h +++ b/lfs.h @@ -50,30 +50,37 @@ typedef int32_t lfs_soff_t; typedef uint32_t lfs_block_t; +// Maximum inline file size in bytes +#ifndef LFS_INLINE_MAX +#define LFS_INLINE_MAX 255 +#endif + +// Maximum size of all attributes per file in bytes +#ifndef LFS_ATTRS_MAX +#define LFS_ATTRS_MAX 255 +#endif + // Max name size in bytes #ifndef LFS_NAME_MAX #define LFS_NAME_MAX 255 #endif -#ifndef LFS_INLINE_MAX -#define LFS_INLINE_MAX 255 -#endif - // Possible error codes, these are negative to allow // valid positive return values enum lfs_error { - LFS_ERR_OK = 0, // No error - LFS_ERR_IO = -5, // Error during device operation - LFS_ERR_CORRUPT = -52, // Corrupted - LFS_ERR_NOENT = -2, // No directory entry - LFS_ERR_EXIST = -17, // Entry already exists - LFS_ERR_NOTDIR = -20, // Entry is not a dir - LFS_ERR_ISDIR = -21, // Entry is a dir - LFS_ERR_NOTEMPTY = -39, // Dir is not empty - LFS_ERR_BADF = -9, // Bad file number - LFS_ERR_INVAL = -22, // Invalid parameter - LFS_ERR_NOSPC = -28, // No space left on device - LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -52, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NAMETOOLONG = -36, // File name too long }; // File types @@ -102,10 +109,10 @@ enum lfs_open_flags { LFS_O_APPEND = 0x0800, // Move to end of file on every write // internally used flags - LFS_F_DIRTY = 0x10000, // File does not match storage - LFS_F_WRITING = 0x20000, // File has been written since last flush - LFS_F_READING = 0x40000, // File has been read since last flush - LFS_F_ERRED = 0x80000, // An error occured during write + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush + LFS_F_READING = 0x040000, // File has been read since last flush + LFS_F_ERRED = 0x080000, // An error occured during write LFS_F_INLINE = 0x100000, // Currently inlined in directory entry }; @@ -183,6 +190,13 @@ struct lfs_config { // Optional, statically allocated buffer for files. Must be program sized. // If enabled, only one file may be opened at a time. void *file_buffer; + + // Optional, + lfs_size_t inline_size; + // Optional, + lfs_size_t attrs_size; + // Optional, + lfs_size_t name_size; }; @@ -258,9 +272,14 @@ typedef struct lfs_dir { typedef struct lfs_superblock { struct lfs_disk_superblock { lfs_block_t root[2]; - uint32_t block_size; - uint32_t block_count; + + lfs_size_t block_size; + lfs_size_t block_count; uint32_t version; + + lfs_size_t inline_size; + lfs_size_t attrs_size; + lfs_size_t name_size; } d; } lfs_superblock_t; @@ -285,6 +304,10 @@ typedef struct lfs { lfs_free_t free; bool deorphaned; + + lfs_size_t inline_size; + lfs_size_t attrs_size; + lfs_size_t name_size; } lfs_t;