Pushed ftree tracking down into lfsr_ftree_carve

This is an attempt to reduce the overhead of on-stack snapshots during
writes, by relaxing the gaurantees provided by lfsr_file_write during
errors.

Before, thanks to the on-stack snapshot, file writes could revert to the
previous state if an error occurred. Now, on-stack snapshots are limited
to lfsr_ftree_carve, so only the state change in lfsr_ftree_carve is
reverted.

This should behave relatively predictably, since lfsr_ftree_flush calls
lfsr_ftree_carve in a normal order. If an error occurs, some, none, or
all of the data is actually written. For truncate/fruncate, there is
only one call to carve, so these remain atomic.

It's tempting to want to push this lower. If you push the atomic
operations down to the btree/bshrub level, individual btree/bshrub commits
are already atomic, so tracking on-stack bshrubs could be dropped
completely. But failures at the sub-carve level get weird! Thanks to
block crystallization and the use of order-statistic operations, the
resulting file can be quite unpredictable. For example:

  on-disk: abcdefghijklmnopqrstuvwxyz
  write:            JKLMN
  error!
  on-disk: abcdstuvwxyz

With atomic carves, worst case is something like this:

  on-disk: abcdefghijklmnopqrstuvwxyz
  write:            JKLMN
  error!
  on-disk: abcdefghiJKlmnopqrstuvwxyz

Thanks to file-level snapshotting, the original file can still be
recovered (and is on-disk until sync is called), but the
unpredictability is concerning.

Alternatively, if we had btree range removals, lfsr_ftree_carve could be
entirely atomic (and more efficient).

Unfortunately, btree range removals still seem like they would be
difficult to implement, and will probably be out of scope for some time.
The added code cost will also likely outweigh any savings from dropping
on-stack bshrub tracking. Still, this is probably worth looking into in
the future.

Code changes:

            code          stack
  before:  33356           3072
  after:   33006 (-1.0%)   2992 (-2.6%)
This commit is contained in:
Christopher Haster
2024-01-03 15:48:48 -06:00
parent f14d06b22b
commit 5ce5927fdd
2 changed files with 116 additions and 144 deletions
+112 -144
View File
@@ -9655,6 +9655,7 @@ static lfs_ssize_t lfsr_ftree_read(lfs_t *lfs, const lfsr_ftree_t *ftree,
return pos_ - pos;
}
// this is atomic, otherwise write failure would get really weird
static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
lfs_off_t pos, lfs_off_t weight, lfs_soff_t delta,
lfsr_tag_t tag, const lfsr_bptr_t *bptr, const lfsr_ecksum_t *becksum) {
@@ -9670,50 +9671,48 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
// These requirements end up conflicting a bit...
//
// The second requirement isn't strictly necessary if we track temporary
// copies during file writes, but it is nice to prove this constraint is
// possible in case we ever don't track temporary copies.
// copies, but it is nice to prove this constraint is possible in case
// we ever don't track temporary copies.
// copy and track state so we can recover from errors
//
// if we don't do this, write errors get really weird (data around
// file position arbitrarily collapsed)
lfsr_ftree_t ftree_ = *ftree;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
int err;
// always convert to bshrub/btree when this function is called
if (!lfsr_ftree_isbshruborbtree(ftree)) {
lfsr_attr_t attrs_[2];
lfs_size_t attr_count_ = 0;
uint8_t buf[LFSR_BPTR_DSIZE+LFSR_ECKSUM_DSIZE];
lfs_size_t buf_size = 0;
// these also check if ftree is non-zero
if (lfsr_ftree_isbsprout(ftree)) {
attrs_[attr_count_++] = LFSR_ATTR(0,
DATA, +lfsr_ftree_size(ftree),
DATA(ftree->u.bsprout.data));
} else if (lfsr_ftree_isbleaf(ftree)) {
attrs_[attr_count_++] = LFSR_ATTR(0,
BLOCK, +lfsr_ftree_size(ftree),
FROMBPTR(&ftree->u.bptr, &buf[buf_size]));
buf_size += LFSR_BPTR_DSIZE;
// append becksum?
if (ftree->u.bleaf.becksum.size != -1) {
attrs_[attr_count_++] = LFSR_ATTR(lfsr_ftree_size(ftree)-1,
BECKSUM, 0,
FROMECKSUM(&ftree->u.bleaf.becksum, &buf[buf_size]));
buf_size += LFSR_ECKSUM_DSIZE;
}
}
ftree->u.bshrub.rbyd.blocks[0] = ftree->mdir.rbyd.blocks[0];
ftree->u.bshrub.rbyd.trunk = 0;
ftree->u.bshrub.rbyd.weight = 0;
ftree_.u.bshrub.rbyd.blocks[0] = ftree->mdir.rbyd.blocks[0];
ftree_.u.bshrub.rbyd.trunk = 0;
ftree_.u.bshrub.rbyd.weight = 0;
// force estimate recalculation
ftree->u.bshrub.estimate = -1;
ftree_.u.bshrub.estimate = -1;
if (attr_count_ > 0) {
LFS_ASSERT(attr_count_ <= sizeof(attrs_)/sizeof(lfsr_attr_t));
LFS_ASSERT(buf_size <= sizeof(buf));
int err = lfsr_bshrub_commit(lfs, &ftree->mdir, &ftree->u.bshrub,
attrs_, attr_count_);
if (lfsr_ftree_size(ftree) > 0) {
uint8_t bptr_buf[LFSR_BPTR_DSIZE];
uint8_t becksum_buf[LFSR_ECKSUM_DSIZE];
err = lfsr_bshrub_commit(lfs, &ftree_.mdir, &ftree_.u.bshrub,
LFSR_ATTRS(
(lfsr_ftree_isbsprout(ftree))
? LFSR_ATTR(0,
DATA, +lfsr_ftree_size(ftree),
DATA(ftree->u.bsprout.data))
: LFSR_ATTR(0,
BLOCK, +lfsr_ftree_size(ftree),
FROMBPTR(&ftree->u.bptr, bptr_buf)),
// append becksum?
(lfsr_ftree_isbleaf(ftree)
&& ftree->u.bleaf.becksum.size != -1)
? LFSR_ATTR(lfsr_ftree_size(ftree)-1,
BECKSUM, 0,
FROMECKSUM(&ftree->u.bleaf.becksum,
becksum_buf))
: LFSR_ATTR_NOOP()));
if (err) {
return err;
goto failed;
}
}
}
@@ -9727,18 +9726,18 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
lfs_size_t buf_size = 0;
// try to carve any existing data
while (pos < lfsr_ftree_size(ftree)) {
while (pos < lfsr_ftree_size(&ftree_)) {
lfsr_bid_t bid_;
lfsr_tag_t tag_;
lfsr_bid_t weight_;
lfsr_bptr_t bptr_;
lfsr_ecksum_t becksum_;
int err = lfsr_ftree_lookupnext(lfs, ftree,
err = lfsr_ftree_lookupnext(lfs, &ftree_,
pos,
&bid_, &tag_, &weight_, &bptr_, &becksum_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
goto failed;
}
// note, an entry can be both a left and right sibling
@@ -9758,7 +9757,7 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
lfs->cfg->fragment_size,
-1);
err = lfsr_bshrub_commit(lfs, &ftree->mdir, &ftree->u.bshrub,
err = lfsr_bshrub_commit(lfs, &ftree_.mdir, &ftree_.u.bshrub,
LFSR_ATTRS(
LFSR_ATTR(bid_,
GROW(WIDE(DATA)),
@@ -9769,7 +9768,7 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
BLOCK, +(weight_ - lfs->cfg->fragment_size),
FROMBPTR(&bptr_, buf))));
if (err) {
return err;
goto failed;
}
weight_ -= lfs->cfg->fragment_size;
@@ -9786,7 +9785,7 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
bptr_.data = lfsr_data_truncate(bptr_.data,
lfsr_data_size(&bptr_.data) - lfs->cfg->fragment_size);
err = lfsr_bshrub_commit(lfs, &ftree->mdir, &ftree->u.bshrub,
err = lfsr_bshrub_commit(lfs, &ftree_.mdir, &ftree_.u.bshrub,
LFSR_ATTRS(
LFSR_ATTR(bid_,
GROW(WIDE(BLOCK)),
@@ -9798,7 +9797,7 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
DATA(lfsr_data_fruncate(right_slice_,
lfs->cfg->fragment_size)))));
if (err) {
return err;
goto failed;
}
bid_ -= (weight_-lfsr_data_size(&bptr_.data));
@@ -9847,10 +9846,10 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
LFS_ASSERT(attr_count_ <= sizeof(attrs_)/sizeof(lfsr_attr_t));
LFS_ASSERT(buf_size <= sizeof(buf));
err = lfsr_bshrub_commit(lfs, &ftree->mdir, &ftree->u.bshrub,
err = lfsr_bshrub_commit(lfs, &ftree_.mdir, &ftree_.u.bshrub,
attrs_, attr_count_);
if (err) {
return err;
goto failed;
}
delta += lfs_min32(weight, bid_+1 - pos);
@@ -9901,16 +9900,16 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
}
// need a hole?
if (pos > lfsr_ftree_size(ftree)) {
if (pos > lfsr_ftree_size(&ftree_)) {
// can we coalesce?
if (lfsr_ftree_size(ftree) > 0) {
attrs_[attr_count_++] = LFSR_ATTR(lfsr_ftree_size(ftree)-1,
GROW, +(pos - lfsr_ftree_size(ftree)), NULL());
if (lfsr_ftree_size(&ftree_) > 0) {
attrs_[attr_count_++] = LFSR_ATTR(lfsr_ftree_size(&ftree_)-1,
GROW, +(pos - lfsr_ftree_size(&ftree_)), NULL());
// new hole
} else {
attrs_[attr_count_++] = LFSR_ATTR(lfsr_ftree_size(ftree),
DATA, +(pos - lfsr_ftree_size(ftree)), NULL());
attrs_[attr_count_++] = LFSR_ATTR(lfsr_ftree_size(&ftree_),
DATA, +(pos - lfsr_ftree_size(&ftree_)), NULL());
}
}
@@ -9953,14 +9952,23 @@ static int lfsr_ftree_carve(lfs_t *lfs, lfsr_ftree_t *ftree,
LFS_ASSERT(attr_count_ <= sizeof(attrs_)/sizeof(lfsr_attr_t));
LFS_ASSERT(buf_size <= sizeof(buf));
int err = lfsr_bshrub_commit(lfs, &ftree->mdir, &ftree->u.bshrub,
err = lfsr_bshrub_commit(lfs, &ftree_.mdir, &ftree_.u.bshrub,
attrs_, attr_count_);
if (err) {
return err;
goto failed;
}
}
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// update our ftree
ftree->u = ftree_.u;
return 0;
failed:;
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
return err;
}
static int lfsr_ftree_flush(lfs_t *lfs, lfsr_ftree_t *ftree,
@@ -10566,20 +10574,11 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
return 0;
}
// copy state so we can recover from errors
lfs_off_t pos_ = file->pos;
bool unflushed_ = lfsr_f_isunflushed(file->flags);
lfs_off_t buffer_pos_ = file->buffer_pos;
lfs_size_t buffer_size_ = file->buffer_size;
lfsr_ftree_t ftree_ = file->ftree;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
int err;
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// update pos if we are appending
lfs_off_t pos_ = file->pos;
if (lfsr_o_isappend(file->flags)) {
pos_ = file->size;
}
@@ -10589,15 +10588,16 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
&& pos_ <= lfs->cfg->cache_size
&& pos_ <= lfs->cfg->inline_size
&& pos_ <= lfs->cfg->fragment_size) {
LFS_ASSERT(unflushed_);
LFS_ASSERT(file->size == buffer_size_);
memset(&file->buffer[buffer_size_],
LFS_ASSERT(lfsr_f_isunflushed(file->flags));
LFS_ASSERT(file->size == file->buffer_size);
memset(&file->buffer[file->buffer_size],
0,
pos_ - buffer_size_);
buffer_size_ = pos_;
pos_ - file->buffer_size);
file->buffer_size = pos_;
}
const uint8_t *buffer_ = buffer;
int err;
while (size > 0) {
// bypass buffer?
//
@@ -10605,8 +10605,9 @@ 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 heuristics
//
if (!unflushed_ && size >= lfs->cfg->cache_size) {
err = lfsr_ftree_flush(lfs, &ftree_,
if (!lfsr_f_isunflushed(file->flags)
&& size >= lfs->cfg->cache_size) {
err = lfsr_ftree_flush(lfs, &file->ftree,
pos_, buffer_, size);
if (err) {
goto failed;
@@ -10615,15 +10616,19 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
// update our buffer if we overlap
//
// but do this after writing so we can't fail
if (pos_ < buffer_pos_ + buffer_size_
&& pos_ + size > buffer_pos_) {
memcpy(&file->buffer[pos_ - lfs_min32(buffer_pos_, pos_)],
&buffer_[buffer_pos_ - lfs_min32(pos_, buffer_pos_)],
if (pos_ < file->buffer_pos + file->buffer_size
&& pos_ + size > file->buffer_pos) {
memcpy(&file->buffer[pos_ - lfs_min32(file->buffer_pos, pos_)],
&buffer_[file->buffer_pos - lfs_min32(
pos_,
file->buffer_pos)],
lfs_min32(
buffer_size_ - (
pos_ - lfs_min32(buffer_pos_, pos_)),
file->buffer_size - (
pos_ - lfs_min32(file->buffer_pos, pos_)),
size - (
buffer_pos_ - lfs_min32(pos_, buffer_pos_))));
file->buffer_pos - lfs_min32(
pos_,
file->buffer_pos))));
}
pos_ += size;
@@ -10642,43 +10647,42 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
// 2. Bypassing the buffer above means we only write to the
// buffer once, and flush at most twice.
//
if (!unflushed_
|| (pos_ >= buffer_pos_
&& pos_ <= buffer_pos_ + buffer_size_
&& pos_ < buffer_pos_ + lfs->cfg->cache_size)) {
if (!lfsr_f_isunflushed(file->flags)
|| (pos_ >= file->buffer_pos
&& pos_ <= file->buffer_pos + file->buffer_size
&& pos_ < file->buffer_pos + lfs->cfg->cache_size)) {
// unused buffer? we can move it where we need it
if (!unflushed_) {
buffer_pos_ = pos_;
buffer_size_ = 0;
if (!lfsr_f_isunflushed(file->flags)) {
file->buffer_pos = pos_;
file->buffer_size = 0;
}
lfs_size_t d = lfs_min32(
size,
lfs->cfg->cache_size - (pos_ - buffer_pos_));
memcpy(&file->buffer[pos_ - buffer_pos_], buffer_, d);
buffer_size_ = lfs_max32(
buffer_size_,
pos_+d - buffer_pos_);
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);
unflushed_ = true;
file->flags |= LFS_F_UNFLUSHED;
pos_ += d;
buffer_ += d;
size -= d;
continue;
}
// TODO can this use lfsr_file_flush?
// flush our buffer so the above can't fail
err = lfsr_ftree_flush(lfs, &ftree_,
buffer_pos_, file->buffer, buffer_size_);
err = lfsr_ftree_flush(lfs, &file->ftree,
file->buffer_pos, file->buffer, file->buffer_size);
if (err) {
goto failed;
}
unflushed_ = false;
file->flags &= ~LFS_F_UNFLUSHED;
}
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as unflushed and unsynced, update file, and return amount written
// mark as unsynced, update file, and return amount written
lfs_size_t written;
if (lfsr_o_isappend(file->flags)) {
written = pos_ - file->size;
@@ -10686,19 +10690,13 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
written = pos_ - file->pos;
}
file->flags |= LFS_F_UNSYNCED;
if (unflushed_) {
file->flags |= LFS_F_UNFLUSHED;
}
file->pos = pos_;
file->size = lfs_max32(file->size, pos_);
file->buffer_pos = buffer_pos_;
file->buffer_size = buffer_size_;
file->ftree.u = ftree_.u;
return written;
failed:;
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// at least keep file size up-to-date
file->size = lfs_max32(file->size, pos_);
// mark as desync so lfsr_file_close doesn't write to disk
file->flags |= LFS_O_DESYNC;
return err;
@@ -10734,32 +10732,22 @@ static int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// copy state so we can recover from errors
lfsr_ftree_t ftree_ = file->ftree;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
int err;
// flush our buffer if it contains any unwritten data
int err;
if (lfsr_f_isunflushed(file->flags) && file->buffer_size != 0) {
// flush
err = lfsr_ftree_flush(lfs, &ftree_,
err = lfsr_ftree_flush(lfs, &file->ftree,
file->buffer_pos, file->buffer, file->buffer_size);
if (err) {
goto failed;
}
}
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as flushed and update our file
file->ftree.u = ftree_.u;
file->flags &= ~LFS_F_UNFLUSHED;
return 0;
failed:;
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as desync so lfsr_file_close doesn't write to disk
file->flags |= LFS_O_DESYNC;
return err;
@@ -10930,13 +10918,8 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// copy state so we can recover from errors
lfsr_ftree_t ftree_ = file->ftree;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
int err;
// does our file become small?
int err;
if (size <= lfs->cfg->cache_size
&& size <= lfs->cfg->inline_size
&& size <= lfs->cfg->fragment_size) {
@@ -10974,12 +10957,12 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
file->flags |= LFS_F_UNFLUSHED;
file->buffer_pos = 0;
file->buffer_size = size;
ftree_.u.size = LFSR_FTREE_NULL;
file->ftree.u.size = LFSR_FTREE_NULL;
// truncate our file normally
} else {
// truncate our ftree
err = lfsr_ftree_carve(lfs, &ftree_,
err = lfsr_ftree_carve(lfs, &file->ftree,
lfs_min32(file->size, size),
file->size - lfs_min32(file->size, size),
+size - file->size,
@@ -10995,11 +10978,8 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
size - lfs_min32(file->buffer_pos, size));
}
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as unsynced and update our size
file->flags |= LFS_F_UNSYNCED;
file->ftree.u = ftree_.u;
file->size = size;
LFS_ASSERT(file->size == lfs_max32(
file->buffer_pos + file->buffer_size,
@@ -11007,8 +10987,6 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
return 0;
failed:;
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as desync so lfsr_file_close doesn't write to disk
file->flags |= LFS_O_DESYNC;
return err;
@@ -11028,13 +11006,8 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// copy state so we can recover from errors
lfsr_ftree_t ftree_ = file->ftree;
// add to tracked mdirs
lfsr_mdir_addopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
int err;
// does our file become small?
int err;
if (size <= lfs->cfg->cache_size
&& size <= lfs->cfg->inline_size
&& size <= lfs->cfg->fragment_size) {
@@ -11082,12 +11055,12 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
file->flags |= LFS_F_UNFLUSHED;
file->buffer_pos = 0;
file->buffer_size = size;
ftree_.u.size = LFSR_FTREE_NULL;
file->ftree.u.size = LFSR_FTREE_NULL;
// fruncate our file normally
} else {
// fruncate our ftree
err = lfsr_ftree_carve(lfs, &ftree_,
err = lfsr_ftree_carve(lfs, &file->ftree,
0,
lfs_smax32(file->size - size, 0),
+size - file->size,
@@ -11110,11 +11083,8 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
file->buffer_pos -= lfs_smin32(file->size - size, file->buffer_pos);
}
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as unsynced and update our size
file->flags |= LFS_F_UNSYNCED;
file->ftree.u = ftree_.u;
file->size = size;
LFS_ASSERT(file->size == lfs_max32(
file->buffer_pos + file->buffer_size,
@@ -11122,8 +11092,6 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) {
return 0;
failed:;
// remove from tracked mdirs
lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_);
// mark as desync so lfsr_file_close doesn't write to disk
file->flags |= LFS_O_DESYNC;
return err;
+4
View File
@@ -831,6 +831,10 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// If an error occurs during a write (including LFS_ERR_NOSPC), the file
// will be marked as desynchronized, the position will be left unchanged,
// and some, all, or none of the data may be written.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);