From fdc8c8caf1f9787c6cf6842f7d3ae6c50bd0e6b3 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 10 Jan 2024 16:47:42 -0600 Subject: [PATCH] Fixed noop sync broadcasting, added more specific sync tests There are a number of nuanced cases to watch out for when mixing sync, desync, and "noop syncs" (sync when no write operation has occured): 1. Noop-sync after unrelated write: op a state b state in-sync in-sync write(a) unsync in-sync sync(b) in-sync in-sync In this case, a should be clobbered by b when b syncs. But this gets tricky since b is still up to date with the disk, so b's unsynced flag is not set. The solution here is to just unconditionally broadcast all sync operations irregardless of on-disk state. This is all in-device anyways, so it shouldn't really add any overhead. 2. Noop-sync after unrelated write-sync after desync: op a state b state in-sync in-sync desync(b) in-sync desync write(a) unsync desync sync(a) in-sync' desync sync(b) in-sync in-sync In this case, a should again be clobbered by b, even though a is in-sync with the disk. This is not tricky because of a's state, but because b doesn't know it is no longer in-sync with the disk. The solution here is to set the unsynced flag on all desynced files when an unrelated file is synced. This way, b knows it needs to update disk if sync is called. We already scan all opened files to update in-sync files, so this has very little cost. 3. Readonly-sync after unrelated write-sync after desync? This is basically the same as 2., but involves a readonly file: op a state b state (rdonly) in-sync in-sync desync(b) in-sync desync write(a) unsync desync sync(a) in-sync' desync sync(b) ??? in-sync In this case, I have no idea what should happen. I would guess the least surprising result would be for b to write its contents to a/disk? Bringing everything in-sync? But this implies that b, a readonly file, should write to disk. This isn't the only place a read operation would result in a write. RDWR files, for example, can flush buffers during a file read. But at least there, the file is open RDWR, not strictly RDONLY. It seems like writing during sync on a readonly file breaks some sort of invariant users expect. But the alternative: Dropping the current state of b in favor of a's state, is inconsistent with sync on WRONLY/RDWR files, and seems like it breaks some sort of invariant about sync modifying the current file's state... Given this situation, I think the best course of action is to just disallow sync on readonly files. It is now an assert. There is some precedent for this, upstream we already omit sync when compiled in LFS_READONLY mode. Though this does deviate from POSIX behavior... Worst case, by asserting, this leaves us free to introduce different readonly-sync behavior in the future without breaking backwards compatibility. --- Maybe there should be some sort of lfsr_file_resync function to discard current changes? Though this can be done with a close+open cycle, so I think the value would be low. Added tests over these cases and fixed where they broke, except for 3., lfsr_file_sync and lfsr_file_flush get asserts now to prevent their use on readonly files. Also added a couple more specific tests to cover cases I was concerned about. --- lfs.c | 100 +++-- tests/test_fsync.toml | 990 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1042 insertions(+), 48 deletions(-) diff --git a/lfs.c b/lfs.c index fc887829..ec94fdc7 100644 --- a/lfs.c +++ b/lfs.c @@ -5118,20 +5118,6 @@ static int lfsr_mdir_lookupwide(lfs_t *lfs, const lfsr_mdir_t *mdir, } // track opened mdirs to keep state in-sync -static void lfsr_addopened(lfs_t *lfs, lfsr_opened_t *opened) { - opened->next = lfs->opened; - lfs->opened = opened; -} - -static void lfsr_removeopened(lfs_t *lfs, lfsr_opened_t *opened) { - for (lfsr_opened_t **p = &lfs->opened; *p; p = &(*p)->next) { - if (*p == opened) { - *p = (*p)->next; - break; - } - } -} - static bool lfsr_isopened(lfs_t *lfs, const lfsr_opened_t *opened) { for (lfsr_opened_t *p = lfs->opened; p; p = p->next) { if (p == opened) { @@ -5142,6 +5128,22 @@ static bool lfsr_isopened(lfs_t *lfs, const lfsr_opened_t *opened) { return false; } +static void lfsr_addopened(lfs_t *lfs, lfsr_opened_t *opened) { + LFS_ASSERT(!lfsr_isopened(lfs, opened)); + opened->next = lfs->opened; + lfs->opened = opened; +} + +static void lfsr_removeopened(lfs_t *lfs, lfsr_opened_t *opened) { + LFS_ASSERT(lfsr_isopened(lfs, opened)); + for (lfsr_opened_t **p = &lfs->opened; *p; p = &(*p)->next) { + if (*p == opened) { + *p = (*p)->next; + break; + } + } +} + /// Metadata-tree things /// @@ -10415,6 +10417,7 @@ static int lfsr_file_flush_(lfs_t *lfs, lfsr_file_t *file, lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, void *buffer, lfs_size_t size) { + // can't read from writeonly files LFS_ASSERT(lfsr_o_isreadable(file->flags)); LFS_ASSERT(file->pos + size <= 0x7fffffff); @@ -10505,6 +10508,7 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file, lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, const void *buffer, lfs_size_t size) { + // can't write to readonly files LFS_ASSERT(lfsr_o_iswriteable(file->flags)); // would this write make our file larger than our size limit? @@ -10655,14 +10659,11 @@ failed:; } int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { - // do nothing if our file is readonly - if (!lfsr_o_iswriteable(file->flags)) { - LFS_ASSERT(!lfsr_f_isunflushed(file->flags) - || (lfsr_file_size_(file) <= lfs->cfg->cache_size - && lfsr_file_size_(file) <= lfs->cfg->inline_size - && lfsr_file_size_(file) <= lfs->cfg->fragment_size)); - return 0; - } + // flushing readonly files is not supported + // + // in theory we could make this a noop, but that would be inconsistent + // with lfsr_file_sync + LFS_ASSERT(lfsr_o_iswriteable(file->flags)); // do nothing if our file is already flushed if (!lfsr_f_isunflushed(file->flags)) { @@ -10706,19 +10707,17 @@ failed:; } int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { + // syncing readonly files is not supported + // + // in theory we could make this a noop, but then syncing desynced + // readonly files would require disk writes + LFS_ASSERT(lfsr_o_iswriteable(file->flags)); + // do nothing if our file has been removed if (file->mdir.mid == -1) { return 0; } - // do nothing if our file is readonly - if (!lfsr_o_iswriteable(file->flags)) { - LFS_ASSERT(!lfsr_f_isunsynced(file->flags)); - // but do clear desync flag - file->flags &= ~LFS_O_DESYNC; - return 0; - } - // first flush any data in our buffer, this is a noop if already // flushed // @@ -10750,14 +10749,14 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { && file->buffer_size <= lfs->cfg->inline_size && file->buffer_size <= lfs->cfg->fragment_size)); - // checkpoint the allocator again - lfs_alloc_ckpoint(lfs); - - // don't write to disk if disk is already in-sync + // don't write to disk if our disk is already in-sync if (lfsr_f_isunsynced(file->flags)) { + // checkpoint the allocator again + lfs_alloc_ckpoint(lfs); + // commit our file's metadata uint8_t buf[LFSR_BTREE_DSIZE]; - int err = lfsr_mdir_commit(lfs, &file->mdir, LFSR_ATTRS( + err = lfsr_mdir_commit(lfs, &file->mdir, LFSR_ATTRS( (lfsr_f_isunflushed(file->flags) && file->buffer_size == 0) ? LFSR_ATTR(file->mdir.mid, WIDE(RM(STRUCT)), 0, @@ -10774,7 +10773,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { WIDE(BTREE), 0, FROMBTREE(&file->ftree.u.btree, buf)))); if (err) { - return err; + goto failed; } } @@ -10786,20 +10785,25 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { if (file_->type == LFS_TYPE_REG && file_->mdir.mid == file->mdir.mid // don't double update - && file_ != file - // don't update desynced file handles - && !lfsr_o_isdesync(file_->flags)) { - file_->flags &= ~LFS_F_UNSYNCED; - if (lfsr_f_isunflushed(file->flags)) { - file_->flags |= LFS_F_UNFLUSHED; + && file_ != file) { + // mark desynced files an unsynced + if (lfsr_o_isdesync(file_->flags)) { + file_->flags |= LFS_F_UNSYNCED; + + // update synced files } else { - file_->flags &= ~LFS_F_UNFLUSHED; + file_->flags &= ~LFS_F_UNSYNCED; + if (lfsr_f_isunflushed(file->flags)) { + file_->flags |= LFS_F_UNFLUSHED; + } else { + file_->flags &= ~LFS_F_UNFLUSHED; + } + file_->ftree = file->ftree; + file_->buffer_pos = file->buffer_pos; + LFS_ASSERT(file->buffer_size <= lfs->cfg->cache_size); + memcpy(file_->buffer, file->buffer, file->buffer_size); + file_->buffer_size = file->buffer_size; } - file_->ftree = file->ftree; - file_->buffer_pos = file->buffer_pos; - LFS_ASSERT(file->buffer_size <= lfs->cfg->cache_size); - memcpy(file_->buffer, file->buffer, file->buffer_size); - file_->buffer_size = file->buffer_size; } } diff --git a/tests/test_fsync.toml b/tests/test_fsync.toml index fcc6d55c..b6e583b1 100644 --- a/tests/test_fsync.toml +++ b/tests/test_fsync.toml @@ -2,6 +2,483 @@ after = 'test_fwrite' +# Some specific tests +[cases.test_fsync_sync_wrr] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - reader kept open, recvs updates from a + // c - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_RDONLY) => 0; + + // write to a + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + + // should not show up in b yet + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => 0; + // or on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + lfsr_file_close(&lfs, &c) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in b + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // lets rewrite a + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // b should still have previous contents + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // lets rewrite a one last time + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // b should still have previous contents + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // close a + lfsr_file_close(&lfs, &a) => 0; + + // now our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + lfsr_file_close(&lfs, &b) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_sync_wwrr] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a/b + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and b + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should not show up in c yet + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // or on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => 0; + lfsr_file_close(&lfs, &d) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // b's contents were clobbered, so we should see a + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // now our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite a one last time + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // close a and b + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_close(&lfs, &b) => 0; + + // now our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_sync_wwrr_noop] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a/b + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and b + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should not show up in c yet + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // or on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => 0; + lfsr_file_close(&lfs, &d) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // b's contents were clobbered, so we should see a + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync a, without doing anything + lfsr_file_sync(&lfs, &a) => 0; + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b, this gets tricky + lfsr_file_sync(&lfs, &b) => 0; + + // b's contents were clobbered, so we should see a + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite b one last time, this time with sync + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + lfsr_file_sync(&lfs, &b) => 0; + + // now our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // close a and b + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_close(&lfs, &b) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_sync_wwrr_append] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a/b + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_APPEND) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY | LFS_O_APPEND) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and b + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should not show up in c yet + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // or on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => 0; + lfsr_file_close(&lfs, &d) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // b's contents were clobbered, so we should see a + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // now our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite a one last time + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // close a and b + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_close(&lfs, &b) => 0; + + // now our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!zdrasti!"); + assert(memcmp(rbuf, "hello!ohayo!zdrasti!", + strlen("hello!ohayo!zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!zdrasti!"); + assert(memcmp(rbuf, "hello!ohayo!zdrasti!", + strlen("hello!ohayo!zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + + # Test multiple readers, this shouldn't really have any issues [cases.test_fsync_rrrr] defines.R = 4 @@ -1221,6 +1698,519 @@ code = ''' # Desynced files make things interesting +# Some specific tests +[cases.test_fsync_desync_wdrr] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - reader kept open, recvs updates from a + // c - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_RDONLY) => 0; + + // write to a and sync + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in b + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // now mark as desync, rewrite, and close + lfsr_file_desync(&lfs, &a) => 0; + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + lfsr_file_close(&lfs, &a) => 0; + + // b should still have previous contents + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // reopen a, mark as desync, rewrite a, and sync + lfsr_file_open(&lfs, &a, "jello", LFS_O_WRONLY) => 0; + lfsr_file_desync(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + // this sync should clear the desync + lfsr_file_sync(&lfs, &a) => 0; + + // now our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + // rewrite a, close, desync flag should have been cleared + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + lfsr_file_close(&lfs, &a) => 0; + + // our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &c) => 0; + + lfsr_file_close(&lfs, &b) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_desync_wrrd] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - reader kept open, recvs updates from a + // c - desynced reader + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_RDONLY) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY | LFS_O_DESYNC) => 0; + + // write to a and sync + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in b + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // but not in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // reopen c, should now be up to date + lfsr_file_close(&lfs, &c) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY | LFS_O_DESYNC) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + + // rewrite and sync + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + // but not in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // reopen c, should now be up to date + lfsr_file_close(&lfs, &c) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY | LFS_O_DESYNC) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + + // rewrite and close + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + lfsr_file_close(&lfs, &a) => 0; + + // our write should show up in b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_read(&lfs, &b, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // but not in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + // reopen c, should now be up to date + lfsr_file_close(&lfs, &c) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY | LFS_O_DESYNC) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + + lfsr_file_close(&lfs, &b) => 0; + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_desync_wdwdrr] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and sync + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // mark b as desync, rewrite, and close + lfsr_file_desync(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + lfsr_file_close(&lfs, &b) => 0; + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // reopen b, mark as desync, rewrite + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY) => 0; + lfsr_file_desync(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + // rewrite a, sync + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + lfsr_file_sync(&lfs, &a) => 0; + + // a should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // b should show up in c, without a's changes + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // rewrite b, close, desync flag should have been cleared + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "annyeong!", strlen("annyeong!")) + => strlen("annyeong!"); + lfsr_file_close(&lfs, &b) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("annyeong!"); + assert(memcmp(rbuf, "annyeong!", strlen("annyeong!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("annyeong!"); + assert(memcmp(rbuf, "annyeong!", strlen("annyeong!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // rewrite a, close + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "czesc!", strlen("czesc!")) + => strlen("czesc!"); + lfsr_file_close(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("czesc!ng!"); + assert(memcmp(rbuf, "czesc!ng!", strlen("czesc!ng!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("czesc!ng!"); + assert(memcmp(rbuf, "czesc!ng!", strlen("czesc!ng!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_desync_wdwdrr_noop] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and sync + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // mark b as desync, this should freeze its contents + lfsr_file_desync(&lfs, &b) => 0; + // rewrite a, sync + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + lfsr_file_sync(&lfs, &a) => 0; + + // a should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("bonjour!"); + assert(memcmp(rbuf, "bonjour!", strlen("bonjour!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b, this may be tricky since we haven't touched b + lfsr_file_sync(&lfs, &b) => 0; + + // b should show up in c, without a's changes + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // rewrite b, close, desync flag should have been cleared + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + lfsr_file_close(&lfs, &b) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!"); + assert(memcmp(rbuf, "ohayo!", strlen("ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // rewrite a, close + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + lfsr_file_close(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("zdrasti!"); + assert(memcmp(rbuf, "zdrasti!", strlen("zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fsync_desync_wdwdrr_append] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // a - writer + // b - writer + // c - reader kept open, recvs updates from a + // d - reader kept closed, checks disk state + lfsr_file_t a; + lfsr_file_t b; + lfsr_file_t c; + lfsr_file_t d; + uint8_t rbuf[256]; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_APPEND) => 0; + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY | LFS_O_APPEND) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a and sync + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + lfsr_file_sync(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // mark b as desync, write, and close + lfsr_file_desync(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + lfsr_file_close(&lfs, &b) => 0; + + // c should still have previous contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!"); + assert(memcmp(rbuf, "hello!", strlen("hello!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // reopen b, mark as desync, write + lfsr_file_open(&lfs, &b, "jello", LFS_O_WRONLY | LFS_O_APPEND) => 0; + lfsr_file_desync(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + // write a, sync + lfsr_file_write(&lfs, &a, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + lfsr_file_sync(&lfs, &a) => 0; + + // a should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!zdrasti!"); + assert(memcmp(rbuf, "hello!zdrasti!", strlen("hello!zdrasti!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!zdrasti!"); + assert(memcmp(rbuf, "hello!zdrasti!", strlen("hello!zdrasti!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync b + lfsr_file_sync(&lfs, &b) => 0; + + // b should show up in c, without a's changes + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("hello!ohayo!"); + assert(memcmp(rbuf, "hello!ohayo!", strlen("hello!ohayo!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // write b, close, desync flag should have been cleared + lfsr_file_write(&lfs, &b, "annyeong!", strlen("annyeong!")) + => strlen("annyeong!"); + lfsr_file_close(&lfs, &b) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!annyeong!"); + assert(memcmp(rbuf, "hello!ohayo!annyeong!", + strlen("hello!ohayo!annyeong!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!annyeong!"); + assert(memcmp(rbuf, "hello!ohayo!annyeong!", + strlen("hello!ohayo!annyeong!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // write a, close + lfsr_file_write(&lfs, &a, "czesc!", strlen("czesc!")) + => strlen("czesc!"); + lfsr_file_close(&lfs, &a) => 0; + + // our write should show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!annyeong!czesc!"); + assert(memcmp(rbuf, "hello!ohayo!annyeong!czesc!", + strlen("hello!ohayo!annyeong!czesc!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) + => strlen("hello!ohayo!annyeong!czesc!"); + assert(memcmp(rbuf, "hello!ohayo!annyeong!czesc!", + strlen("hello!ohayo!annyeong!czesc!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + + # Test one desynced writer, multiple readers [cases.test_fsync_drrr] defines.R = 4