Added lfsr_file_resync
lfsr_file_resync discards the current working state of a file and
reverts it to the contents on disk. It also clears the desynced flag
from files, so provides an alternative to lfsr_file_sync for when you
don't want to write to the filesystem:
disk=A file=A disk=A file=A
| write B | write B
v v
disk=A file=B disk=A file=B
| sync | resync
v v
disk=B file=B disk=A file=A
The main motivation for this is to provide a way to mark desynced
readonly files as in-sync, without putting them into a weird state where
they are "in-sync" but don't match disk.
It's also a bit safer if the file is desynced due to an error, since
errors aren't currently guaranteed to leave file data in a defined
state. Needed to resync to recover from errors avoids accidentally
syncing partial writes.
This exact behavior can also be accomplished by closing+opening the
file, but lfsr_file_resync makes it much easier without _that_ much
extra code. It may even pay for itself if you consider what code it
saves on the user's side of things.
I considered naming this lfsr_file_discard because I think it sounds
cooler, but I figured including sync in the name provides a stronger
hint that it affects the file's desync status.
---
You may think it's not possible for a readonly file to become
out-of-sync from disk, since it's, well, readonly. But it is possible
thanks to desynced files ignoring other sync broadcasts.
Consider what happens if you open a file readonly, and write+sync the
file with another file handle at the same time:
disk=A f1=A f2=A
| desync f2
v
disk=A f1=A f2=A
| write f1=B
v
disk=A f1=B f2=A
| sync f1
v
disk=B f1=B f2=A <-- f2 is out-of-sync without any writes
---
This commit also changes lfsr_file_sync/flush to assert if the file is
readonly. Previously we allowed lfsr_file_sync to be called on readonly
files if it would be a noop, but lfsr_file_resync makes this
unnecessary.
More code means more code, but I think it is well worth it for the
additional flexibility:
code stack
before: 36412 2616
after: 36748 (+0.9%) 2616 (+0.0%)
This commit is contained in:
@@ -11127,15 +11127,15 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
}
|
||||
|
||||
// setup file state
|
||||
file->cfg = cfg;
|
||||
file->o.o.flags = lfsr_o_settype(flags, LFS_TYPE_REG)
|
||||
// mounted with LFS_M_FLUSH/SYNC? implies LFS_O_FLUSH/SYNC
|
||||
| (lfs->flags & (LFS_M_FLUSH | LFS_M_SYNC));
|
||||
file->cfg = cfg;
|
||||
// default data state
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
file->pos = 0;
|
||||
file->eblock = 0;
|
||||
file->eoff = -1;
|
||||
// default data state
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
|
||||
// lookup our parent
|
||||
lfsr_tag_t tag;
|
||||
@@ -11202,7 +11202,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
// if we're truncating don't bother to read any state, we're
|
||||
// just going to truncate after all
|
||||
if (!lfsr_o_istrunc(flags)) {
|
||||
// read any inlined state
|
||||
// lookup the file struct, if there is one
|
||||
lfsr_tag_t tag;
|
||||
lfsr_data_t data;
|
||||
err = lfsr_mdir_lookupnext(lfs, &file->o.o.mdir, LFSR_TAG_DATA,
|
||||
@@ -11259,9 +11259,10 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
file->buffer.size = 0;
|
||||
|
||||
// if our file is small, try to keep the whole thing in our buffer
|
||||
if (lfsr_bshrub_size(&file->o.bshrub) <= lfsr_file_inlinesize(lfs, file)) {
|
||||
lfs_size_t size = lfsr_bshrub_size(&file->o.bshrub);
|
||||
if (size <= lfsr_file_inlinesize(lfs, file)) {
|
||||
lfs_ssize_t d = lfsr_file_read_(lfs, file,
|
||||
0, file->buffer.buffer, lfsr_bshrub_size(&file->o.bshrub));
|
||||
0, file->buffer.buffer, size);
|
||||
if (d < 0) {
|
||||
err = d;
|
||||
goto failed;
|
||||
@@ -11269,9 +11270,9 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
|
||||
// small files remain perpetually unflushed
|
||||
file->o.o.flags |= LFS_O_UNFLUSH;
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = lfsr_bshrub_size(&file->o.bshrub);
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = size;
|
||||
}
|
||||
|
||||
// check metadata/data for errors?
|
||||
@@ -12561,10 +12562,8 @@ failed:;
|
||||
|
||||
int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
|
||||
// readonly files should do nothing
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(file->o.o.flags)
|
||||
|| !lfsr_o_isunflush(file->o.o.flags)
|
||||
|| lfsr_file_size_(file) <= lfsr_file_inlinesize(lfs, file));
|
||||
// can't write to readonly files
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(file->o.o.flags));
|
||||
|
||||
// do nothing if our file is already flushed
|
||||
if (!lfsr_o_isunflush(file->o.o.flags)) {
|
||||
@@ -12609,6 +12608,10 @@ failed:;
|
||||
|
||||
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
|
||||
// can't write to readonly files, if you want to resync call
|
||||
// lfsr_file_resync
|
||||
LFS_ASSERT(!lfsr_o_isrdonly(file->o.o.flags));
|
||||
|
||||
// removed? we can't sync
|
||||
int err;
|
||||
if (lfsr_o_iszombie(file->o.o.flags)) {
|
||||
@@ -12648,23 +12651,8 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_ASSERT(!lfsr_o_isorphan(file->o.o.flags)
|
||||
|| lfsr_o_isunsync(file->o.o.flags));
|
||||
|
||||
// don't write to disk if our disk is already in-sync
|
||||
// don't write to disk if already in-sync
|
||||
if (lfsr_o_isunsync(file->o.o.flags)) {
|
||||
// readonly files should do nothing
|
||||
//
|
||||
// but readonly files _can_ end up unsynced, in the roundabout
|
||||
// case where:
|
||||
//
|
||||
// 1. a file is opened rdonly + desync
|
||||
// 2. the same file is opened and written to
|
||||
// 3. we try to sync our original file handle
|
||||
//
|
||||
// the best thing we can do in this case is return an error
|
||||
if (lfsr_o_isrdonly(file->o.o.flags)) {
|
||||
err = LFS_ERR_INVAL;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// commit any changes to our file's metadata
|
||||
lfsr_attr_t attrs[2];
|
||||
lfs_size_t attr_count = 0;
|
||||
@@ -12782,10 +12770,113 @@ int lfsr_file_desync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
(void)lfs;
|
||||
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
|
||||
|
||||
// mark as desynced
|
||||
file->o.o.flags |= LFS_O_DESYNC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfsr_file_resync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFS_ASSERT(lfsr_omdir_isopen(lfs, &file->o.o));
|
||||
|
||||
// removed? we can't resync
|
||||
int err;
|
||||
if (lfsr_o_iszombie(file->o.o.flags)) {
|
||||
err = LFS_ERR_NOENT;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// do nothing if already in-sync
|
||||
if (lfsr_o_isunsync(file->o.o.flags)) {
|
||||
// default data state
|
||||
file->o.bshrub_ = LFSR_BSHRUB_BNULL();
|
||||
|
||||
// don't bother reading disk if we're an orphan
|
||||
if (!lfsr_o_isorphan(file->o.o.flags)) {
|
||||
// lookup the file struct, if there is one
|
||||
lfsr_tag_t tag;
|
||||
lfsr_data_t data;
|
||||
err = lfsr_mdir_lookupnext(lfs, &file->o.o.mdir, LFSR_TAG_DATA,
|
||||
&tag, &data);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// note many of these functions leave bshrub undefined if
|
||||
// there is an error, so we first read into the staging
|
||||
// bshrub
|
||||
|
||||
// may be a sprout (simple inlined data)
|
||||
if (err != LFS_ERR_NOENT && tag == LFSR_TAG_DATA) {
|
||||
file->o.bshrub_.u.bsprout = data;
|
||||
|
||||
// or a direct block
|
||||
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BLOCK) {
|
||||
err = lfsr_data_readbptr(lfs, &data,
|
||||
&file->o.bshrub_.u.bptr);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// or a bshrub (inlined btree)
|
||||
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BSHRUB) {
|
||||
err = lfsr_data_readshrub(lfs, &data, &file->o.o.mdir,
|
||||
&file->o.bshrub_.u.bshrub);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// or a btree
|
||||
} else if (err != LFS_ERR_NOENT && tag == LFSR_TAG_BTREE) {
|
||||
err = lfsr_data_fetchbtree(lfs, &data,
|
||||
&file->o.bshrub_.u.btree);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark as flushed and synced if we're not an orphan
|
||||
file->o.o.flags &= ~(
|
||||
LFS_O_UNFLUSH
|
||||
| ((!lfsr_o_isorphan(file->o.o.flags))
|
||||
? LFS_O_UNSYNC
|
||||
: 0));
|
||||
// update the bshrub
|
||||
file->o.bshrub = file->o.bshrub_;
|
||||
// discard the current buffer
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = 0;
|
||||
|
||||
// if our file is small, try to keep the whole thing in our buffer
|
||||
//
|
||||
// if this fails we may end up with corrupt data, but that's ok, we
|
||||
// just can't end up with corrupt metadata
|
||||
lfs_size_t size = lfsr_bshrub_size(&file->o.bshrub);
|
||||
if (size <= lfsr_file_inlinesize(lfs, file)) {
|
||||
lfs_ssize_t d = lfsr_file_read_(lfs, file,
|
||||
0, file->buffer.buffer, size);
|
||||
if (d < 0) {
|
||||
err = d;
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// small files remain perpetually unflushed
|
||||
file->o.o.flags |= LFS_O_UNFLUSH;
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
// mark as resynced
|
||||
file->o.o.flags &= ~LFS_O_DESYNC;
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
file->o.o.flags |= LFS_O_DESYNC;
|
||||
return err;
|
||||
}
|
||||
|
||||
// other file operations
|
||||
|
||||
lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file,
|
||||
@@ -12897,9 +12988,9 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
|
||||
|
||||
// small files remain perpetually unflushed
|
||||
file->o.o.flags |= LFS_O_UNFLUSH;
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = size_;
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
|
||||
// truncate our file normally
|
||||
} else {
|
||||
@@ -13019,9 +13110,9 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
|
||||
|
||||
// small files remain perpetually unflushed
|
||||
file->o.o.flags |= LFS_O_UNFLUSH;
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
file->buffer.pos = 0;
|
||||
file->buffer.size = size_;
|
||||
file->o.bshrub = LFSR_BSHRUB_BNULL();
|
||||
|
||||
// fruncate our file normally
|
||||
} else {
|
||||
|
||||
@@ -988,6 +988,15 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file);
|
||||
//int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
|
||||
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
// Flush any buffered data
|
||||
//
|
||||
// This does not update metadata and is called implicitly by lfsr_file_sync.
|
||||
// Calling this explicitly may be useful for preventing write errors in
|
||||
// read operations.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
// Mark a file as desynchronized
|
||||
//
|
||||
// Desynchronized files do not recieve file updates and do not sync on close.
|
||||
@@ -997,20 +1006,19 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file);
|
||||
// If an error occurs during a write operation, the file is implicitly marked
|
||||
// as desynchronized.
|
||||
//
|
||||
// An explicit and successful call to lfsr_file_sync reverses this, marking
|
||||
// the file as synchronized again.
|
||||
// An explicit and successful call to either lfsr_file_sync or
|
||||
// lfsr_file_resync reverses this, marking the file as synchronized again.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfsr_file_desync(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
// Flush any buffered data
|
||||
// Discard unsynchronized changes and mark a file as synchronized
|
||||
//
|
||||
// This does not update metadata and is called implicitly by lfsr_file_sync.
|
||||
// Calling this explicitly may be useful for preventing write errors in
|
||||
// read operations.
|
||||
// This is effectively the same as closing and reopening the file, and
|
||||
// may read from disk to figure out file state.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file);
|
||||
int lfsr_file_resync(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
// Read data from file
|
||||
//
|
||||
|
||||
+1335
-123
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user