Introduced the LFS_O_SNAPSHOT flag

LFS_O_SNAPSHOT brings back some of littlefs's idiosyncratic behavior
removed in the changes to open file syncing in a form that may be more
useful for users.

LFS_O_SNAPSHOT allows you to open a "snapshot" of a file. This is a cheap,
local copy of a file who's changes are not reflected on disk.

Internally, snapshot files use the same mechanism as pending writes. A
separate, copy-on-write CTZ skip-list is created, with read-only
references to the existing data blocks until a write occurs. The
difference is that snapshot files are not enrolled in the mlist, meaning
they won't get updates from open file syncs, and during close their
contents are simply discarded.

As an extra benefit, LFS_O_CREAT | LFS_O_SNAPSHOT is equivalent to
Linux's O_TMPFILE, making it easy to create temporary, unnamed files.

This may be useful for embedded development, where unnamed flash-backed
buffers may provide a slower, but larger, alternative to RAM-backed
buffers.
This commit is contained in:
Christopher Haster
2020-12-24 18:44:14 -06:00
parent deeaa17317
commit c7820e653e
4 changed files with 549 additions and 97 deletions

138
lfs.c
View File

@@ -821,8 +821,12 @@ static int lfs_dir_traverse(lfs_t *lfs,
return err; return err;
} }
} else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) {
for (unsigned i = 0; i < lfs_tag_size(tag); i++) {
const struct lfs_attr *a = buffer; const struct lfs_attr *a = buffer;
for (unsigned i = 0; i < lfs_tag_size(tag); i++) {
if (a[i].size > lfs->attr_max) {
return LFS_ERR_NOSPC;
}
int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type,
lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); lfs_tag_id(tag) + diff, a[i].size), a[i].buffer);
if (err) { if (err) {
@@ -1180,6 +1184,11 @@ static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir,
dir->tail[0] = lfs->root[0]; dir->tail[0] = lfs->root[0];
dir->tail[1] = lfs->root[1]; dir->tail[1] = lfs->root[1];
// NULL path == root
if (!name) {
return tag;
}
while (true) { while (true) {
nextname: nextname:
// skip slashes // skip slashes
@@ -2020,6 +2029,7 @@ compact:
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_rawmkdir(lfs_t *lfs, const char *path) { static int lfs_rawmkdir(lfs_t *lfs, const char *path) {
// deorphan if we haven't yet, needed at most once after poweron // deorphan if we haven't yet, needed at most once after poweron
LFS_ASSERT(path);
int err = lfs_fs_forceconsistency(lfs); int err = lfs_fs_forceconsistency(lfs);
if (err) { if (err) {
return err; return err;
@@ -2457,14 +2467,14 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
const struct lfs_file_config *cfg) { const struct lfs_file_config *cfg) {
#ifndef LFS_READONLY #ifndef LFS_READONLY
// deorphan if we haven't yet, needed at most once after poweron // deorphan if we haven't yet, needed at most once after poweron
if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { if (flags & LFS_O_WRONLY) {
int err = lfs_fs_forceconsistency(lfs); int err = lfs_fs_forceconsistency(lfs);
if (err) { if (err) {
return err; return err;
} }
} }
#else #else
LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); LFS_ASSERT(flags & LFS_O_RDONLY);
#endif #endif
// setup simple file details // setup simple file details
@@ -2475,7 +2485,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
file->off = 0; file->off = 0;
file->cache.buffer = NULL; file->cache.buffer = NULL;
// allocate entry for file if it doesn't exist // find path
lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id);
if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) {
err = tag; err = tag;
@@ -2483,20 +2493,23 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
} }
// get id, add to list of mdirs to catch update changes // get id, add to list of mdirs to catch update changes
if (!(flags & LFS_O_SNAPSHOT)) {
file->type = LFS_TYPE_REG; file->type = LFS_TYPE_REG;
lfs_mlist_append(lfs, (struct lfs_mlist *)file); lfs_mlist_append(lfs, (struct lfs_mlist *)file);
#ifdef LFS_READONLY
if (tag == LFS_ERR_NOENT) {
err = LFS_ERR_NOENT;
goto cleanup;
#else
if (tag == LFS_ERR_NOENT) {
if (!(flags & LFS_O_CREAT)) {
err = LFS_ERR_NOENT;
goto cleanup;
} }
#ifndef LFS_READONLY
if ((flags & LFS_O_CREAT) && (flags & LFS_O_SNAPSHOT) &&
(tag == LFS_ERR_NOENT || lfs_tag_type3(tag) != LFS_TYPE_REG)) {
// special case for "temporary" files
tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
} else if ((flags & LFS_O_EXCL) && tag != LFS_ERR_NOENT) {
err = LFS_ERR_EXIST;
goto cleanup;
} else if ((flags & LFS_O_CREAT) && tag == LFS_ERR_NOENT) {
// allocate entry for file if it doesn't exist
LFS_ASSERT(path);
// check that name fits // check that name fits
lfs_size_t nlen = strlen(path); lfs_size_t nlen = strlen(path);
if (nlen > lfs->name_max) { if (nlen > lfs->name_max) {
@@ -2512,26 +2525,29 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
{LFS_MKTAG(LFS_FROM_USERATTRS, file->id, {LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
file->cfg->attr_count), file->cfg->attrs})); file->cfg->attr_count), file->cfg->attrs}));
if (err) { if (err) {
err = LFS_ERR_NAMETOOLONG;
goto cleanup; goto cleanup;
} }
tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
} else if (flags & LFS_O_EXCL) { } else /**/
err = LFS_ERR_EXIST; #endif /**/
/*********/
/**/ if (tag == LFS_ERR_NOENT) {
err = LFS_ERR_NOENT;
goto cleanup; goto cleanup;
#endif
} else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) {
err = LFS_ERR_ISDIR; err = LFS_ERR_ISDIR;
goto cleanup; goto cleanup;
} else {
#ifndef LFS_READONLY #ifndef LFS_READONLY
} else if (flags & LFS_O_TRUNC) { if (flags & LFS_O_TRUNC) {
// truncate if requested // truncate if requested
// always mark dirty in case we have custom attributes // always mark dirty in case we have custom attributes
tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
file->flags |= LFS_F_DIRTY; file->flags |= LFS_F_DIRTY;
#endif } else /**/
} else { #endif /*********/
/**/ {
// try to load what's on disk, if it's inlined we'll fix it later // try to load what's on disk, if it's inlined we'll fix it later
tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz);
@@ -2542,10 +2558,9 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
lfs_ctz_fromle32(&file->ctz); lfs_ctz_fromle32(&file->ctz);
} }
// fetch attrs // fetch attrs if opened for read / read-write operations
if (flags & LFS_O_RDONLY) {
for (lfs_size_t 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 ((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,
LFS_MKTAG(0x7ff, 0x3ff, 0), LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type,
@@ -2556,17 +2571,8 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
goto cleanup; goto cleanup;
} }
} }
#ifndef LFS_READONLY
// if opened for write / read-write operations
if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) {
if (file->cfg->attrs[i].size > lfs->attr_max) {
err = LFS_ERR_NOSPC;
goto cleanup;
} }
} }
#endif
}
// allocate buffer if needed // allocate buffer if needed
if (file->cfg->buffer) { if (file->cfg->buffer) {
@@ -2610,7 +2616,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file,
cleanup: cleanup:
// clean up lingering resources // clean up lingering resources
#ifndef LFS_READONLY #ifndef LFS_READONLY
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
#endif #endif
lfs_file_rawclose(lfs, file); lfs_file_rawclose(lfs, file);
return err; return err;
@@ -2624,10 +2630,9 @@ static int lfs_file_rawopen(lfs_t *lfs, lfs_file_t *file,
} }
static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file) { static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file) {
#ifndef LFS_READONLY
int err = lfs_file_rawsync(lfs, file);
#else
int err = 0; int err = 0;
#ifndef LFS_READONLY
err = lfs_file_rawsync(lfs, file);
#endif #endif
// remove from list of mdirs // remove from list of mdirs
@@ -2809,17 +2814,22 @@ relocate:
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
if (file->flags & LFS_F_ERRED) { if (file->flags & LFS_F_ZOMBIE) {
// it's not safe to do anything if our file errored // it's not safe to do anything if our file errored
return 0; return 0;
} }
int err = lfs_file_flush(lfs, file); int err = lfs_file_flush(lfs, file);
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
if (file->flags & LFS_O_SNAPSHOT) {
// we do flush snapshot files, but we don't commit, so stop here
return 0;
}
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
@@ -2848,7 +2858,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
{LFS_MKTAG(LFS_FROM_USERATTRS, file->id, {LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
file->cfg->attr_count), file->cfg->attrs})); file->cfg->attr_count), file->cfg->attrs}));
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
@@ -2860,7 +2870,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
f->id == file->id && f->id == file->id &&
// only readable handles because wronly files // only readable handles because wronly files
// may reference attributes in ROM // may reference attributes in ROM
(f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { (f->flags & LFS_O_RDONLY)) {
// sync disk structure // sync disk structure
f->ctz = file->ctz; f->ctz = file->ctz;
// copying the cache is required for inline files // copying the cache is required for inline files
@@ -2892,7 +2902,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) {
static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size) { void *buffer, lfs_size_t size) {
LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); LFS_ASSERT(file->flags & LFS_O_RDONLY);
uint8_t *data = buffer; uint8_t *data = buffer;
lfs_size_t nsize = size; lfs_size_t nsize = size;
@@ -2966,7 +2976,7 @@ static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file,
#ifndef LFS_READONLY #ifndef LFS_READONLY
static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size) { const void *buffer, lfs_size_t size) {
LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); LFS_ASSERT(file->flags & LFS_O_WRONLY);
const uint8_t *data = buffer; const uint8_t *data = buffer;
lfs_size_t nsize = size; lfs_size_t nsize = size;
@@ -3008,7 +3018,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
// inline file doesn't fit anymore // inline file doesn't fit anymore
int err = lfs_file_outline(lfs, file); int err = lfs_file_outline(lfs, file);
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
} }
@@ -3024,7 +3034,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
file->ctz.head, file->ctz.size, file->ctz.head, file->ctz.size,
file->pos-1, &file->block, &file->off); file->pos-1, &file->block, &file->off);
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
@@ -3038,7 +3048,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
file->block, file->pos, file->block, file->pos,
&file->block, &file->off); &file->block, &file->off);
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
} else { } else {
@@ -3058,7 +3068,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
goto relocate; goto relocate;
} }
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
@@ -3066,7 +3076,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file,
relocate: relocate:
err = lfs_file_relocate(lfs, file); err = lfs_file_relocate(lfs, file);
if (err) { if (err) {
file->flags |= LFS_F_ERRED; file->flags |= LFS_F_ZOMBIE;
return err; return err;
} }
} }
@@ -3079,7 +3089,7 @@ relocate:
lfs_alloc_ack(lfs); lfs_alloc_ack(lfs);
} }
file->flags &= ~LFS_F_ERRED; file->flags &= ~LFS_F_ZOMBIE;
return size; return size;
} }
#endif #endif
@@ -3116,7 +3126,7 @@ static lfs_soff_t lfs_file_rawseek(lfs_t *lfs, lfs_file_t *file,
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); LFS_ASSERT(file->flags & LFS_O_WRONLY);
if (size > LFS_FILE_MAX) { if (size > LFS_FILE_MAX) {
return LFS_ERR_INVAL; return LFS_ERR_INVAL;
@@ -3477,7 +3487,7 @@ static int lfs_commitattr(lfs_t *lfs, const char *path,
f->id == id && f->id == id &&
// only readable handles because wronly files // only readable handles because wronly files
// may reference attributes in ROM // may reference attributes in ROM
(f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { (f->flags & LFS_O_RDONLY)) {
// sync attrs // sync attrs
for (lfs_size_t i = 0; i < f->cfg->attr_count; i++) { for (lfs_size_t i = 0; i < f->cfg->attr_count; i++) {
if (f->cfg->attrs[i].type == type) { if (f->cfg->attrs[i].type == type) {
@@ -5148,7 +5158,8 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
err = lfs_file_rawclose(lfs, file); err = lfs_file_rawclose(lfs, file);
@@ -5164,7 +5175,8 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
err = lfs_file_rawsync(lfs, file); err = lfs_file_rawsync(lfs, file);
@@ -5182,7 +5194,8 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
} }
LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")",
(void*)lfs, (void*)file, buffer, size); (void*)lfs, (void*)file, buffer, size);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
lfs_ssize_t res = lfs_file_rawread(lfs, file, buffer, size); lfs_ssize_t res = lfs_file_rawread(lfs, file, buffer, size);
@@ -5200,7 +5213,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
} }
LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")",
(void*)lfs, (void*)file, buffer, size); (void*)lfs, (void*)file, buffer, size);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
lfs_ssize_t res = lfs_file_rawwrite(lfs, file, buffer, size); lfs_ssize_t res = lfs_file_rawwrite(lfs, file, buffer, size);
@@ -5218,7 +5232,8 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
} }
LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)",
(void*)lfs, (void*)file, off, whence); (void*)lfs, (void*)file, off, whence);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
lfs_soff_t res = lfs_file_rawseek(lfs, file, off, whence); lfs_soff_t res = lfs_file_rawseek(lfs, file, off, whence);
@@ -5235,7 +5250,8 @@ int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
} }
LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")",
(void*)lfs, (void*)file, size); (void*)lfs, (void*)file, size);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
err = lfs_file_rawtruncate(lfs, file, size); err = lfs_file_rawtruncate(lfs, file, size);
@@ -5251,7 +5267,8 @@ lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
lfs_soff_t res = lfs_file_rawtell(lfs, file); lfs_soff_t res = lfs_file_rawtell(lfs, file);
@@ -5280,7 +5297,8 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
return err; return err;
} }
LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file);
LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) ||
lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
lfs_soff_t res = lfs_file_rawsize(lfs, file); lfs_soff_t res = lfs_file_rawsize(lfs, file);

10
lfs.h
View File

@@ -127,20 +127,24 @@ enum lfs_open_flags {
#ifndef LFS_READONLY #ifndef LFS_READONLY
LFS_O_WRONLY = 2, // Open a file as write only LFS_O_WRONLY = 2, // Open a file as write only
LFS_O_RDWR = 3, // Open a file as read and write LFS_O_RDWR = 3, // Open a file as read and write
#endif
#ifndef LFS_READONLY
LFS_O_CREAT = 0x0100, // Create a file if it does not exist LFS_O_CREAT = 0x0100, // Create a file if it does not exist
LFS_O_EXCL = 0x0200, // Fail if a file already exists LFS_O_EXCL = 0x0200, // Fail if a file already exists
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS_O_APPEND = 0x0800, // Move to end of file on every write LFS_O_APPEND = 0x0800, // Move to end of file on every write
LFS_O_SNAPSHOT = 0x1000, // Open a temporary snapshot, ignore changes
#endif #endif
// internally used flags // internally used flags
#ifndef LFS_READONLY #ifndef LFS_READONLY
LFS_F_DIRTY = 0x010000, // File does not match storage LFS_F_DIRTY = 0x010000, // File does not match storage
LFS_F_WRITING = 0x020000, // File has been written since last flush
#endif #endif
LFS_F_READING = 0x040000, // File has been read since last flush LFS_F_READING = 0x020000, // File has been read since last flush
#ifndef LFS_READONLY #ifndef LFS_READONLY
LFS_F_ERRED = 0x080000, // An error occurred during write LFS_F_WRITING = 0x040000, // File has been written since last flush
LFS_F_ZOMBIE = 0x080000, // An error occurred during write
#endif #endif
LFS_F_INLINE = 0x100000, // Currently inlined in directory entry LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
}; };

View File

@@ -219,8 +219,8 @@ code = '''
assert(memcmp(buffer+10, "ccccc", 5) == 0); assert(memcmp(buffer+10, "ccccc", 5) == 0);
attrs1[0].size = LFS_ATTR_MAX+1; attrs1[0].size = LFS_ATTR_MAX+1;
lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) lfs_file_opencfg(&lfs, &file, "hello/hello2",
=> LFS_ERR_NOSPC; LFS_O_WRONLY | LFS_O_CREAT, &cfg1) => LFS_ERR_NOSPC;
struct lfs_attr attrs2[] = { struct lfs_attr attrs2[] = {
{'A', buffer, 4}, {'A', buffer, 4},

View File

@@ -504,3 +504,433 @@ code = '''
} }
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # simple snapshot for reading
define.SIZE = [10, 100, 1000, 10000]
code = '''
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
const char nums[] = "0123456789";
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
const struct lfs_file_config filecfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', "abcd", 4},
},
};
lfs_file_opencfg(&lfs, &file, "open_me",
LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// open reader/writer/snapshot
lfs_mount(&lfs, &cfg) => 0;
lfs_file_t reader;
const struct lfs_file_config readercfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_t writer;
const struct lfs_file_config writercfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_t snapshot;
const struct lfs_file_config snapshotcfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
lfs_file_opencfg(&lfs, &writer, "open_me",
LFS_O_WRONLY, &writercfg) => 0;
lfs_file_opencfg(&lfs, &snapshot, "open_me",
LFS_O_RDONLY | LFS_O_SNAPSHOT, &snapshotcfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE/2; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE/2; j++) {
lfs_file_read(&lfs, &snapshot, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
// write file
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &writer, &nums[j % 10], 1) => 1;
}
memcpy(writercfg.attrs[0].buffer, "0123", 4);
lfs_file_sync(&lfs, &writer) => 0;
// reader should change
assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0);
for (int j = SIZE/2; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
// snapshot should remain unchanged
assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = SIZE/2; j < SIZE; j++) {
lfs_file_read(&lfs, &snapshot, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_file_close(&lfs, &writer) => 0;
lfs_file_close(&lfs, &snapshot) => 0;
// disk should change
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # simple snapshot for writing
define.SIZE = [10, 100, 1000, 10000]
code = '''
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
const char nums[] = "0123456789";
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
const struct lfs_file_config filecfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', "abcd", 4},
},
};
lfs_file_opencfg(&lfs, &file, "open_me",
LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// open reader/snapshot
lfs_mount(&lfs, &cfg) => 0;
lfs_file_t reader;
const struct lfs_file_config readercfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_t snapshot;
const struct lfs_file_config snapshotcfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
lfs_file_opencfg(&lfs, &snapshot, "open_me",
LFS_O_RDWR | LFS_O_SNAPSHOT, &snapshotcfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE/2; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &snapshot, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
// modify snapshot
lfs_file_rewind(&lfs, &snapshot) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &snapshot, &nums[j % 10], 1) => 1;
}
memcpy(snapshotcfg.attrs[0].buffer, "0123", 4);
lfs_file_rewind(&lfs, &snapshot) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &snapshot, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_sync(&lfs, &snapshot) => 0;
// reader should not change
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = SIZE/2; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
// snapshot should changed
assert(memcmp(snapshotcfg.attrs[0].buffer, "0123", 4) == 0);
lfs_file_rewind(&lfs, &snapshot) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &snapshot, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_file_close(&lfs, &snapshot) => 0;
// disk should not change
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # temporary files
define.SIZE = [10, 100, 1000, 10000]
define.TMP_PATH = 'range(4)'
code = '''
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
const char nums[] = "0123456789";
const char caps[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
const struct lfs_file_config filecfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', "abcd", 4},
},
};
lfs_file_opencfg(&lfs, &file, "open_me",
LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_file_opencfg(&lfs, &file, "dont_open_me",
LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// open reader/writer/temp
lfs_mount(&lfs, &cfg) => 0;
lfs_file_t reader;
const struct lfs_file_config readercfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_t writer;
const struct lfs_file_config writercfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_t tmp;
const struct lfs_file_config tmpcfg = {
.attr_count = 1,
.attrs = (struct lfs_attr[]){
{'A', &(uint8_t[4]){0}, 4}
},
};
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
lfs_file_opencfg(&lfs, &writer, "open_me",
LFS_O_WRONLY, &writercfg) => 0;
const char *tmp_paths[] = {NULL, "/", "/tmp", "/open_me.tmp"};
lfs_file_opencfg(&lfs, &tmp, tmp_paths[TMP_PATH],
LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT, &tmpcfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE/3; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
assert(memcmp(tmpcfg.attrs[0].buffer, "\0\0\0\0", 4) == 0);
assert(lfs_file_size(&lfs, &tmp) == 0);
// write to tmp
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &tmp, &nums[j % 10], 1) => 1;
}
memcpy(tmpcfg.attrs[0].buffer, "0123", 4);
lfs_file_rewind(&lfs, &tmp) => 0;
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &tmp, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_sync(&lfs, &tmp) => 0;
// reader should not change
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = SIZE/3; j < 2*SIZE/3; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
// tmp should change
assert(memcmp(tmpcfg.attrs[0].buffer, "0123", 4) == 0);
lfs_file_rewind(&lfs, &tmp) => 0;
for (int j = 0; j < SIZE/2; j++) {
lfs_file_read(&lfs, &tmp, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
// write to file
for (int j = 0; j < SIZE; j++) {
lfs_file_write(&lfs, &writer, &caps[j % 26], 1) => 1;
}
memcpy(writercfg.attrs[0].buffer, "ABCD", 4);
lfs_file_sync(&lfs, &writer) => 0;
// reader should change
assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0);
for (int j = 2*SIZE/3; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == caps[j % 26]);
}
// tmp should not change
assert(memcmp(tmpcfg.attrs[0].buffer, "0123", 4) == 0);
for (int j = SIZE/2; j < SIZE; j++) {
lfs_file_read(&lfs, &tmp, buffer, 1) => 1;
assert(buffer[0] == nums[j % 10]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_file_close(&lfs, &writer) => 0;
lfs_file_close(&lfs, &tmp) => 0;
// tmp should not appear on disk
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "dont_open_me") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "open_me") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == caps[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_file_opencfg(&lfs, &reader, "dont_open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "dont_open_me") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "open_me") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_opencfg(&lfs, &reader, "open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == caps[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_file_opencfg(&lfs, &reader, "dont_open_me",
LFS_O_RDONLY, &readercfg) => 0;
assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0);
for (int j = 0; j < SIZE; j++) {
lfs_file_read(&lfs, &reader, buffer, 1) => 1;
assert(buffer[0] == alphas[j % 26]);
}
lfs_file_close(&lfs, &reader) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # test snapshot open errors
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, NULL,
LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_ISDIR;
lfs_file_open(&lfs, &file, "/",
LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_ISDIR;
lfs_file_open(&lfs, &file, "/tmp",
LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_NOENT;
lfs_file_open(&lfs, &file, "/tmp/",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT) => LFS_ERR_NOENT;
lfs_file_open(&lfs, &file, "/tmp/tmp",
LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT) => LFS_ERR_NOENT;
lfs_unmount(&lfs) => 0;
'''