Implemented file-level error recover in theory
Not yet tested, thus the "in theory". Testing this is going to be a bit
tricky. Fortunately on-stack copies are a pretty resilient way to
recover from errors.
This comes with an unfortunate, but necessary, code/stack increase:
code stack
before: 31280 2736
after: 31584 (+1.0%) 2824 (+3.2%)
This commit is contained in:
@@ -10280,17 +10280,20 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// create a copy and track it so our shrub gets updates
|
||||
lfsr_file_t file_ = *file;
|
||||
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
int err;
|
||||
|
||||
// checkpoint the allocator
|
||||
lfs_alloc_ack(lfs);
|
||||
|
||||
// update pos if we are appending
|
||||
if (lfsr_file_isappend(file)) {
|
||||
file->pos = file->size;
|
||||
if (lfsr_file_isappend(&file_)) {
|
||||
file_.pos = file_.size;
|
||||
}
|
||||
|
||||
lfs_off_t pos = file->pos;
|
||||
const uint8_t *buffer_ = buffer;
|
||||
int err;
|
||||
while (size > 0) {
|
||||
// bypass write buffer?
|
||||
//
|
||||
@@ -10298,64 +10301,83 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
|
||||
// strictly necessary, but enforces a more intuitive write order
|
||||
// and avoids weird cases with low-level write strategies
|
||||
//
|
||||
if (file->buffer_size == 0
|
||||
if (file_.buffer_size == 0
|
||||
&& size >= lfs->cfg->cache_size) {
|
||||
// TODO can we avoid needing F_UNSYNCED before lfsr_file_flush
|
||||
file->flags |= LFS_F_UNSYNCED;
|
||||
file_.flags |= LFS_F_UNSYNCED;
|
||||
|
||||
err = lfsr_file_flush(lfs, file,
|
||||
pos, buffer_, size);
|
||||
err = lfsr_file_flush(lfs, &file_,
|
||||
file_.pos, buffer_, size);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
pos += size;
|
||||
file_.pos += size;
|
||||
buffer_ += size;
|
||||
size -= size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to fill our write buffer
|
||||
if (file->buffer_size == 0
|
||||
|| (pos >= file->buffer_pos
|
||||
&& pos <= file->buffer_pos + file->buffer_size
|
||||
&& pos < file->buffer_pos + lfs->cfg->cache_size)) {
|
||||
//
|
||||
// This is a bit delicate, since our buffer is shared between our
|
||||
// backup and staging file copies, but note:
|
||||
//
|
||||
// 1. We only write to yet unused buffer memory.
|
||||
//
|
||||
// 2. Bypassing the buffer above means we only write to the
|
||||
// buffer once, and flush at most twice.
|
||||
//
|
||||
if (file_.buffer_size == 0
|
||||
|| (file_.pos >= file_.buffer_pos
|
||||
&& file_.pos <= file_.buffer_pos + file_.buffer_size
|
||||
&& file_.pos < file_.buffer_pos + lfs->cfg->cache_size)) {
|
||||
// unused buffer? we can move this where we need it
|
||||
if (file->buffer_size == 0) {
|
||||
file->buffer_pos = pos;
|
||||
if (file_.buffer_size == 0) {
|
||||
file_.buffer_pos = file_.pos;
|
||||
}
|
||||
|
||||
lfs_size_t d = lfs_min32(
|
||||
size,
|
||||
lfs->cfg->cache_size - (pos - file->buffer_pos));
|
||||
memcpy(&file->buffer[pos - file->buffer_pos], buffer_, d);
|
||||
file->buffer_size = lfs_max32(
|
||||
file->buffer_size,
|
||||
pos+d - file->buffer_pos);
|
||||
lfs->cfg->cache_size - (file_.pos - file_.buffer_pos));
|
||||
memcpy(&file_.buffer[file_.pos - file_.buffer_pos], buffer_, d);
|
||||
file_.buffer_size = lfs_max32(
|
||||
file_.buffer_size,
|
||||
file_.pos+d - file_.buffer_pos);
|
||||
|
||||
pos += d;
|
||||
file_.pos += d;
|
||||
buffer_ += d;
|
||||
size -= d;
|
||||
file->flags |= LFS_F_UNSYNCED;
|
||||
file_.flags |= LFS_F_UNSYNCED;
|
||||
continue;
|
||||
}
|
||||
|
||||
// flush our buffer so the above can't fail
|
||||
err = lfsr_file_flush(lfs, file,
|
||||
file->buffer_pos, file->buffer, file->buffer_size);
|
||||
err = lfsr_file_flush(lfs, &file_,
|
||||
file_.buffer_pos, file_.buffer, file_.buffer_size);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
file->buffer_pos = 0;
|
||||
file->buffer_size = 0;
|
||||
file_.buffer_pos = 0;
|
||||
file_.buffer_size = 0;
|
||||
}
|
||||
|
||||
lfs_size_t written = pos - file->pos;
|
||||
file->pos = pos;
|
||||
file->size = lfs_max32(file->size, file->pos);
|
||||
// update size
|
||||
file_.size = lfs_max32(file_.size, file_.pos);
|
||||
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// update file and return amount written
|
||||
lfs_size_t written = file_.pos - (
|
||||
(lfsr_file_isappend(&file_)) ? file->size : file->pos);
|
||||
file_.next = file->next;
|
||||
*file = file_;
|
||||
return written;
|
||||
|
||||
failed:;
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// mark as errored so lfsr_file_close doesn't write to disk
|
||||
file->flags |= LFS_F_ERRORED;
|
||||
return err;
|
||||
}
|
||||
@@ -10427,17 +10449,39 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
} else {
|
||||
// first make sure to flush our buffer
|
||||
//
|
||||
// note that flush does not change the actual file data, so if
|
||||
// flush succeeds but mdir commit fails it's ok to fall back to
|
||||
// our flushed state
|
||||
//
|
||||
// TODO can we avoid an extra commit here? this may be too complex
|
||||
// to be worth doing...
|
||||
//
|
||||
if (file->buffer_size > 0) {
|
||||
err = lfsr_file_flush(lfs, file,
|
||||
file->buffer_pos, file->buffer, file->buffer_size);
|
||||
// TODO dedup into lfsr_file_flush?
|
||||
|
||||
// create a copy and track it so our shrub gets updates
|
||||
lfsr_file_t file_ = *file;
|
||||
lfsr_mdir_addopened(lfs, LFS_TYPE_REG,
|
||||
(lfsr_openedmdir_t*)&file_);
|
||||
|
||||
// flush
|
||||
err = lfsr_file_flush(lfs, &file_,
|
||||
file_.buffer_pos, file_.buffer, file_.buffer_size);
|
||||
if (err) {
|
||||
// make sure to untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG,
|
||||
(lfsr_openedmdir_t*)&file_);
|
||||
goto failed;
|
||||
}
|
||||
file->buffer_pos = 0;
|
||||
file->buffer_size = 0;
|
||||
file_.buffer_pos = 0;
|
||||
file_.buffer_size = 0;
|
||||
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG,
|
||||
(lfsr_openedmdir_t*)&file_);
|
||||
// update file
|
||||
file_.next = file->next;
|
||||
*file = file_;
|
||||
}
|
||||
|
||||
// now commit our file's metadata
|
||||
@@ -10527,47 +10571,62 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO make this recoverable on failure
|
||||
// create a copy and track it so our shrub gets updates
|
||||
lfsr_file_t file_ = *file;
|
||||
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
int err;
|
||||
|
||||
// mark as unsynced before we commit anything
|
||||
file->flags |= LFS_F_UNSYNCED;
|
||||
file_.flags |= LFS_F_UNSYNCED;
|
||||
|
||||
// TODO we should also revert to sprout even if data is not already
|
||||
// in buffer
|
||||
//
|
||||
// if our truncated file is contained entirely in our buffer,
|
||||
// revert to a sprout
|
||||
lfs_off_t buffer_pos = lfs_min32(file->buffer_pos, size);
|
||||
lfs_off_t buffer_pos = lfs_min32(file_.buffer_pos, size);
|
||||
lfs_size_t buffer_size = lfs_min32(
|
||||
file->buffer_size,
|
||||
size - lfs_min32(file->buffer_pos, size));
|
||||
file_.buffer_size,
|
||||
size - lfs_min32(file_.buffer_pos, size));
|
||||
if (buffer_size >= size) {
|
||||
file->u.bsprout = LFSR_FILE_BNULL();
|
||||
file_.u.bsprout = LFSR_FILE_BNULL();
|
||||
|
||||
// TODO, wait, could we just update file->size and leave it to
|
||||
// TODO, wait, could we just update file_.size and leave it to
|
||||
// lfsr_file_sync to update the shrub?
|
||||
// otherwise, we need to modify our sprout/bptr/bshrub/btree
|
||||
} else {
|
||||
int err = lfsr_file_carve(lfs, file,
|
||||
lfs_min32(file->size, size),
|
||||
file->size - lfs_min32(file->size, size),
|
||||
+size - file->size,
|
||||
err = lfsr_file_carve(lfs, &file_,
|
||||
lfs_min32(file_.size, size),
|
||||
file_.size - lfs_min32(file_.size, size),
|
||||
+size - file_.size,
|
||||
LFSR_TAG_DATA, LFSR_DATA_NULL());
|
||||
if (err) {
|
||||
return err;
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
LFS_ASSERT(!lfsr_file_isbshruborbtree(file)
|
||||
|| lfsr_file_uweight(file) > 0);
|
||||
LFS_ASSERT(!lfsr_file_isbshruborbtree(&file_)
|
||||
|| lfsr_file_uweight(&file_) > 0);
|
||||
|
||||
// update our buffer
|
||||
file->buffer_pos = buffer_pos;
|
||||
file->buffer_size = buffer_size;
|
||||
file_.buffer_pos = buffer_pos;
|
||||
file_.buffer_size = buffer_size;
|
||||
|
||||
// update our internal file size
|
||||
file->size = size;
|
||||
file_.size = size;
|
||||
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// update file
|
||||
file_.next = file->next;
|
||||
*file = file_;
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// mark as errored so lfsr_file_close doesn't write to disk
|
||||
file->flags |= LFS_F_ERRORED;
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
|
||||
@@ -10581,21 +10640,24 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO make this recoverable on failure
|
||||
// create a copy and track it so our shrub gets updates
|
||||
lfsr_file_t file_ = *file;
|
||||
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
int err;
|
||||
|
||||
// mark as unsynced before we commit anything
|
||||
file->flags |= LFS_F_UNSYNCED;
|
||||
file_.flags |= LFS_F_UNSYNCED;
|
||||
|
||||
// TODO we should also revert to sprout even if data is not already
|
||||
// in buffer
|
||||
//
|
||||
// if our truncated file is contained entirely in our buffer,
|
||||
// revert to a sprout
|
||||
lfs_size_t buffer_size = file->buffer_size - lfs_min32(
|
||||
lfs_smax32(file->size - size - file->buffer_pos, 0),
|
||||
file->buffer_size);
|
||||
lfs_size_t buffer_size = file_.buffer_size - lfs_min32(
|
||||
lfs_smax32(file_.size - size - file_.buffer_pos, 0),
|
||||
file_.buffer_size);
|
||||
if (buffer_size >= size) {
|
||||
file->u.bsprout = LFSR_FILE_BNULL();
|
||||
file_.u.bsprout = LFSR_FILE_BNULL();
|
||||
|
||||
// otherwise, we need to modify our sprout/bptr/bshrub/btree
|
||||
} else {
|
||||
@@ -10603,34 +10665,46 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
|
||||
// merged somehow?
|
||||
//
|
||||
// revert shrubs if they go to zero
|
||||
if ((lfs_soff_t)(file->size - size)
|
||||
>= (lfs_soff_t)lfsr_file_uweight(file)) {
|
||||
file->u.bsprout = LFSR_FILE_BNULL();
|
||||
if ((lfs_soff_t)(file_.size - size)
|
||||
>= (lfs_soff_t)lfsr_file_uweight(&file_)) {
|
||||
file_.u.bsprout = LFSR_FILE_BNULL();
|
||||
} else {
|
||||
int err = lfsr_file_carve(lfs, file,
|
||||
err = lfsr_file_carve(lfs, &file_,
|
||||
0,
|
||||
lfs_smax32(file->size - size, 0),
|
||||
+size - file->size,
|
||||
lfs_smax32(file_.size - size, 0),
|
||||
+size - file_.size,
|
||||
LFSR_TAG_DATA, LFSR_DATA_NULL());
|
||||
if (err) {
|
||||
return err;
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
LFS_ASSERT(!lfsr_file_isbshruborbtree(file)
|
||||
|| lfsr_file_uweight(file) > 0);
|
||||
LFS_ASSERT(!lfsr_file_isbshruborbtree(&file_)
|
||||
|| lfsr_file_uweight(&file_) > 0);
|
||||
|
||||
// update our buffer
|
||||
file->buffer_pos -= lfs_smin32(file->size - size, file->buffer_pos);
|
||||
memmove(file->buffer,
|
||||
file->buffer + (file->buffer_size - buffer_size),
|
||||
file_.buffer_pos -= lfs_smin32(file_.size - size, file_.buffer_pos);
|
||||
memmove(file_.buffer,
|
||||
file_.buffer + (file_.buffer_size - buffer_size),
|
||||
buffer_size);
|
||||
file->buffer_size = buffer_size;
|
||||
file_.buffer_size = buffer_size;
|
||||
|
||||
// update our internal file size
|
||||
file->size = size;
|
||||
file_.size = size;
|
||||
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// update file
|
||||
file_.next = file->next;
|
||||
*file = file_;
|
||||
return 0;
|
||||
|
||||
failed:;
|
||||
// untrack temporary copy
|
||||
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&file_);
|
||||
// mark as errored so lfsr_file_close doesn't write to disk
|
||||
file->flags |= LFS_F_ERRORED;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user