From 0f4ad6d842283d075716875f76f84e1937c7b1b4 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 25 Apr 2025 20:51:17 -0500 Subject: [PATCH] Tweaked lfsr_file_open to sync when LFS_O_SYNC So now the following creates a reg file (instead of just a stickynote): lfsr_file_open(&lfs, &file, "test.txt", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_SYNC) => 0; // powerloss!! struct lfsr_info info; lfsr_stat(&lfs, "test.txt", &info) => 0; // LFS_ERR_NOENT before assert(info.type == LFS_TYPE_REG); This hopefully results in more intuitive behavior around lfsr_file_open with LFS_O_SYNC. Which is important as LFS_O_SYNC is often used as an escape hatch to avoid needing to reason about syncing things when performance is not a big concern. Unfortunately this does come with a surprisingly big code/stack cost, but I'm thinking of putting these flags (LFS_O_FLUSH/LFS_O_SYNC) behind ifdefs anyways (LFS_MAYBE_SYNC?): code stack ctx before: 35780 2440 640 after: 35836 (+0.2%) 2488 (+2.0%) 640 (+0.0%) Also added some more tests to make sure these open+LFS_O_SYNC cases are explicitly covered: - test_fsync_sync_o_wrr - test_fsync_sync_o_wwrr - test_fsync_desync_o_wdwrr - test_fsync_resync_o_wdwyrr This does make a bit of a mess when you combined LFS_O_SYNC + LFS_O_DESYNC. What exactly should a SYNC + DESYNC file look like? For now I've just made LFS_O_SYNC + LFS_O_DESYNC behave as if you opened a file with LFS_O_SYNC and then immediately called lfsr_file_desync on it. So it doesn't receive broadcasts, but _does_ create the reg file, and _does_ sync on first write, clearing the desync flag. But this may be worth revisiting. Maybe LFS_O_DESYNC files shouldn't have their desync flags cleared unless lfsr_file_sync is explicitly called? Or maybe LFS_O_SYNC + LFS_O_DESYNC should just be an error? Unsure... --- lfs.c | 25 +- tests/test_fsync.toml | 590 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 611 insertions(+), 4 deletions(-) diff --git a/lfs.c b/lfs.c index 0550fc49..a5dbfa5d 100644 --- a/lfs.c +++ b/lfs.c @@ -11339,6 +11339,9 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, || !lfsr_o_isexcl(cfg->attrs[i].flags)); } + // mounted with LFS_M_FLUSH/SYNC? implies LFS_O_FLUSH/SYNC + flags |= lfs->flags & (LFS_M_FLUSH | LFS_M_SYNC); + if (!lfsr_o_isrdonly(flags)) { // prepare our filesystem for writing int err = lfsr_fs_mkconsistent(lfs); @@ -11350,8 +11353,6 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, // setup file state file->cfg = cfg; file->b.o.flags = lfsr_o_settype(flags, LFS_TYPE_REG) - // mounted with LFS_M_FLUSH/SYNC? implies LFS_O_FLUSH/SYNC - | (lfs->flags & (LFS_M_FLUSH | LFS_M_SYNC)) // default to unflushed for orphans/truncated files | LFS_o_UNFLUSH; file->pos = 0; @@ -11441,8 +11442,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, file->cache.size = 0; // fetch the file struct and custom attrs - err = lfsr_file_fetch(lfs, file, - lfsr_o_istrunc(file->b.o.flags)); + err = lfsr_file_fetch(lfs, file, lfsr_o_istrunc(flags)); if (err) { goto failed; } @@ -11457,6 +11457,23 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, // add to tracked mdirs lfsr_omdir_open(lfs, &file->b.o); + + // sync if requested + if (lfsr_o_issync(flags) && !lfsr_o_isrdonly(flags)) { + err = lfsr_file_sync(lfs, file); + if (err) { + lfsr_omdir_close(lfs, &file->b.o); + goto failed; + } + + // TODO should we do this for all LFS_O_SYNC operations? + // sync clears the desync flag, so reset it if we're desync + // + // note this matches the behavior of calling lfsr_file_sync and + // then lfsr_file_desync after opening the file + file->b.o.flags |= flags & LFS_O_DESYNC; + } + return 0; failed:; diff --git a/tests/test_fsync.toml b/tests/test_fsync.toml index a814f575..bf7ab37b 100644 --- a/tests/test_fsync.toml +++ b/tests/test_fsync.toml @@ -104,6 +104,98 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test that LFS_O_SYNC/LFS_O_FLUSH flags work as expected +[cases.test_fsync_sync_o_wrr] +# SYNC=0 => no sync, readers not updated +# SYNC=1 => sync via lfsr_file_sync +# SYNC=2 => sync via LFS_O_SYNC +# SYNC=3 => sync via LFS_M_SYNC +defines.SYNC = [2, 3] +# FLUSH=0 => no flush +# FLUSH=1 => flush via lfsr_file_flush +# FLUSH=2 => flush via LFS_O_FLUSH +# FLUSH=3 => flush via LFS_M_FLUSH +defines.FLUSH = [0, 2, 3] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((FLUSH == 3) ? LFS_M_FLUSH : 0) + | ((SYNC == 3) ? LFS_M_SYNC : 0), + 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]; + // LFS_O_SYNC/LFS_M_SYNC should immediately create the file + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY + | LFS_O_CREAT + | LFS_O_EXCL + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0; + struct lfs_info info; + lfsr_stat(&lfs, "jello", &info) => 0; + assert(strcmp(info.name, "jello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + // create the other files + lfsr_file_open(&lfs, &b, "jello", LFS_O_RDONLY) => 0; + + // write to a + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + + // should immediately 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!"); + + // should immediately 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!"); + // close a + lfsr_file_close(&lfs, &a) => 0; + + // should immediately 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; @@ -224,6 +316,128 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test that LFS_O_SYNC/LFS_O_FLUSH flags work as expected +[cases.test_fsync_sync_o_wwrr] +# SYNC=0 => no sync, readers not updated +# SYNC=1 => sync via lfsr_file_sync +# SYNC=2 => sync via LFS_O_SYNC +# SYNC=3 => sync via LFS_M_SYNC +defines.SYNC = [2, 3] +# FLUSH=0 => no flush +# FLUSH=1 => flush via lfsr_file_flush +# FLUSH=2 => flush via LFS_O_FLUSH +# FLUSH=3 => flush via LFS_M_FLUSH +defines.FLUSH = [0, 2, 3] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((FLUSH == 3) ? LFS_M_FLUSH : 0) + | ((SYNC == 3) ? LFS_M_SYNC : 0), + 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]; + // LFS_O_SYNC/LFS_M_SYNC should immediately create the file + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY + | LFS_O_CREAT + | LFS_O_EXCL + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0; + struct lfs_info info; + lfsr_stat(&lfs, "jello", &info) => 0; + assert(strcmp(info.name, "jello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + // create the other files + lfsr_file_open(&lfs, &b, "jello", + LFS_O_WRONLY + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to a + lfsr_file_write(&lfs, &a, "hello!", strlen("hello!")) + => strlen("hello!"); + + // should immediately 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; + + // write to b + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should immediately 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 a + lfsr_file_sync(&lfs, &a) => 0; + + // a's contents were clobbered, so we should see b + 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; + + // lets rewrite a one last time + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // should immediately show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // close a and b + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_close(&lfs, &b) => 0; + + // should still have a + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + [cases.test_fsync_sync_wwrr_zero] code = ''' lfs_t lfs; @@ -2183,6 +2397,199 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test that LFS_O_SYNC/LFS_O_FLUSH flags work as expected +[cases.test_fsync_desync_o_wdwrr] +# SYNC=0 => no sync, readers not updated +# SYNC=1 => sync via lfsr_file_sync +# SYNC=2 => sync via LFS_O_SYNC +# SYNC=3 => sync via LFS_M_SYNC +defines.SYNC = [2, 3] +# FLUSH=0 => no flush +# FLUSH=1 => flush via lfsr_file_flush +# FLUSH=2 => flush via LFS_O_FLUSH +# FLUSH=3 => flush via LFS_M_FLUSH +defines.FLUSH = [0, 2, 3] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((FLUSH == 3) ? LFS_M_FLUSH : 0) + | ((SYNC == 3) ? LFS_M_SYNC : 0), + CFG) => 0; + + // a - desynced 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]; + // LFS_O_SYNC/LFS_M_SYNC should immediately create the file + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY + | LFS_O_CREAT + | LFS_O_EXCL + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0) + | LFS_O_DESYNC) => 0; + struct lfs_info info; + lfsr_stat(&lfs, "jello", &info) => 0; + assert(strcmp(info.name, "jello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + // create the other files + lfsr_file_open(&lfs, &b, "jello", + LFS_O_WRONLY + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to b + lfsr_file_write(&lfs, &b, "hello!", strlen("hello!")) + => strlen("hello!"); + + // 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 a + lfsr_file_sync(&lfs, &a) => 0; + + // c should revert to a's contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // and 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; + + // rewrite a + lfsr_file_write(&lfs, &a, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should immediately 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 + lfsr_file_sync(&lfs, &b) => 0; + + // should still have a + 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; + + // rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!", strlen("ohayo!")) + => strlen("ohayo!"); + + // should immediately show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // c should still have b, this may be surprising, but the implicit + // sync when we rewrote a clears the desync flag + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // desync a + lfsr_file_desync(&lfs, &a) => 0; + // rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + + // should immediately 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 a + lfsr_file_sync(&lfs, &a) => 0; + + // c should revert to a's contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // lets rewrite a one last time + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "annyeong!", strlen("annyeong!")) + => strlen("annyeong!"); + + // should immediately 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; + + // close a and b + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_close(&lfs, &b) => 0; + + // should still have a + 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; + + lfsr_file_close(&lfs, &c) => 0; + lfsr_unmount(&lfs) => 0; +''' + [cases.test_fsync_desync_wwdrr_zero] code = ''' lfs_t lfs; @@ -3807,6 +4214,189 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' +# test that LFS_O_SYNC/LFS_O_FLUSH flags work as expected +[cases.test_fsync_resync_o_wdwyrr] +# SYNC=0 => no sync, readers not updated +# SYNC=1 => sync via lfsr_file_sync +# SYNC=2 => sync via LFS_O_SYNC +# SYNC=3 => sync via LFS_M_SYNC +defines.SYNC = [2, 3] +# FLUSH=0 => no flush +# FLUSH=1 => flush via lfsr_file_flush +# FLUSH=2 => flush via LFS_O_FLUSH +# FLUSH=3 => flush via LFS_M_FLUSH +defines.FLUSH = [0, 2, 3] +code = ''' + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + lfsr_mount(&lfs, + LFS_M_RDWR + | ((FLUSH == 3) ? LFS_M_FLUSH : 0) + | ((SYNC == 3) ? LFS_M_SYNC : 0), + CFG) => 0; + + // a - desynced 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]; + // LFS_O_SYNC/LFS_M_SYNC should immediately create the file + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY + | LFS_O_CREAT + | LFS_O_EXCL + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0) + | LFS_O_DESYNC) => 0; + struct lfs_info info; + lfsr_stat(&lfs, "jello", &info) => 0; + assert(strcmp(info.name, "jello") == 0); + assert(info.type == LFS_TYPE_REG); + assert(info.size == 0); + // create the other files + lfsr_file_open(&lfs, &b, "jello", + LFS_O_WRONLY + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0)) => 0; + lfsr_file_open(&lfs, &c, "jello", LFS_O_RDONLY) => 0; + + // write to b + lfsr_file_write(&lfs, &b, "hello!", strlen("hello!")) + => strlen("hello!"); + + // 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 a + lfsr_file_sync(&lfs, &a) => 0; + + // c should revert to a's contents + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => 0; + // and 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; + + // reopen a + lfsr_file_close(&lfs, &a) => 0; + lfsr_file_open(&lfs, &a, "jello", + LFS_O_WRONLY + | ((FLUSH == 2) ? LFS_O_FLUSH : 0) + | ((SYNC == 2) ? LFS_O_SYNC : 0) + | LFS_O_DESYNC) => 0; + // rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "bonjour!", strlen("bonjour!")) + => strlen("bonjour!"); + + // should immediately 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; + + // resync a + lfsr_file_resync(&lfs, &a) => 0; + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // c should still show b + 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; + + // desync a + lfsr_file_desync(&lfs, &a) => 0; + // rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "ohayo!r!", strlen("ohayo!r!")) + => strlen("ohayo!r!"); + + // should immediately show up in c + lfsr_file_rewind(&lfs, &c) => 0; + lfsr_file_read(&lfs, &c, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + // and on disk + lfsr_file_open(&lfs, &d, "jello", LFS_O_RDONLY) => 0; + lfsr_file_read(&lfs, &d, rbuf, sizeof(rbuf)) => strlen("ohayo!r!"); + assert(memcmp(rbuf, "ohayo!r!", strlen("ohayo!r!")) == 0); + lfsr_file_close(&lfs, &d) => 0; + + // sync a + lfsr_file_sync(&lfs, &a) => 0; + + // c should revert to a's contents + 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; + + // rewrite a + lfsr_file_rewind(&lfs, &a) => 0; + lfsr_file_write(&lfs, &a, "annyeong!", strlen("annyeong!")) + => strlen("annyeong!"); + // desync b + lfsr_file_desync(&lfs, &b) => 0; + // resync b + lfsr_file_resync(&lfs, &b) => 0; + // rewrite b + lfsr_file_rewind(&lfs, &b) => 0; + lfsr_file_write(&lfs, &b, "zdrasti!", strlen("zdrasti!")) + => strlen("zdrasti!"); + + // should immediately 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 b + lfsr_file_close(&lfs, &b) => 0; + // close a + lfsr_file_close(&lfs, &a) => 0; + + // c should still show b's changes + 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_resync_wwdyrr_zero] code = ''' lfs_t lfs;