Skip to content

Commit 6c754c8

Browse files
committed
Added support for atomically committing custom attributes
Although it's simple and probably what most users expect, the previous custom attributes API suffered from one problem: the inability to update attributes atomically. If we consider our timestamp use case, updating a file would require: 1. Update the file 2. Update the timestamp If a power loss occurs during this sequence of updates, we could end up with a file with an incorrect timestamp. Is this a big deal? Probably not, but it could be a surprise only found after a power-loss. And littlefs was developed with the _specifically_ to avoid suprises during power-loss. The littlefs is perfectly capable of bundling multiple attribute updates in a single directory commit. That's kind of what it was designed to do. So all we need is a new committer opcode for list of attributes, and then poking that list of attributes through the API. We could provide the single-attribute functions, but don't, because the fewer functions makes for a smaller codebase, and these are already the more advanced functions so we can expect more from users. This also changes semantics about what happens when we don't find an attribute, since erroring would throw away all of the other attributes we're processing. To atomically commit both custom attributes and file updates, we need a new API, lfs_file_setattr. Unfortunately the semantics are a bit more confusing than lfs_setattr, since the attributes aren't written out immediately.
1 parent 6ffc8d3 commit 6c754c8

File tree

2 files changed

+218
-118
lines changed

2 files changed

+218
-118
lines changed

lfs.c

Lines changed: 143 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -515,13 +515,20 @@ struct lfs_region {
515515
LFS_FROM_DROP,
516516
LFS_FROM_MEM,
517517
LFS_FROM_REGION,
518+
LFS_FROM_ATTRS,
518519
} type;
519520

520521
lfs_off_t off;
521522
const void *buffer;
522523
lfs_ssize_t size;
523524
};
524525

526+
struct lfs_attrs_region {
527+
const struct lfs_attr *attrs;
528+
int count;
529+
lfs_size_t len;
530+
};
531+
525532
struct lfs_region_region {
526533
lfs_block_t block;
527534
lfs_off_t off;
@@ -568,6 +575,73 @@ static int lfs_commit_region(lfs_t *lfs,
568575
newoff += regions[i].size;
569576
break;
570577
}
578+
case LFS_FROM_ATTRS: {
579+
const struct lfs_attrs_region *attrs = regions[i].buffer;
580+
581+
// order doesn't matter, so we write new attrs first. this
582+
// is still O(n^2) but only O(n) disk access
583+
for (int j = 0; j < attrs->count; j++) {
584+
if (attrs->attrs[j].size == 0) {
585+
continue;
586+
}
587+
588+
lfs_entry_attr_t attr = {
589+
.d.type = attrs->attrs[j].type,
590+
.d.len = attrs->attrs[j].size,
591+
};
592+
593+
lfs_crc(crc, &attr.d, sizeof(attr.d));
594+
int err = lfs_bd_prog(lfs, newblock, newoff,
595+
&attr.d, sizeof(attr.d));
596+
if (err) {
597+
return err;
598+
}
599+
600+
lfs_crc(crc,
601+
attrs->attrs[j].buffer, attrs->attrs[j].size);
602+
err = lfs_bd_prog(lfs, newblock, newoff+sizeof(attr.d),
603+
attrs->attrs[j].buffer, attrs->attrs[j].size);
604+
if (err) {
605+
return err;
606+
}
607+
608+
newoff += sizeof(attr.d) + attrs->attrs[j].size;
609+
}
610+
611+
// copy over attributes without updates
612+
lfs_entry_attr_t attr;
613+
for (lfs_off_t k = 0; k < attrs->len; k += 2+attr.d.len) {
614+
int err = lfs_bd_read(lfs, oldblock, oldoff,
615+
&attr.d, sizeof(attr.d));
616+
if (err) {
617+
return err;
618+
}
619+
620+
bool updating = false;
621+
for (int j = 0; j < attrs->count; j++) {
622+
if (attr.d.type == attrs->attrs[j].type) {
623+
updating = true;
624+
}
625+
}
626+
627+
if (!updating) {
628+
err = lfs_commit_region(lfs,
629+
oldblock, oldoff,
630+
newblock, newoff,
631+
0, NULL, 0,
632+
attr.d.len, crc);
633+
if (err) {
634+
return err;
635+
}
636+
637+
newoff += 2+attr.d.len;
638+
}
639+
640+
oldoff += 2+attr.d.len;
641+
}
642+
643+
break;
644+
}
571645
}
572646

573647
i += 1;
@@ -590,6 +664,8 @@ static int lfs_commit_region(lfs_t *lfs,
590664
}
591665
}
592666

667+
// sanity check our commit math
668+
LFS_ASSERT(newoff == end);
593669
return 0;
594670
}
595671

@@ -1044,118 +1120,102 @@ static int lfs_dir_getinfo(lfs_t *lfs,
10441120
return 0;
10451121
}
10461122

1047-
static int lfs_dir_getattr(lfs_t *lfs,
1123+
static int lfs_dir_getattrs(lfs_t *lfs,
10481124
lfs_dir_t *dir, const lfs_entry_t *entry,
1049-
uint8_t type, void *buffer, lfs_size_t size) {
1125+
const struct lfs_attr *attrs, int count) {
1126+
// set to zero in case we can't find the attributes or size mismatch
1127+
for (int j = 0; j < count; j++) {
1128+
memset(attrs[j].buffer, 0, attrs[j].size);
1129+
}
1130+
10501131
// search for attribute in attribute region
1051-
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
1052-
lfs_off_t i = 0;
1053-
while (i < lfs_entry_alen(entry)) {
1054-
lfs_attr_t attr;
1132+
lfs_off_t off = 4+lfs_entry_elen(entry);
1133+
lfs_entry_attr_t attr;
1134+
for (lfs_off_t i = 0; i < lfs_entry_alen(entry); i += 2+attr.d.len) {
10551135
int err = lfs_dir_get(lfs, dir,
10561136
entry->off+off+i, &attr.d, sizeof(attr.d));
10571137
if (err) {
10581138
return err;
10591139
}
10601140

1061-
if (attr.d.type != type) {
1062-
i += attr.d.len;
1063-
continue;
1064-
}
1065-
1066-
if (attr.d.len > size) {
1067-
return LFS_ERR_RANGE;
1068-
}
1141+
for (int j = 0; j < count; j++) {
1142+
if (attr.d.type == attrs[j].type) {
1143+
if (attr.d.len > attrs[j].size) {
1144+
return LFS_ERR_RANGE;
1145+
}
10691146

1070-
err = lfs_dir_get(lfs, dir,
1071-
entry->off+off+i+sizeof(attr.d), buffer, attr.d.len);
1072-
if (err) {
1073-
return err;
1147+
err = lfs_dir_get(lfs, dir,
1148+
entry->off+off+i+sizeof(attr.d),
1149+
attrs[j].buffer, attr.d.len);
1150+
if (err) {
1151+
return err;
1152+
}
1153+
}
10741154
}
1075-
1076-
return attr.d.len;
10771155
}
10781156

1079-
return LFS_ERR_NODATA;
1157+
return 0;
10801158
}
10811159

1082-
static int lfs_dir_setattr(lfs_t *lfs,
1160+
static lfs_ssize_t lfs_dir_checkattrs(lfs_t *lfs,
10831161
lfs_dir_t *dir, lfs_entry_t *entry,
1084-
uint8_t type, const void *buffer, lfs_size_t size) {
1085-
// search for attribute in attribute region
1086-
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
1087-
lfs_off_t i = 0;
1088-
lfs_size_t oldlen = 0;
1089-
while (i < lfs_entry_alen(entry)) {
1090-
lfs_attr_t attr;
1162+
const struct lfs_attr *attrs, int count) {
1163+
// check that attributes fit
1164+
lfs_size_t nsize = 0;
1165+
for (int j = 0; j < count; j++) {
1166+
nsize += 2+attrs[j].size;
1167+
}
1168+
1169+
lfs_off_t off = 4+lfs_entry_elen(entry);
1170+
lfs_entry_attr_t attr;
1171+
for (lfs_off_t i = 0; i < lfs_entry_alen(entry); i += 2+attr.d.len) {
10911172
int err = lfs_dir_get(lfs, dir,
10921173
entry->off+off+i, &attr.d, sizeof(attr.d));
10931174
if (err) {
10941175
return err;
10951176
}
10961177

1097-
if (attr.d.type != type) {
1098-
i += attr.d.len;
1099-
continue;
1178+
bool updated = false;
1179+
for (int j = 0; j < count; j++) {
1180+
if (attr.d.type == attrs[j].type) {
1181+
updated = true;
1182+
}
11001183
}
11011184

1102-
oldlen = attr.d.len;
1103-
break;
1185+
if (!updated) {
1186+
nsize += 2+attr.d.len;
1187+
}
11041188
}
11051189

1106-
// make sure the attribute fits
1107-
if (lfs_entry_elen(entry) - oldlen + size > lfs->attrs_size ||
1108-
(0x7fffffff & dir->d.size) - oldlen + size > lfs->cfg->block_size) {
1190+
if (nsize > lfs->attrs_size || (
1191+
(0x7fffffff & dir->d.size) + lfs_entry_size(entry) -
1192+
lfs_entry_alen(entry) + nsize > lfs->cfg->block_size)) {
11091193
return LFS_ERR_NOSPC;
11101194
}
11111195

1112-
lfs_attr_t attr;
1113-
attr.d.type = type;
1114-
attr.d.len = size;
1115-
int err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){
1116-
{LFS_FROM_MEM, off+i, &attr.d, sizeof(attr.d)},
1117-
{LFS_FROM_MEM, off+i, buffer, size},
1118-
{LFS_FROM_DROP, off+i, NULL, -oldlen}}, 3);
1119-
if (err) {
1120-
return err;
1121-
}
1122-
1123-
return 0;
1196+
return nsize;
11241197
}
11251198

1126-
static int lfs_dir_removeattr(lfs_t *lfs,
1127-
lfs_dir_t *dir, lfs_entry_t *entry, uint8_t type) {
1128-
// search for attribute in attribute region
1129-
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
1130-
lfs_off_t i = 0;
1131-
while (i < lfs_entry_alen(entry)) {
1132-
lfs_attr_t attr;
1133-
int err = lfs_dir_get(lfs, dir,
1134-
entry->off+off+i, &attr.d, sizeof(attr.d));
1135-
if (err) {
1136-
return err;
1137-
}
1138-
1139-
if (attr.d.type != type) {
1140-
i += attr.d.len;
1141-
continue;
1142-
}
1143-
1144-
err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){
1145-
{LFS_FROM_DROP, off+i,
1146-
NULL, -(sizeof(attr.d)+attr.d.len)}}, 1);
1147-
if (err) {
1148-
return err;
1149-
}
1150-
1151-
return 0;
1199+
static int lfs_dir_setattrs(lfs_t *lfs,
1200+
lfs_dir_t *dir, lfs_entry_t *entry,
1201+
const struct lfs_attr *attrs, int count) {
1202+
// make sure attributes fit
1203+
lfs_ssize_t nsize = lfs_dir_checkattrs(lfs, dir, entry, attrs, count);
1204+
if (nsize < 0) {
1205+
return nsize;
11521206
}
11531207

1154-
return LFS_ERR_NODATA;
1208+
// commit to entry, majority of work is in LFS_FROM_ATTRS
1209+
lfs_size_t oldlen = lfs_entry_alen(entry);
1210+
entry->d.alen = (0xc0 & entry->d.alen) | nsize;
1211+
return lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){
1212+
{LFS_FROM_MEM, 0, &entry->d, 4},
1213+
{LFS_FROM_DROP, 0, NULL, -4},
1214+
{LFS_FROM_ATTRS, 4+lfs_entry_elen(entry),
1215+
&(struct lfs_attrs_region){attrs, count, oldlen}, nsize}}, 3);
11551216
}
11561217

11571218

1158-
11591219
/// Top level directory operations ///
11601220
int lfs_mkdir(lfs_t *lfs, const char *path) {
11611221
// deorphan if we haven't yet, needed at most once after poweron
@@ -2372,25 +2432,8 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
23722432

23732433

23742434
/// Attribute operations ///
2375-
int lfs_getattr(lfs_t *lfs, const char *path,
2376-
uint8_t type, void *buffer, lfs_size_t size) {
2377-
lfs_dir_t cwd;
2378-
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
2379-
if (err) {
2380-
return err;
2381-
}
2382-
2383-
lfs_entry_t entry;
2384-
err = lfs_dir_find(lfs, &cwd, &entry, &path);
2385-
if (err) {
2386-
return err;
2387-
}
2388-
2389-
return lfs_dir_getattr(lfs, &cwd, &entry, type, buffer, size);
2390-
}
2391-
2392-
int lfs_setattr(lfs_t *lfs, const char *path,
2393-
uint8_t type, const void *buffer, lfs_size_t size) {
2435+
int lfs_getattrs(lfs_t *lfs, const char *path,
2436+
const struct lfs_attr *attrs, int count) {
23942437
lfs_dir_t cwd;
23952438
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
23962439
if (err) {
@@ -2403,10 +2446,11 @@ int lfs_setattr(lfs_t *lfs, const char *path,
24032446
return err;
24042447
}
24052448

2406-
return lfs_dir_setattr(lfs, &cwd, &entry, type, buffer, size);
2449+
return lfs_dir_getattrs(lfs, &cwd, &entry, attrs, count);
24072450
}
24082451

2409-
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
2452+
int lfs_setattrs(lfs_t *lfs, const char *path,
2453+
const struct lfs_attr *attrs, int count) {
24102454
lfs_dir_t cwd;
24112455
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
24122456
if (err) {
@@ -2419,7 +2463,7 @@ int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
24192463
return err;
24202464
}
24212465

2422-
return lfs_dir_removeattr(lfs, &cwd, &entry, type);
2466+
return lfs_dir_setattrs(lfs, &cwd, &entry, attrs, count);
24232467
}
24242468

24252469

0 commit comments

Comments
 (0)