Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
49698e4
Separated type/struct fields in dir entries
geky Mar 3, 2018
4c35c86
Added different sources for commits, now with disk->disk moves
geky Mar 4, 2018
73d29f0
Adopted a tiny LISP-like DSL for some extra flexibility
geky Mar 11, 2018
e3daee2
Changed dir append to mirror commit DSL
geky Mar 11, 2018
692f0c5
Naive implementation of resizable entries
geky Mar 11, 2018
ca3d6a5
Made implicity tag updates explicit
geky Mar 11, 2018
f30ab67
Traded enum-based DSL for full callback-based DSL
geky Mar 11, 2018
e4a0cd9
Take advantage of empty space early in dir search
geky Mar 12, 2018
362b0bb
Minor improvement to from-memory commits
geky Mar 13, 2018
03b262b
Separated out version of dir remove/append for non-entries
geky Mar 16, 2018
9273ac7
Added size field to entry structure
geky Mar 16, 2018
fb23044
Fixed big-endian support for entry structures
geky Mar 16, 2018
836e238
Shoehorned in hacky implementation of inline files
geky Mar 17, 2018
d8cadec
Better implementation of inline files, now with overflowing
geky Mar 18, 2018
701e4fa
Fixed a handful of bugs as result of testing
geky Mar 19, 2018
d0e0453
Changed how we write out superblock to use append
geky Mar 23, 2018
ad74825
Added internal lfs_dir_get to consolidate logic for reading dir entries
geky Mar 23, 2018
9555458
Added internal lfs_dir_set, an umbrella to dir append/update/remove o…
geky Mar 27, 2018
6362afa
Added disk-backed limits on the name/attrs/inline sizes
geky Apr 1, 2018
6774276
Expanded inline files up to a limit of 1023 bytes
geky Apr 3, 2018
65ea6b3
Bumped versions, cleaned up some TODOs and missing comments
geky Apr 3, 2018
6ffc8d3
Added simple custom attributes
geky Apr 6, 2018
6c754c8
Added support for atomically committing custom attributes
geky Apr 6, 2018
636c0ed
Modified commit regions to work better with custom attributes
geky Apr 8, 2018
93244a3
Added file-level and fs-level custom attribute APIs
geky Apr 8, 2018
746b909
Added lfs_fs_size for finding a count of used blocks
geky Apr 9, 2018
2a8277b
Added test coverage for filesystems with no inline files
geky Apr 10, 2018
ea4ded4
Fixed big-endian support again
geky Apr 10, 2018
61f454b
Added tests for resizable entries and custom attributes
geky Apr 11, 2018
1f8c509
Changed license to BSD-3-Clause
geky Jun 21, 2018
c23481c
Fixed script issue with bash expansion inside makefile parameter
geky Jul 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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.
  • Loading branch information
geky committed Oct 10, 2018
commit 6c754c802352d7c46b8b10ba4dd8088b2cea2d86
242 changes: 143 additions & 99 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,13 +515,20 @@ struct lfs_region {
LFS_FROM_DROP,
LFS_FROM_MEM,
LFS_FROM_REGION,
LFS_FROM_ATTRS,
} type;

lfs_off_t off;
const void *buffer;
lfs_ssize_t size;
};

struct lfs_attrs_region {
const struct lfs_attr *attrs;
int count;
lfs_size_t len;
};

struct lfs_region_region {
lfs_block_t block;
lfs_off_t off;
Expand Down Expand Up @@ -568,6 +575,73 @@ static int lfs_commit_region(lfs_t *lfs,
newoff += regions[i].size;
break;
}
case LFS_FROM_ATTRS: {
const struct lfs_attrs_region *attrs = regions[i].buffer;

// order doesn't matter, so we write new attrs first. this
// is still O(n^2) but only O(n) disk access
for (int j = 0; j < attrs->count; j++) {
if (attrs->attrs[j].size == 0) {
continue;
}

lfs_entry_attr_t attr = {
.d.type = attrs->attrs[j].type,
.d.len = attrs->attrs[j].size,
};

lfs_crc(crc, &attr.d, sizeof(attr.d));
int err = lfs_bd_prog(lfs, newblock, newoff,
&attr.d, sizeof(attr.d));
if (err) {
return err;
}

lfs_crc(crc,
attrs->attrs[j].buffer, attrs->attrs[j].size);
err = lfs_bd_prog(lfs, newblock, newoff+sizeof(attr.d),
attrs->attrs[j].buffer, attrs->attrs[j].size);
if (err) {
return err;
}

newoff += sizeof(attr.d) + attrs->attrs[j].size;
}

// copy over attributes without updates
lfs_entry_attr_t attr;
for (lfs_off_t k = 0; k < attrs->len; k += 2+attr.d.len) {
int err = lfs_bd_read(lfs, oldblock, oldoff,
&attr.d, sizeof(attr.d));
if (err) {
return err;
}

bool updating = false;
for (int j = 0; j < attrs->count; j++) {
if (attr.d.type == attrs->attrs[j].type) {
updating = true;
}
}

if (!updating) {
err = lfs_commit_region(lfs,
oldblock, oldoff,
newblock, newoff,
0, NULL, 0,
attr.d.len, crc);
if (err) {
return err;
}

newoff += 2+attr.d.len;
}

oldoff += 2+attr.d.len;
}

break;
}
}

i += 1;
Expand All @@ -590,6 +664,8 @@ static int lfs_commit_region(lfs_t *lfs,
}
}

// sanity check our commit math
LFS_ASSERT(newoff == end);
return 0;
}

Expand Down Expand Up @@ -1044,118 +1120,102 @@ static int lfs_dir_getinfo(lfs_t *lfs,
return 0;
}

static int lfs_dir_getattr(lfs_t *lfs,
static int lfs_dir_getattrs(lfs_t *lfs,
lfs_dir_t *dir, const lfs_entry_t *entry,
uint8_t type, void *buffer, lfs_size_t size) {
const struct lfs_attr *attrs, int count) {
// set to zero in case we can't find the attributes or size mismatch
for (int j = 0; j < count; j++) {
memset(attrs[j].buffer, 0, attrs[j].size);
}

// search for attribute in attribute region
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
lfs_off_t i = 0;
while (i < lfs_entry_alen(entry)) {
lfs_attr_t attr;
lfs_off_t off = 4+lfs_entry_elen(entry);
lfs_entry_attr_t attr;
for (lfs_off_t i = 0; i < lfs_entry_alen(entry); i += 2+attr.d.len) {
int err = lfs_dir_get(lfs, dir,
entry->off+off+i, &attr.d, sizeof(attr.d));
if (err) {
return err;
}

if (attr.d.type != type) {
i += attr.d.len;
continue;
}

if (attr.d.len > size) {
return LFS_ERR_RANGE;
}
for (int j = 0; j < count; j++) {
if (attr.d.type == attrs[j].type) {
if (attr.d.len > attrs[j].size) {
return LFS_ERR_RANGE;
}

err = lfs_dir_get(lfs, dir,
entry->off+off+i+sizeof(attr.d), buffer, attr.d.len);
if (err) {
return err;
err = lfs_dir_get(lfs, dir,
entry->off+off+i+sizeof(attr.d),
attrs[j].buffer, attr.d.len);
if (err) {
return err;
}
}
}

return attr.d.len;
}

return LFS_ERR_NODATA;
return 0;
}

static int lfs_dir_setattr(lfs_t *lfs,
static lfs_ssize_t lfs_dir_checkattrs(lfs_t *lfs,
lfs_dir_t *dir, lfs_entry_t *entry,
uint8_t type, const void *buffer, lfs_size_t size) {
// search for attribute in attribute region
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
lfs_off_t i = 0;
lfs_size_t oldlen = 0;
while (i < lfs_entry_alen(entry)) {
lfs_attr_t attr;
const struct lfs_attr *attrs, int count) {
// check that attributes fit
lfs_size_t nsize = 0;
for (int j = 0; j < count; j++) {
nsize += 2+attrs[j].size;
}

lfs_off_t off = 4+lfs_entry_elen(entry);
lfs_entry_attr_t attr;
for (lfs_off_t i = 0; i < lfs_entry_alen(entry); i += 2+attr.d.len) {
int err = lfs_dir_get(lfs, dir,
entry->off+off+i, &attr.d, sizeof(attr.d));
if (err) {
return err;
}

if (attr.d.type != type) {
i += attr.d.len;
continue;
bool updated = false;
for (int j = 0; j < count; j++) {
if (attr.d.type == attrs[j].type) {
updated = true;
}
}

oldlen = attr.d.len;
break;
if (!updated) {
nsize += 2+attr.d.len;
}
}

// make sure the attribute fits
if (lfs_entry_elen(entry) - oldlen + size > lfs->attrs_size ||
(0x7fffffff & dir->d.size) - oldlen + size > lfs->cfg->block_size) {
if (nsize > lfs->attrs_size || (
(0x7fffffff & dir->d.size) + lfs_entry_size(entry) -
lfs_entry_alen(entry) + nsize > lfs->cfg->block_size)) {
return LFS_ERR_NOSPC;
}

lfs_attr_t attr;
attr.d.type = type;
attr.d.len = size;
int err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){
{LFS_FROM_MEM, off+i, &attr.d, sizeof(attr.d)},
{LFS_FROM_MEM, off+i, buffer, size},
{LFS_FROM_DROP, off+i, NULL, -oldlen}}, 3);
if (err) {
return err;
}

return 0;
return nsize;
}

static int lfs_dir_removeattr(lfs_t *lfs,
lfs_dir_t *dir, lfs_entry_t *entry, uint8_t type) {
// search for attribute in attribute region
lfs_off_t off = sizeof(dir->d) + lfs_entry_elen(entry);
lfs_off_t i = 0;
while (i < lfs_entry_alen(entry)) {
lfs_attr_t attr;
int err = lfs_dir_get(lfs, dir,
entry->off+off+i, &attr.d, sizeof(attr.d));
if (err) {
return err;
}

if (attr.d.type != type) {
i += attr.d.len;
continue;
}

err = lfs_dir_set(lfs, dir, entry, (struct lfs_region[]){
{LFS_FROM_DROP, off+i,
NULL, -(sizeof(attr.d)+attr.d.len)}}, 1);
if (err) {
return err;
}

return 0;
static int lfs_dir_setattrs(lfs_t *lfs,
lfs_dir_t *dir, lfs_entry_t *entry,
const struct lfs_attr *attrs, int count) {
// make sure attributes fit
lfs_ssize_t nsize = lfs_dir_checkattrs(lfs, dir, entry, attrs, count);
if (nsize < 0) {
return nsize;
}

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



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


/// Attribute operations ///
int lfs_getattr(lfs_t *lfs, const char *path,
uint8_t type, void *buffer, lfs_size_t size) {
lfs_dir_t cwd;
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
if (err) {
return err;
}

lfs_entry_t entry;
err = lfs_dir_find(lfs, &cwd, &entry, &path);
if (err) {
return err;
}

return lfs_dir_getattr(lfs, &cwd, &entry, type, buffer, size);
}

int lfs_setattr(lfs_t *lfs, const char *path,
uint8_t type, const void *buffer, lfs_size_t size) {
int lfs_getattrs(lfs_t *lfs, const char *path,
const struct lfs_attr *attrs, int count) {
lfs_dir_t cwd;
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
if (err) {
Expand All @@ -2403,10 +2446,11 @@ int lfs_setattr(lfs_t *lfs, const char *path,
return err;
}

return lfs_dir_setattr(lfs, &cwd, &entry, type, buffer, size);
return lfs_dir_getattrs(lfs, &cwd, &entry, attrs, count);
}

int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
int lfs_setattrs(lfs_t *lfs, const char *path,
const struct lfs_attr *attrs, int count) {
lfs_dir_t cwd;
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
if (err) {
Expand All @@ -2419,7 +2463,7 @@ int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
return err;
}

return lfs_dir_removeattr(lfs, &cwd, &entry, type);
return lfs_dir_setattrs(lfs, &cwd, &entry, attrs, count);
}


Expand Down
Loading