Skip to content

Commit 6362afa

Browse files
committed
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.
1 parent 9555458 commit 6362afa

File tree

2 files changed

+159
-54
lines changed

2 files changed

+159
-54
lines changed

lfs.c

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@ static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) {
365365
d->block_size = lfs_fromle32(d->block_size);
366366
d->block_count = lfs_fromle32(d->block_count);
367367
d->version = lfs_fromle32(d->version);
368+
d->inline_size = lfs_fromle32(d->inline_size);
369+
d->attrs_size = lfs_fromle32(d->attrs_size);
370+
d->name_size = lfs_fromle32(d->name_size);
368371
}
369372

370373
static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
@@ -373,6 +376,9 @@ static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
373376
d->block_size = lfs_tole32(d->block_size);
374377
d->block_count = lfs_tole32(d->block_count);
375378
d->version = lfs_tole32(d->version);
379+
d->inline_size = lfs_tole32(d->inline_size);
380+
d->attrs_size = lfs_tole32(d->attrs_size);
381+
d->name_size = lfs_tole32(d->name_size);
376382
}
377383

378384

@@ -1018,6 +1024,12 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
10181024
return err ? err : LFS_ERR_EXIST;
10191025
}
10201026

1027+
// check that name fits
1028+
lfs_size_t nlen = strlen(path);
1029+
if (nlen > lfs->name_size) {
1030+
return LFS_ERR_NAMETOOLONG;
1031+
}
1032+
10211033
// build up new directory
10221034
lfs_alloc_ack(lfs);
10231035

@@ -1037,7 +1049,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
10371049
entry.d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR;
10381050
entry.d.elen = sizeof(entry.d) - 4;
10391051
entry.d.alen = 0;
1040-
entry.d.nlen = strlen(path);
1052+
entry.d.nlen = nlen;
10411053
entry.d.u.dir[0] = dir.pair[0];
10421054
entry.d.u.dir[1] = dir.pair[1];
10431055
entry.size = 0;
@@ -1046,7 +1058,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
10461058
cwd.d.tail[1] = dir.pair[1];
10471059
err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){
10481060
{LFS_FROM_MEM, 0, &entry.d, sizeof(entry.d)},
1049-
{LFS_FROM_MEM, 0, path, entry.d.nlen}}, 2);
1061+
{LFS_FROM_MEM, 0, path, nlen}}, 2);
10501062
if (err) {
10511063
return err;
10521064
}
@@ -1427,16 +1439,22 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
14271439
return LFS_ERR_NOENT;
14281440
}
14291441

1442+
// check that name fits
1443+
lfs_size_t nlen = strlen(path);
1444+
if (nlen > lfs->name_size) {
1445+
return LFS_ERR_NAMETOOLONG;
1446+
}
1447+
14301448
// create entry to remember name
14311449
entry.d.type = LFS_STRUCT_INLINE | LFS_TYPE_REG;
14321450
entry.d.elen = 0;
14331451
entry.d.alen = 0;
1434-
entry.d.nlen = strlen(path);
1452+
entry.d.nlen = nlen;
14351453
entry.size = 0;
14361454

14371455
err = lfs_dir_set(lfs, &cwd, &entry, (struct lfs_region[]){
14381456
{LFS_FROM_MEM, 0, &entry.d, 4},
1439-
{LFS_FROM_MEM, 0, path, entry.d.nlen}}, 2);
1457+
{LFS_FROM_MEM, 0, path, nlen}}, 2);
14401458
if (err) {
14411459
return err;
14421460
}
@@ -1571,10 +1589,6 @@ relocate:;
15711589

15721590
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
15731591
if (file->flags & LFS_F_READING) {
1574-
if (!(file->flags & LFS_F_INLINE)) {
1575-
// just drop read cache
1576-
file->cache.block = 0xffffffff;
1577-
}
15781592
file->flags &= ~LFS_F_READING;
15791593
}
15801594

@@ -1807,9 +1821,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
18071821
// TODO need to move out if no longer fits in block also
18081822
// TODO store INLINE_MAX in superblock?
18091823
// TODO what if inline files is > block size (ie 128)
1810-
if ((file->flags & LFS_F_INLINE) && (
1811-
(file->pos + nsize >= LFS_INLINE_MAX) ||
1812-
(file->pos + nsize >= lfs->cfg->read_size))) {
1824+
if ((file->flags & LFS_F_INLINE) &&
1825+
file->pos + nsize >= lfs->inline_size) {
18131826
file->block = 0xfffffffe;
18141827
file->off = file->pos;
18151828

@@ -2137,6 +2150,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
21372150
bool prevexists = (err != LFS_ERR_NOENT);
21382151
bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
21392152

2153+
// check that name fits
2154+
lfs_size_t nlen = strlen(newpath);
2155+
if (nlen > lfs->name_size) {
2156+
return LFS_ERR_NAMETOOLONG;
2157+
}
2158+
21402159
// must have same type
21412160
if (prevexists && preventry.d.type != oldentry.d.type) {
21422161
return LFS_ERR_ISDIR;
@@ -2174,7 +2193,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
21742193
lfs_entry_t newentry = preventry;
21752194
newentry.d = oldentry.d;
21762195
newentry.d.type &= ~LFS_STRUCT_MOVED;
2177-
newentry.d.nlen = strlen(newpath);
2196+
newentry.d.nlen = nlen;
21782197
if (!prevexists) {
21792198
newentry.size = 0;
21802199
}
@@ -2185,8 +2204,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
21852204
oldcwd.pair[0], oldentry.off, (struct lfs_region[]){
21862205
{LFS_FROM_MEM, 0, &newentry.d, 4},
21872206
{LFS_FROM_DROP, 0, NULL, -4},
2188-
{LFS_FROM_MEM, newsize - newentry.d.nlen,
2189-
newpath, newentry.d.nlen}}, 3},
2207+
{LFS_FROM_MEM, newsize - nlen, newpath, nlen}}, 3},
21902208
newsize},
21912209
{LFS_FROM_DROP, 0, NULL, -preventry.size}}, prevexists ? 2 : 1);
21922210
if (err) {
@@ -2272,6 +2290,26 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
22722290
LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
22732291
<= lfs->cfg->block_size);
22742292

2293+
// check that the size limits are sane
2294+
LFS_ASSERT(lfs->cfg->inline_size <= LFS_INLINE_MAX);
2295+
LFS_ASSERT(lfs->cfg->inline_size <= lfs->cfg->read_size);
2296+
lfs->inline_size = lfs->cfg->inline_size;
2297+
if (!lfs->inline_size) {
2298+
lfs->inline_size = lfs_min(LFS_INLINE_MAX, lfs->cfg->read_size);
2299+
}
2300+
2301+
LFS_ASSERT(lfs->cfg->attrs_size <= LFS_ATTRS_MAX);
2302+
lfs->attrs_size = lfs->cfg->attrs_size;
2303+
if (!lfs->attrs_size) {
2304+
lfs->attrs_size = LFS_ATTRS_MAX;
2305+
}
2306+
2307+
LFS_ASSERT(lfs->cfg->name_size <= LFS_NAME_MAX);
2308+
lfs->name_size = lfs->cfg->name_size;
2309+
if (!lfs->name_size) {
2310+
lfs->name_size = LFS_NAME_MAX;
2311+
}
2312+
22752313
// setup default state
22762314
lfs->root[0] = 0xffffffff;
22772315
lfs->root[1] = 0xffffffff;
@@ -2336,13 +2374,16 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
23362374
superdir.d.tail[0] = lfs->root[0];
23372375
superdir.d.tail[1] = lfs->root[1];
23382376

2339-
// write one superblocks
2377+
// write one superblock
23402378
lfs_superblock_t superblock;
23412379
superblock.d.version = LFS_DISK_VERSION,
23422380
superblock.d.root[0] = lfs->root[0];
23432381
superblock.d.root[1] = lfs->root[1];
23442382
superblock.d.block_size = lfs->cfg->block_size;
23452383
superblock.d.block_count = lfs->cfg->block_count;
2384+
superblock.d.inline_size = lfs->inline_size;
2385+
superblock.d.attrs_size = lfs->attrs_size;
2386+
superblock.d.name_size = lfs->name_size;
23462387

23472388
lfs_entry_t superentry;
23482389
superentry.d.type = LFS_STRUCT_DIR | LFS_TYPE_SUPERBLOCK;
@@ -2385,33 +2426,41 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
23852426

23862427
// load superblock
23872428
lfs_dir_t dir;
2429+
lfs_entry_t entry;
23882430
lfs_superblock_t superblock;
23892431
char magic[8];
2432+
23902433
err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
2391-
if (err && err != LFS_ERR_CORRUPT) {
2434+
if (err) {
2435+
if (err == LFS_ERR_CORRUPT) {
2436+
LFS_ERROR("Invalid superblock at %d %d", 0, 1);
2437+
}
23922438
return err;
23932439
}
23942440

2395-
if (!err) {
2396-
err = lfs_dir_get(lfs, &dir,
2397-
sizeof(dir.d)+4, &superblock.d, sizeof(superblock.d));
2398-
lfs_superblock_fromle32(&superblock.d);
2399-
if (err) {
2400-
return err;
2401-
}
2441+
err = lfs_dir_get(lfs, &dir, sizeof(dir.d), &entry.d, sizeof(entry.d));
2442+
if (err) {
2443+
return err;
2444+
}
24022445

2403-
err = lfs_dir_get(lfs, &dir,
2404-
sizeof(dir.d)+4 + sizeof(superblock.d), magic, sizeof(magic));
2405-
if (err) {
2406-
return err;
2407-
}
2446+
memset(&superblock.d, 0, sizeof(superblock.d));
2447+
err = lfs_dir_get(lfs, &dir,
2448+
sizeof(dir.d)+4, &superblock.d,
2449+
lfs_min(sizeof(superblock.d), entry.d.elen));
2450+
lfs_superblock_fromle32(&superblock.d);
2451+
if (err) {
2452+
return err;
2453+
}
24082454

2409-
lfs->root[0] = superblock.d.root[0];
2410-
lfs->root[1] = superblock.d.root[1];
2455+
err = lfs_dir_get(lfs, &dir,
2456+
sizeof(dir.d)+4+entry.d.elen+entry.d.alen, magic,
2457+
lfs_min(sizeof(magic), entry.d.nlen));
2458+
if (err) {
2459+
return err;
24112460
}
24122461

2413-
if (err || memcmp(magic, "littlefs", 8) != 0) {
2414-
LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]);
2462+
if (memcmp(magic, "littlefs", 8) != 0) {
2463+
LFS_ERROR("Invalid superblock at %d %d", 0, 1);
24152464
return LFS_ERR_CORRUPT;
24162465
}
24172466

@@ -2423,6 +2472,39 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
24232472
return LFS_ERR_INVAL;
24242473
}
24252474

2475+
if (superblock.d.inline_size) {
2476+
if (superblock.d.inline_size > lfs->inline_size) {
2477+
LFS_ERROR("Unsupported inline size (%d > %d)",
2478+
superblock.d.inline_size, lfs->inline_size);
2479+
return LFS_ERR_INVAL;
2480+
}
2481+
2482+
lfs->inline_size = superblock.d.inline_size;
2483+
}
2484+
2485+
if (superblock.d.attrs_size) {
2486+
if (superblock.d.attrs_size > lfs->attrs_size) {
2487+
LFS_ERROR("Unsupported attrs size (%d > %d)",
2488+
superblock.d.attrs_size, lfs->attrs_size);
2489+
return LFS_ERR_INVAL;
2490+
}
2491+
2492+
lfs->attrs_size = superblock.d.attrs_size;
2493+
}
2494+
2495+
if (superblock.d.name_size) {
2496+
if (superblock.d.name_size > lfs->name_size) {
2497+
LFS_ERROR("Unsupported name size (%d > %d)",
2498+
superblock.d.name_size, lfs->name_size);
2499+
return LFS_ERR_INVAL;
2500+
}
2501+
2502+
lfs->name_size = superblock.d.name_size;
2503+
}
2504+
2505+
lfs->root[0] = superblock.d.root[0];
2506+
lfs->root[1] = superblock.d.root[1];
2507+
24262508
return 0;
24272509
}
24282510

lfs.h

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,30 +50,37 @@ typedef int32_t lfs_soff_t;
5050

5151
typedef uint32_t lfs_block_t;
5252

53+
// Maximum inline file size in bytes
54+
#ifndef LFS_INLINE_MAX
55+
#define LFS_INLINE_MAX 255
56+
#endif
57+
58+
// Maximum size of all attributes per file in bytes
59+
#ifndef LFS_ATTRS_MAX
60+
#define LFS_ATTRS_MAX 255
61+
#endif
62+
5363
// Max name size in bytes
5464
#ifndef LFS_NAME_MAX
5565
#define LFS_NAME_MAX 255
5666
#endif
5767

58-
#ifndef LFS_INLINE_MAX
59-
#define LFS_INLINE_MAX 255
60-
#endif
61-
6268
// Possible error codes, these are negative to allow
6369
// valid positive return values
6470
enum lfs_error {
65-
LFS_ERR_OK = 0, // No error
66-
LFS_ERR_IO = -5, // Error during device operation
67-
LFS_ERR_CORRUPT = -52, // Corrupted
68-
LFS_ERR_NOENT = -2, // No directory entry
69-
LFS_ERR_EXIST = -17, // Entry already exists
70-
LFS_ERR_NOTDIR = -20, // Entry is not a dir
71-
LFS_ERR_ISDIR = -21, // Entry is a dir
72-
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
73-
LFS_ERR_BADF = -9, // Bad file number
74-
LFS_ERR_INVAL = -22, // Invalid parameter
75-
LFS_ERR_NOSPC = -28, // No space left on device
76-
LFS_ERR_NOMEM = -12, // No more memory available
71+
LFS_ERR_OK = 0, // No error
72+
LFS_ERR_IO = -5, // Error during device operation
73+
LFS_ERR_CORRUPT = -52, // Corrupted
74+
LFS_ERR_NOENT = -2, // No directory entry
75+
LFS_ERR_EXIST = -17, // Entry already exists
76+
LFS_ERR_NOTDIR = -20, // Entry is not a dir
77+
LFS_ERR_ISDIR = -21, // Entry is a dir
78+
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
79+
LFS_ERR_BADF = -9, // Bad file number
80+
LFS_ERR_INVAL = -22, // Invalid parameter
81+
LFS_ERR_NOSPC = -28, // No space left on device
82+
LFS_ERR_NOMEM = -12, // No more memory available
83+
LFS_ERR_NAMETOOLONG = -36, // File name too long
7784
};
7885

7986
// File types
@@ -102,10 +109,10 @@ enum lfs_open_flags {
102109
LFS_O_APPEND = 0x0800, // Move to end of file on every write
103110

104111
// internally used flags
105-
LFS_F_DIRTY = 0x10000, // File does not match storage
106-
LFS_F_WRITING = 0x20000, // File has been written since last flush
107-
LFS_F_READING = 0x40000, // File has been read since last flush
108-
LFS_F_ERRED = 0x80000, // An error occured during write
112+
LFS_F_DIRTY = 0x010000, // File does not match storage
113+
LFS_F_WRITING = 0x020000, // File has been written since last flush
114+
LFS_F_READING = 0x040000, // File has been read since last flush
115+
LFS_F_ERRED = 0x080000, // An error occured during write
109116
LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
110117
};
111118

@@ -183,6 +190,13 @@ struct lfs_config {
183190
// Optional, statically allocated buffer for files. Must be program sized.
184191
// If enabled, only one file may be opened at a time.
185192
void *file_buffer;
193+
194+
// Optional,
195+
lfs_size_t inline_size;
196+
// Optional,
197+
lfs_size_t attrs_size;
198+
// Optional,
199+
lfs_size_t name_size;
186200
};
187201

188202

@@ -258,9 +272,14 @@ typedef struct lfs_dir {
258272
typedef struct lfs_superblock {
259273
struct lfs_disk_superblock {
260274
lfs_block_t root[2];
261-
uint32_t block_size;
262-
uint32_t block_count;
275+
276+
lfs_size_t block_size;
277+
lfs_size_t block_count;
263278
uint32_t version;
279+
280+
lfs_size_t inline_size;
281+
lfs_size_t attrs_size;
282+
lfs_size_t name_size;
264283
} d;
265284
} lfs_superblock_t;
266285

@@ -285,6 +304,10 @@ typedef struct lfs {
285304

286305
lfs_free_t free;
287306
bool deorphaned;
307+
308+
lfs_size_t inline_size;
309+
lfs_size_t attrs_size;
310+
lfs_size_t name_size;
288311
} lfs_t;
289312

290313

0 commit comments

Comments
 (0)