Added device-side syncing of open files

Compared to other filesystems, littlefs's handling of open files may
come across as a bit odd, especially when you open the same file with
multiple file handles.

This commit addresses this by forcing all open readable file handles to
be consistent if any open writable file handle is synced though either
lfs_file_sync or lfs_file_close. This means open readable file handles
always mirror the state of the filesystem on disk.

To do this we again rely on the internal linked-list of open file
handles, marking files as clean, copying over the written file
cache, and synchronizing any custom attributes attached to the file
handles.

Note, this still needs cleanup and tests

---

Why was the previous behavior?

One of the nifty mechanism in littlefs is the ability to have multiple
device-side copies of a file that share copy-on-write blocks of data.
This is very useful for staging any amount of changes, which may live either
in RAM caches or allocated-but-not-committed blocks on disk, that can be
atomically updated in a single commit. After this change, littlefs still uses
this update mechanism to track open files, meaning if you lose power, the
entire file will revert to what was written at the last lfs_file_sync.

Because this mechanism already exists, it was easy enough to rely on
this to handle multiple open file handles gracefully. Each file handle
gets its own copy-on-write copy of the contents at time of open, and and
writes are managed independently of other open files.

This behavior was idiosyncratic, but consistent, though after some time
enough users raised feedback that this behavior needed to be reassessed.

Now multiple open files should conform to what's found in other
filesystem APIs, at a small code cost to manage syncing open files.
This commit is contained in:
Christopher Haster
2020-12-16 20:51:35 -06:00
parent 1a59954ec6
commit d97d66adf5

80
lfs.c
View File

@@ -24,6 +24,14 @@ static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
pcache->block = LFS_BLOCK_NULL; pcache->block = LFS_BLOCK_NULL;
} }
static inline void lfs_cache_copy(lfs_t *lfs,
lfs_cache_t *dcache, const lfs_cache_t *scache) {
memcpy(dcache->buffer, scache->buffer, lfs->cfg->cache_size);
dcache->block = scache->block;
dcache->off = scache->off;
dcache->size = scache->size;
}
static int lfs_bd_read(lfs_t *lfs, static int lfs_bd_read(lfs_t *lfs,
const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
lfs_block_t block, lfs_off_t off, lfs_block_t block, lfs_off_t off,
@@ -1821,7 +1829,7 @@ relocate:
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
const struct lfs_mattr *attrs, int attrcount) { const struct lfs_mattr *attrs, int attrcount) {
// check for any inline files that aren't RAM backed and // check for any open inline files that aren't RAM backed and
// forcefully evict them, needed for filesystem consistency // forcefully evict them, needed for filesystem consistency
for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 &&
@@ -2498,6 +2506,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
} }
// get next slot and create entry to remember name // get next slot and create entry to remember name
// TODO commit with attrs?
err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL},
{LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path},
@@ -2533,7 +2542,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
} }
// fetch attrs // fetch attrs
for (unsigned i = 0; i < file->cfg->attr_count; i++) { for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) {
// if opened for read / read-write operations // if opened for read / read-write operations
if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) {
lfs_stag_t res = lfs_dir_get(lfs, &file->m, lfs_stag_t res = lfs_dir_get(lfs, &file->m,
@@ -2555,6 +2564,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
goto cleanup; goto cleanup;
} }
// TODO remove this?
file->flags |= LFS_F_DIRTY; file->flags |= LFS_F_DIRTY;
} }
#endif #endif
@@ -2812,7 +2822,6 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
if ((file->flags & LFS_F_DIRTY) && if ((file->flags & LFS_F_DIRTY) &&
!lfs_pair_isnull(file->m.pair)) { !lfs_pair_isnull(file->m.pair)) {
// update dir entry // update dir entry
@@ -2845,6 +2854,40 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
// update readable handles referencing this file device-side
for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
if (f != file &&
f->type == LFS_TYPE_REG &&
lfs_pair_cmp(f->m.pair, file->m.pair) == 0 &&
f->id == file->id &&
// only readable handles because wronly files
// may reference attributes in ROM
(f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) {
// sync disk structure
f->ctz = file->ctz;
// copying the cache is required for inline files
lfs_cache_copy(lfs, &f->cache, &file->cache);
// sync attrs
for (lfs_size_t i = 0; i < f->cfg->attr_count; i++) {
for (lfs_size_t j = 0; j < file->cfg->attr_count; j++) {
if (f->cfg->attrs[i].type == file->cfg->attrs[i].type) {
lfs_size_t diff = lfs_min(
f->cfg->attrs[i].size,
file->cfg->attrs[i].size);
memcpy(f->cfg->attrs[i].buffer,
file->cfg->attrs[i].buffer,
diff);
memset((uint8_t*)f->cfg->attrs[i].buffer + diff,
0, f->cfg->attrs[i].size - diff);
}
}
}
file->flags &= ~(LFS_F_DIRTY | LFS_F_WRITING | LFS_F_READING);
}
}
file->flags &= ~LFS_F_DIRTY; file->flags &= ~LFS_F_DIRTY;
} }
@@ -3424,8 +3467,37 @@ static int lfs_commitattr(lfs_t *lfs, const char *path,
} }
} }
return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( int err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer}));
if (err) {
return err;
}
if (lfs_tag_type3(tag) == LFS_TYPE_REG && size != 0x3ff) {
// sync attrs with any files open for reading, this follows
// the behavior of lfs_file_sync with attributes
for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
if (f->type == LFS_TYPE_REG &&
lfs_pair_cmp(f->m.pair, cwd.pair) == 0 &&
f->id == id &&
// only readable handles because wronly files
// may reference attributes in ROM
(f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) {
// sync attrs
for (lfs_size_t i = 0; i < f->cfg->attr_count; i++) {
if (f->cfg->attrs[i].type == type) {
// TODO should this zero trailing bytes?
lfs_size_t diff = lfs_min(f->cfg->attrs[i].size, size);
memcpy(f->cfg->attrs[i].buffer, buffer, diff);
memset((uint8_t*)f->cfg->attrs[i].buffer + diff,
0, f->cfg->attrs[i].size - diff);
}
}
}
}
}
return 0;
} }
#endif #endif