Skip to content

Commit c67a41a

Browse files
committed
Added support for deleting attributes
littlefs has a mechanism for deleting file entries, but it doesn't have a mechanism for deleting individual tags. This _is_ sufficient for a filesystem, but limits our flexibility. Deleting attributes would be useful in the custom attribute API and for future improvements (hint the child pointers in B-trees). However, deleteing attributes is tricky. We can't just omit the attribute, since we can only add new tags. Additionally, we need a way to track what attributes have been deleted during compaction, which currently relies on writing out attributes to disk. The solution here is pretty nifty. First we have to come up with a way to represent a "deleted" attribute. Rather than adding an additional bit to the already squished tag structure, we use a -1 length field, specifically 0xfff. Now we can commit a delete attribute, and this deleted tag acts as a place holder during compacts. However our delete tag will never leave our metadata log. We need some way to discard our delete tag if we know it's the only representation of that tag on the metadata log. Ah! We know it's the only tag if it's in the first commit on the metadata log. So we add an additional bit to the CRC entry to indicate if we're on the first commit, and use that to decide if we need to keep delete tags around. Now we have working tag deletion. Interestingly enough, tag deletion is actually indirectly more efficient than entry deletion, since compacting entries requires multiple passes, whereas tag deletion gets cleaned up lazily. However we can't adopt the same strategy in entry deletion because of the compact ordering of entries. Tag deletion works because tag types are unique and static. Managing entry deletion in this manner would require static id allocation, which would cause problems when creating files, running out of space, and disallow arbitrary insertions of files.
1 parent 6046d85 commit c67a41a

File tree

2 files changed

+43
-27
lines changed

2 files changed

+43
-27
lines changed

lfs.c

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ static inline bool lfs_tag_isuser(uint32_t tag) {
380380
return (tag & 0x40000000);
381381
}
382382

383+
static inline bool lfs_tag_isdelete(uint32_t tag) {
384+
return (tag & 0x00000fff) == 0xfff;
385+
}
386+
383387
static inline uint16_t lfs_tag_type(uint32_t tag) {
384388
return (tag & 0x7fc00000) >> 22;
385389
}
@@ -396,6 +400,10 @@ static inline lfs_size_t lfs_tag_size(uint32_t tag) {
396400
return tag & 0x00000fff;
397401
}
398402

403+
static inline lfs_size_t lfs_tag_dsize(uint32_t tag) {
404+
return sizeof(tag) + lfs_tag_size(tag) + lfs_tag_isdelete(tag);
405+
}
406+
399407
// operations on set of globals
400408
static inline void lfs_global_xor(lfs_global_t *a, const lfs_global_t *b) {
401409
for (int i = 0; i < sizeof(lfs_global_t)/4; i++) {
@@ -469,8 +477,8 @@ static int32_t lfs_commit_get(lfs_t *lfs,
469477
gettag += getdiff;
470478

471479
// iterate over dir block backwards (for faster lookups)
472-
while (off >= 2*sizeof(uint32_t)+lfs_tag_size(tag)) {
473-
off -= sizeof(tag)+lfs_tag_size(tag);
480+
while (off >= sizeof(uint32_t) + lfs_tag_dsize(tag)) {
481+
off -= lfs_tag_dsize(tag);
474482

475483
if (lfs_tag_subtype(tag) == LFS_TYPE_CRC && stopatcommit) {
476484
break;
@@ -481,7 +489,8 @@ static int32_t lfs_commit_get(lfs_t *lfs,
481489
}
482490
}
483491

484-
if ((tag & getmask) == ((gettag - getdiff) & getmask)) {
492+
if (!lfs_tag_isdelete(tag) &&
493+
(tag & getmask) == ((gettag - getdiff) & getmask)) {
485494
if (buffer) {
486495
lfs_size_t diff = lfs_min(
487496
lfs_tag_size(gettag), lfs_tag_size(tag));
@@ -563,8 +572,8 @@ static int lfs_commit_attr(lfs_t *lfs, struct lfs_commit *commit,
563572
}
564573

565574
// check if we fit
566-
lfs_size_t size = lfs_tag_size(tag);
567-
if (commit->off + sizeof(tag)+size > commit->end) {
575+
lfs_size_t dsize = lfs_tag_dsize(tag);
576+
if (commit->off + dsize > commit->end) {
568577
return LFS_ERR_NOSPC;
569578
}
570579

@@ -577,18 +586,18 @@ static int lfs_commit_attr(lfs_t *lfs, struct lfs_commit *commit,
577586

578587
if (!(tag & 0x80000000)) {
579588
// from memory
580-
err = lfs_commit_prog(lfs, commit, buffer, size);
589+
err = lfs_commit_prog(lfs, commit, buffer, dsize-sizeof(tag));
581590
if (err) {
582591
return err;
583592
}
584593
} else {
585594
// from disk
586595
const struct lfs_diskoff *disk = buffer;
587-
for (lfs_off_t i = 0; i < size; i++) {
596+
for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) {
588597
// rely on caching to make this efficient
589598
uint8_t dat;
590599
err = lfs_bd_read(lfs,
591-
&lfs->pcache, &lfs->rcache, size-i,
600+
&lfs->pcache, &lfs->rcache, dsize-sizeof(tag)-i,
592601
disk->block, disk->off+i, &dat, 1);
593602
if (err) {
594603
return err;
@@ -626,7 +635,8 @@ static int lfs_commit_move(lfs_t *lfs, struct lfs_commit *commit, int pass,
626635
// iterate through list and commits, only committing unique entries
627636
lfs_off_t off = dir->off;
628637
uint32_t ntag = dir->etag;
629-
while (attrs || off >= 2*sizeof(uint32_t)+lfs_tag_size(ntag)) {
638+
bool end = false;
639+
while (attrs || off >= sizeof(uint32_t) + lfs_tag_dsize(ntag)) {
630640
struct lfs_diskoff disk;
631641
uint32_t tag;
632642
const void *buffer;
@@ -635,7 +645,7 @@ static int lfs_commit_move(lfs_t *lfs, struct lfs_commit *commit, int pass,
635645
buffer = attrs->buffer;
636646
attrs = attrs->next;
637647
} else {
638-
off -= sizeof(ntag)+lfs_tag_size(ntag);
648+
off -= lfs_tag_dsize(ntag);
639649

640650
tag = ntag;
641651
buffer = &disk;
@@ -653,13 +663,16 @@ static int lfs_commit_move(lfs_t *lfs, struct lfs_commit *commit, int pass,
653663
tag |= 0x80000000;
654664
}
655665

656-
if (lfs_tag_subtype(tag) == LFS_TYPE_DELETE &&
666+
if (lfs_tag_subtype(tag) == LFS_TYPE_CRC) {
667+
end = 2 & lfs_tag_type(tag);
668+
} else if (lfs_tag_subtype(tag) == LFS_TYPE_DELETE &&
657669
lfs_tag_id(tag) <= lfs_tag_id(fromtag - fromdiff)) {
658670
// something was deleted, we need to move around it
659671
fromdiff -= LFS_MKTAG(0, 1, 0);
660672
}
661673

662-
if ((tag & frommask) == ((fromtag - fromdiff) & frommask)) {
674+
if ((tag & frommask) == ((fromtag - fromdiff) & frommask) &&
675+
!(lfs_tag_isdelete(tag) && end)) {
663676
bool duplicate;
664677
if (pass == 0) {
665678
duplicate = (lfs_tag_subtype(tag) != LFS_TYPE_NAME);
@@ -714,7 +727,8 @@ static int lfs_commit_globals(lfs_t *lfs, struct lfs_commit *commit,
714727
return err;
715728
}
716729

717-
static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) {
730+
static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit,
731+
bool compacting) {
718732
// align to program units
719733
lfs_off_t off = lfs_alignup(commit->off + 2*sizeof(uint32_t),
720734
lfs->cfg->prog_size);
@@ -730,7 +744,7 @@ static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) {
730744

731745
// build crc tag
732746
bool reset = ~lfs_fromle32(tag) >> 31;
733-
tag = LFS_MKTAG(LFS_TYPE_CRC + reset,
747+
tag = LFS_MKTAG(LFS_TYPE_CRC + (compacting << 1) + reset,
734748
0x3ff, off - (commit->off+sizeof(uint32_t)));
735749

736750
// write out crc
@@ -889,7 +903,7 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
889903
}
890904

891905
// check we're in valid range
892-
if (off + sizeof(tag)+lfs_tag_size(tag) > lfs->cfg->block_size) {
906+
if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
893907
dir->erased = false;
894908
break;
895909
}
@@ -919,7 +933,7 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
919933

920934
// update with what's found so far
921935
foundtag = tempfoundtag;
922-
dir->off = off + sizeof(tag)+lfs_tag_size(tag);
936+
dir->off = off + lfs_tag_dsize(tag);
923937
dir->etag = tag;
924938
dir->count = tempcount;
925939
dir->tail[0] = temptail[0];
@@ -931,11 +945,11 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
931945
crc = 0xffffffff;
932946
} else {
933947
// crc the entry first, leaving it in the cache
934-
for (lfs_off_t j = 0; j < lfs_tag_size(tag); j++) {
948+
for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) {
935949
uint8_t dat;
936950
err = lfs_bd_read(lfs,
937951
NULL, &lfs->rcache, lfs->cfg->block_size,
938-
dir->pair[0], off+sizeof(tag)+j, &dat, 1);
952+
dir->pair[0], off+j, &dat, 1);
939953
if (err) {
940954
if (err == LFS_ERR_CORRUPT) {
941955
dir->erased = false;
@@ -994,7 +1008,9 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
9941008

9951009
if ((tag & findmask) == (findtag & findmask)) {
9961010
// found a match?
997-
if (lfs_tag_type(findtag) == LFS_TYPE_DIRSTRUCT) {
1011+
if (lfs_tag_isdelete(findtag)) {
1012+
tempfoundtag = LFS_ERR_NOENT;
1013+
} else if (lfs_tag_type(findtag) == LFS_TYPE_DIRSTRUCT) {
9981014
lfs_block_t child[2];
9991015
err = lfs_bd_read(lfs,
10001016
&lfs->pcache, &lfs->rcache,
@@ -1037,7 +1053,7 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
10371053
}
10381054

10391055
ptag = tag;
1040-
off += sizeof(tag)+lfs_tag_size(tag);
1056+
off += lfs_tag_dsize(tag);
10411057
}
10421058

10431059
// consider what we have good enough
@@ -1259,7 +1275,7 @@ static int lfs_dir_compact(lfs_t *lfs,
12591275
}
12601276
}
12611277

1262-
err = lfs_commit_crc(lfs, &commit);
1278+
err = lfs_commit_crc(lfs, &commit, true);
12631279
if (err) {
12641280
if (err == LFS_ERR_CORRUPT) {
12651281
goto relocate;
@@ -1428,7 +1444,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
14281444
.ack = 0,
14291445
};
14301446

1431-
// iterate over commits backwards, this lets us "append" commits cheaply
1447+
// iterate over commits backwards, this lets us "append" commits
14321448
for (int i = 0; i < attrcount; i++) {
14331449
const lfs_mattr_t *a = attrs;
14341450
for (int j = 0; j < attrcount-i-1; j++) {
@@ -1454,7 +1470,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
14541470
return err;
14551471
}
14561472

1457-
err = lfs_commit_crc(lfs, &commit);
1473+
err = lfs_commit_crc(lfs, &commit, false);
14581474
if (err) {
14591475
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
14601476
goto compact;
@@ -2926,8 +2942,8 @@ int lfs_setattr(lfs_t *lfs, const char *path,
29262942
}
29272943

29282944
return lfs_dir_commit(lfs, &cwd,
2929-
LFS_MKATTR(0x100 | type, id, buffer, size,
2930-
NULL));
2945+
LFS_MKATTR(0x100 | type, id, buffer, size,
2946+
NULL));
29312947
}
29322948

29332949

lfs.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ typedef uint32_t lfs_block_t;
4949
// to <= 0xfff. Stored in superblock and must be respected by other
5050
// littlefs drivers.
5151
#ifndef LFS_ATTR_MAX
52-
#define LFS_ATTR_MAX 0xfff
52+
#define LFS_ATTR_MAX 0xffe
5353
#endif
5454

5555
// Maximum name size in bytes, may be redefined to reduce the size of the
@@ -64,7 +64,7 @@ typedef uint32_t lfs_block_t;
6464
// block. Limited to <= LFS_ATTR_MAX and <= cache_size. Stored in superblock
6565
// and must be respected by other littlefs drivers.
6666
#ifndef LFS_INLINE_MAX
67-
#define LFS_INLINE_MAX 0xfff
67+
#define LFS_INLINE_MAX 0xffe
6868
#endif
6969

7070
// Possible error codes, these are negative to allow

0 commit comments

Comments
 (0)