diff --git a/lfs.c b/lfs.c index 02769499..660d8c62 100644 --- a/lfs.c +++ b/lfs.c @@ -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; diff --git a/lfs.h b/lfs.h index 803f38e6..7940f208 100644 --- a/lfs.h +++ b/lfs.h @@ -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);