From b15940461d9009384b44950f7039657a889e8571 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 2 Jan 2024 13:44:19 -0600 Subject: [PATCH] Implemented desynchronized files Desynchronized files are a new concept intended to capture some useful quirks of the previous multiple-open-file behavior. This adds: - LFS_O_DESYNC - Mark a file as desync during open - lfsr_file_desync - Mark a file as desync whenever - lfsr_file_sync - Mark a file as NOT desync, and sync the file Desynced files: 1. Don't recieve updates from writes to other file handles. This makes desynced files act as a sort of snapshot of the file at the time it was marked desync. 2. Don't call lfsr_file_sync on close. Unless lfsr_file_sync is explicitly called, changes to desynced files are not reflected on disk and not broadcasted to other file handles. A side-effect of 2., is that this gives you a quick way to abort a file write. Marking a file as desync and then closing the file will never error. Additionally, if an error occurs during a write operation, the file is implicitly marked as desync. This provides graceful write aborting in unlikely error cases. This has actually always been a feature in littlefs, it was just named differently and didn't have an optional recovery mode. Since littlefs actually has to do more work to keep files in sync, the desync feature is quite cheap: code stack before: 33324 3072 after: 33360 (+0.1%) 3072 (+0.0%) --- lfs.c | 57 ++-- lfs.h | 32 +- tests/test_fmulti.toml | 709 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 699 insertions(+), 99 deletions(-) diff --git a/lfs.c b/lfs.c index e5b51692..d9da8319 100644 --- a/lfs.c +++ b/lfs.c @@ -9241,6 +9241,10 @@ static inline bool lfsr_o_isappend(uint32_t flags) { return flags & LFS_O_APPEND; } +static inline bool lfsr_o_isdesync(uint32_t flags) { + return flags & LFS_O_DESYNC; +} + static inline bool lfsr_f_isunflushed(uint32_t flags) { return flags & LFS_F_UNFLUSHED; } @@ -9249,10 +9253,6 @@ static inline bool lfsr_f_isunsynced(uint32_t flags) { return flags & LFS_F_UNSYNCED; } -static inline bool lfsr_f_iserrored(uint32_t flags) { - return flags & LFS_F_ERRORED; -} - // file operations // needed in lfsr_file_opencfg @@ -9457,7 +9457,15 @@ int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file, int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file); int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) { - int err = lfsr_file_sync(lfs, file); + // don't call lfsr_file_sync if we're readonly or desynced + int err = 0; + if (lfsr_o_iswriteable(file->flags) + && !lfsr_o_isdesync(file->flags)) { + err = lfsr_file_sync(lfs, file); + if (err) { + return err; + } + } // remove from tracked mdirs lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)file); @@ -10694,8 +10702,8 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file, failed:; // remove from tracked mdirs lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_); - // mark as errored so lfsr_file_close doesn't write to disk - file->flags |= LFS_F_ERRORED; + // mark as desync so lfsr_file_close doesn't write to disk + file->flags |= LFS_O_DESYNC; return err; } @@ -10755,17 +10763,12 @@ static int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) { failed:; // remove from tracked mdirs lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_); - // mark as errored so lfsr_file_close doesn't write to disk - file->flags |= LFS_F_ERRORED; + // mark as desync so lfsr_file_close doesn't write to disk + file->flags |= LFS_O_DESYNC; return err; } int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { - // it's not safe to do anything if our file errored - if (lfsr_f_iserrored(file->flags)) { - return 0; - } - // do nothing if our file has been removed if (file->ftree.mdir.mid == -1) { return 0; @@ -10774,11 +10777,15 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { // 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; } // do nothing if we're already in sync if (!lfsr_f_isunsynced(file->flags)) { + // but do clear desync flag + file->flags &= ~LFS_O_DESYNC; return 0; } @@ -10839,14 +10846,16 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { } // mark as synced - file->flags &= ~LFS_F_UNSYNCED; + file->flags &= ~LFS_F_UNSYNCED & ~LFS_O_DESYNC; // update other file handles for (lfsr_openedmdir_t *opened = lfs->opened[ LFS_TYPE_REG-LFS_TYPE_REG]; opened; opened = opened->next) { lfsr_file_t *file_ = (lfsr_file_t*)opened; - if (file_->ftree.mdir.mid == file->ftree.mdir.mid) { + if (file_->ftree.mdir.mid == file->ftree.mdir.mid + // don't update desynced file handles + && !lfsr_o_isdesync(file_->flags)) { file_->size = file->size; file_->ftree.u = file->ftree.u; file_->buffer_pos = file->buffer_pos; @@ -10858,10 +10867,16 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { return 0; failed:; - file->flags |= LFS_F_ERRORED; + file->flags |= LFS_O_DESYNC; return err; } +int lfsr_file_desync(lfs_t *lfs, lfsr_file_t *file) { + (void)lfs; + file->flags |= LFS_O_DESYNC; + return 0; +} + lfs_soff_t lfsr_file_seek(lfs_t *lfs, lfsr_file_t *file, lfs_soff_t off, uint8_t whence) { // TODO check for out-of-range? @@ -10997,8 +11012,8 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) { failed:; // remove from tracked mdirs lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_); - // mark as errored so lfsr_file_close doesn't write to disk - file->flags |= LFS_F_ERRORED; + // mark as desync so lfsr_file_close doesn't write to disk + file->flags |= LFS_O_DESYNC; return err; } @@ -11112,8 +11127,8 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size) { failed:; // remove from tracked mdirs lfsr_mdir_removeopened(lfs, LFS_TYPE_REG, (lfsr_openedmdir_t*)&ftree_); - // mark as errored so lfsr_file_close doesn't write to disk - file->flags |= LFS_F_ERRORED; + // 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 5307faf3..803f38e6 100644 --- a/lfs.h +++ b/lfs.h @@ -162,12 +162,12 @@ enum lfs_open_flags { LFS_O_EXCL = 0x0200, // Fail if a file already exists LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size LFS_O_APPEND = 0x0800, // Move to end of file on every write + LFS_O_DESYNC = 0x1000, // Do not sync or recieve file updates #endif // internally used flags LFS_F_UNFLUSHED = 0x010000, // File's data does not match storage LFS_F_UNSYNCED = 0x020000, // File's metadata does not match storage - LFS_F_ERRORED = 0x040000, // An error occurred during write }; // File seek flags @@ -778,8 +778,13 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, // Close a file // -// Any pending writes are written out to storage as though -// sync had been called and releases any allocated resources. +// If the file is not desynchronized, any pending writes are written out +// to storage as though sync had been called. +// +// Releases any allocated resources, even if there is an error. +// +// Readonly and desynchronized files do not touch disk and will always +// return 0. // // Returns a negative error code on failure. int lfs_file_close(lfs_t *lfs, lfs_file_t *file); @@ -787,11 +792,30 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file); // Synchronize a file on storage // -// Any pending writes are written out to storage. +// Any pending writes are written out to storage and other open files. +// +// If the file was desynchronized, it is now marked as synchronized. It will +// now recieve file updates and syncs on close. +// // Returns a negative error code on failure. int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file); +// Mark a file as desynchronized +// +// Desynchronized files do not recieve file updates and do not sync on close. +// They effectively act as snapshots of the underlying file at that point +// in time. +// +// If an error occurs during a write operation, the file is implicitly marked +// as desynchronized. +// +// An explicit and successful call to lfsr_file_sync reverses this, marking +// the file as synchronized again. +// +// Returns a negative error code on failure. +int lfsr_file_desync(lfs_t *lfs, lfsr_file_t *file); + // Read data from file // // Takes a buffer and size indicating where to store the read data. diff --git a/tests/test_fmulti.toml b/tests/test_fmulti.toml index 1cbca13a..97cfebef 100644 --- a/tests/test_fmulti.toml +++ b/tests/test_fmulti.toml @@ -1,6 +1,7 @@ # Test multiple open file handles in different r/w configurations after = 'test_fwrite' + # Test multiple readers, this shouldn't really have any issues [cases.test_fmulti_rrrr] defines.R = 4 @@ -161,7 +162,7 @@ code = ''' } lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK; memcpy(&after[i], wbuf, CHUNK); - if (SYNC == 1) { + if (SYNC != 0) { lfsr_file_sync(&lfs, &writer) => 0; } @@ -184,7 +185,7 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; @@ -248,7 +249,7 @@ code = ''' } lfsr_file_write(&lfs, &writer, wbuf, size) => size; memcpy(&after[off], wbuf, size); - if (SYNC == 1) { + if (SYNC != 0) { lfsr_file_sync(&lfs, &writer) => 0; } @@ -277,7 +278,7 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; @@ -332,13 +333,13 @@ code = ''' wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &writers[w], wbuf, CHUNK) => CHUNK; - if (SYNC == 1) { - lfsr_file_sync(&lfs, &writers[w]) => 0; - memcpy(&after[i], wbuf, CHUNK); - } else { + if (SYNC == 0) { if (w == 0) { memcpy(&after[i], wbuf, CHUNK); } + } else { + lfsr_file_sync(&lfs, &writers[w]) => 0; + memcpy(&after[i], wbuf, CHUNK); } } } @@ -350,7 +351,7 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; @@ -412,13 +413,13 @@ code = ''' wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &writers[w], wbuf, size) => size; - if (SYNC == 1) { - lfsr_file_sync(&lfs, &writers[w]) => 0; - memcpy(&after[off], wbuf, size); - } else { + if (SYNC == 0) { if (w == 0) { memcpy(&after[off], wbuf, size); } + } else { + lfsr_file_sync(&lfs, &writers[w]) => 0; + memcpy(&after[off], wbuf, size); } } } @@ -430,7 +431,7 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; @@ -490,13 +491,13 @@ code = ''' wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &writers[w], wbuf, CHUNK) => CHUNK; - if (SYNC == 1) { - lfsr_file_sync(&lfs, &writers[w]) => 0; - memcpy(&after[i], wbuf, CHUNK); - } else { + if (SYNC == 0) { if (w == 0) { memcpy(&after[i], wbuf, CHUNK); } + } else { + lfsr_file_sync(&lfs, &writers[w]) => 0; + memcpy(&after[i], wbuf, CHUNK); } } for (lfs_size_t r = 0; r < R; r++) { @@ -520,7 +521,7 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; @@ -587,13 +588,13 @@ code = ''' wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &writers[w], wbuf, size) => size; - if (SYNC == 1) { - lfsr_file_sync(&lfs, &writers[w]) => 0; - memcpy(&after[off], wbuf, size); - } else { + if (SYNC == 0) { if (w == 0) { memcpy(&after[off], wbuf, size); } + } else { + lfsr_file_sync(&lfs, &writers[w]) => 0; + memcpy(&after[off], wbuf, size); } } for (lfs_size_t r = 0; r < R; r++) { @@ -623,15 +624,15 @@ code = ''' lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' # Test multiple rd/wrers -[cases.test_fmulti_mmmm] -defines.M = 4 +[cases.test_fmulti_rwrw] +defines.RW = 4 # 0 => no sync, readers not updated # 1 => sync via lfsr_file_sync defines.SYNC = [0, 1] @@ -657,9 +658,9 @@ code = ''' for (lfs_size_t i = 0; i < SIZE; i++) { before[i] = 'a' + (TEST_PRNG(&prng) % 26); } - uint8_t between[M][SIZE]; - for (lfs_size_t m = 0; m < M; m++) { - memcpy(between[M], before, SIZE); + uint8_t between[RW][SIZE]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + memcpy(between[rw], before, SIZE); } uint8_t after[SIZE]; memcpy(after, before, SIZE); @@ -670,56 +671,56 @@ code = ''' lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; - // write M rdwrs in parallel - lfsr_file_t rdwrs[M]; - for (lfs_size_t m = 0; m < M; m++) { - lfsr_file_open(&lfs, &rdwrs[m], "jello", LFS_O_RDWR) => 0; + // write RW rdwrs in parallel + lfsr_file_t rdwrs[RW]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_open(&lfs, &rdwrs[rw], "jello", LFS_O_RDWR) => 0; } for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { - for (lfs_size_t m = 0; m < M; m++) { + for (lfs_size_t rw = 0; rw < RW; rw++) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } - lfsr_file_seek(&lfs, &rdwrs[m], i, LFS_SEEK_SET) => i; - lfsr_file_write(&lfs, &rdwrs[m], wbuf, CHUNK) => CHUNK; - memcpy(&between[m][i], wbuf, CHUNK); - if (SYNC == 1) { - lfsr_file_sync(&lfs, &rdwrs[m]) => 0; - memcpy(&after[i], wbuf, CHUNK); - } else { - if (m == 0) { + lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i; + lfsr_file_write(&lfs, &rdwrs[rw], wbuf, CHUNK) => CHUNK; + memcpy(&between[rw][i], wbuf, CHUNK); + if (SYNC == 0) { + if (rw == 0) { memcpy(&after[i], wbuf, CHUNK); } + } else { + lfsr_file_sync(&lfs, &rdwrs[rw]) => 0; + memcpy(&after[i], wbuf, CHUNK); } } - for (lfs_size_t m = 0; m < M; m++) { - lfsr_file_seek(&lfs, &rdwrs[m], i, LFS_SEEK_SET) => i; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i; uint8_t rbuf[CHUNK]; - lfsr_file_read(&lfs, &rdwrs[m], rbuf, CHUNK) => CHUNK; + lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => CHUNK; if (SYNC == 0) { - assert(memcmp(rbuf, &between[m][i], CHUNK) == 0); + assert(memcmp(rbuf, &between[rw][i], CHUNK) == 0); } else { assert(memcmp(rbuf, &after[i], CHUNK) == 0); } } } - for (lfs_size_t m = 0; m < M; m++) { - lfsr_file_close(&lfs, &rdwrs[m]) => 0; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_close(&lfs, &rdwrs[rw]) => 0; } // check that file was written as expected lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' -[cases.test_fmulti_mmmm_fuzz] -defines.M = 4 +[cases.test_fmulti_rwrw_fuzz] +defines.RW = 4 # 0 => no sync, readers not updated # 1 => sync via lfsr_file_sync defines.SYNC = [0, 1] @@ -747,9 +748,9 @@ code = ''' for (lfs_size_t i = 0; i < SIZE; i++) { before[i] = 'a' + (TEST_PRNG(&prng) % 26); } - uint8_t between[M][SIZE]; - for (lfs_size_t m = 0; m < M; m++) { - memcpy(between[m], before, SIZE); + uint8_t between[RW][SIZE]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + memcpy(between[rw], before, SIZE); } uint8_t after[SIZE]; memcpy(after, before, SIZE); @@ -760,59 +761,619 @@ code = ''' lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; - // write M rdwrs in parallel - lfsr_file_t rdwrs[M]; - for (lfs_size_t m = 0; m < M; m++) { - lfsr_file_open(&lfs, &rdwrs[m], "jello", LFS_O_RDWR) => 0; + // write RW rdwrs in parallel + lfsr_file_t rdwrs[RW]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_open(&lfs, &rdwrs[rw], "jello", LFS_O_RDWR) => 0; } for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { - for (lfs_size_t m = 0; m < M; m++) { + for (lfs_size_t rw = 0; rw < RW; rw++) { // choose a random offset lfs_off_t off = TEST_PRNG(&prng) % SIZE; lfs_size_t size = lfs_min32(CHUNK, SIZE - off); - lfsr_file_seek(&lfs, &rdwrs[m], off, LFS_SEEK_SET) => off; + lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off; // write uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < size; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } - lfsr_file_write(&lfs, &rdwrs[m], wbuf, size) => size; - memcpy(&between[m][off], wbuf, size); - if (SYNC == 1) { - lfsr_file_sync(&lfs, &rdwrs[m]) => 0; - memcpy(&after[off], wbuf, size); - } else { - if (m == 0) { + lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size; + memcpy(&between[rw][off], wbuf, size); + if (SYNC == 0) { + if (rw == 0) { memcpy(&after[off], wbuf, size); } + } else { + lfsr_file_sync(&lfs, &rdwrs[rw]) => 0; + memcpy(&after[off], wbuf, size); } } - for (lfs_size_t m = 0; m < M; m++) { + for (lfs_size_t rw = 0; rw < RW; rw++) { // choose a random offset lfs_off_t off = TEST_PRNG(&prng) % SIZE; lfs_size_t size = lfs_min32(CHUNK, SIZE - off); - lfsr_file_seek(&lfs, &rdwrs[m], off, LFS_SEEK_SET) => off; + lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off; // read uint8_t rbuf[CHUNK]; - lfsr_file_read(&lfs, &rdwrs[m], rbuf, CHUNK) => size; + lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size; if (SYNC == 0) { - assert(memcmp(rbuf, &between[m][off], size) == 0); + assert(memcmp(rbuf, &between[rw][off], size) == 0); } else { assert(memcmp(rbuf, &after[off], size) == 0); } } } - for (lfs_size_t m = 0; m < M; m++) { - lfsr_file_close(&lfs, &rdwrs[m]) => 0; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_close(&lfs, &rdwrs[rw]) => 0; } // check that file was written as expected lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; - assert(memcmp(rbuf, &after, SIZE) == 0); + assert(memcmp(rbuf, after, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + + +# Desynced files make things interesting + +# Test one desynced writer, multiple readers +[cases.test_fmulti_drrr] +defines.R = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = [32, 8, 1] +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write 1 handle, read R handles in parallel + lfsr_file_t writer; + lfsr_file_t readers[R]; + lfsr_file_open(&lfs, &writer, "jello", + LFS_O_WRONLY | LFS_O_DESYNC) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0; + } + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK; + memcpy(&after[i], wbuf, CHUNK); + if (SYNC != 0) { + lfsr_file_sync(&lfs, &writer) => 0; + } + + for (lfs_size_t r = 0; r < R; r++) { + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK; + if (SYNC == 0) { + assert(memcmp(rbuf, &before[i], CHUNK) == 0); + } else { + assert(memcmp(rbuf, &after[i], CHUNK) == 0); + } + } + } + lfsr_file_close(&lfs, &writer) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_close(&lfs, &readers[r]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + if (SYNC == 0) { + assert(memcmp(rbuf, before, SIZE) == 0); + } else { + assert(memcmp(rbuf, after, SIZE) == 0); + } + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fmulti_drrr_fuzz] +defines.R = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SEED = 'range(10)' +defines.N = 20 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = [32, 8, 1] +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write 1 handle, read R handles in parallel + lfsr_file_t writer; + lfsr_file_t readers[R]; + lfsr_file_open(&lfs, &writer, "jello", + LFS_O_WRONLY | LFS_O_DESYNC) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_open(&lfs, &readers[r], "jello", LFS_O_RDONLY) => 0; + } + for (lfs_size_t i = 0; i < N; i++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &writer, off, LFS_SEEK_SET) => off; + + // write + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &writer, wbuf, size) => size; + memcpy(&after[off], wbuf, size); + if (SYNC != 0) { + lfsr_file_sync(&lfs, &writer) => 0; + } + + for (lfs_size_t r = 0; r < R; r++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off; + + // read + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size; + if (SYNC == 0) { + assert(memcmp(rbuf, &before[off], size) == 0); + } else { + assert(memcmp(rbuf, &after[off], size) == 0); + } + } + } + lfsr_file_close(&lfs, &writer) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_close(&lfs, &readers[r]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + if (SYNC == 0) { + assert(memcmp(rbuf, before, SIZE) == 0); + } else { + assert(memcmp(rbuf, after, SIZE) == 0); + } + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# Test one writer, multiple desynced readers +[cases.test_fmulti_wddd] +defines.R = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = [32, 8, 1] +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write 1 handle, read R handles in parallel + lfsr_file_t writer; + lfsr_file_t readers[R]; + lfsr_file_open(&lfs, &writer, "jello", LFS_O_WRONLY) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_open(&lfs, &readers[r], "jello", + LFS_O_RDONLY | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &writer, wbuf, CHUNK) => CHUNK; + memcpy(&after[i], wbuf, CHUNK); + if (SYNC != 0) { + lfsr_file_sync(&lfs, &writer) => 0; + } + + for (lfs_size_t r = 0; r < R; r++) { + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => CHUNK; + assert(memcmp(rbuf, &before[i], CHUNK) == 0); + } + } + lfsr_file_close(&lfs, &writer) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_close(&lfs, &readers[r]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, after, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fmulti_wddd_fuzz] +defines.R = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SEED = 'range(10)' +defines.N = 20 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = [32, 8, 1] +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write 1 handle, read R handles in parallel + lfsr_file_t writer; + lfsr_file_t readers[R]; + lfsr_file_open(&lfs, &writer, "jello", LFS_O_WRONLY) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_open(&lfs, &readers[r], "jello", + LFS_O_RDONLY | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < N; i++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &writer, off, LFS_SEEK_SET) => off; + + // write + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &writer, wbuf, size) => size; + memcpy(&after[off], wbuf, size); + if (SYNC != 0) { + lfsr_file_sync(&lfs, &writer) => 0; + } + + for (lfs_size_t r = 0; r < R; r++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &readers[r], off, LFS_SEEK_SET) => off; + + // read + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &readers[r], rbuf, CHUNK) => size; + assert(memcmp(rbuf, &before[off], size) == 0); + } + } + lfsr_file_close(&lfs, &writer) => 0; + for (lfs_size_t r = 0; r < R; r++) { + lfsr_file_close(&lfs, &readers[r]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + assert(memcmp(rbuf, after, SIZE) == 0); + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +# Test multiple desynced rd/wrers +[cases.test_fmulti_rdrd] +defines.RW = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = [32, 8, 1] +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t between[RW][SIZE]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + memcpy(between[rw], before, SIZE); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write RW rdwrs in parallel + lfsr_file_t rdwrs[RW]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_open(&lfs, &rdwrs[rw], "jello", + LFS_O_RDWR | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + for (lfs_size_t rw = 0; rw < RW; rw++) { + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < CHUNK; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i; + lfsr_file_write(&lfs, &rdwrs[rw], wbuf, CHUNK) => CHUNK; + memcpy(&between[rw][i], wbuf, CHUNK); + if (SYNC == 0) { + if (rw == 0) { + memcpy(&after[i], wbuf, CHUNK); + } + } else { + lfsr_file_sync(&lfs, &rdwrs[rw]) => 0; + if (i == 0) { + memcpy(after, between[rw], SIZE); + } else { + memcpy(&after[i], wbuf, CHUNK); + } + } + } + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_seek(&lfs, &rdwrs[rw], i, LFS_SEEK_SET) => i; + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => CHUNK; + if (SYNC == 0) { + assert(memcmp(rbuf, &between[rw][i], CHUNK) == 0); + } else { + assert(memcmp(rbuf, &after[i], CHUNK) == 0); + } + } + } + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_close(&lfs, &rdwrs[rw]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + if (SYNC == 0) { + assert(memcmp(rbuf, before, SIZE) == 0); + } else { + assert(memcmp(rbuf, after, SIZE) == 0); + } + lfsr_file_close(&lfs, &file) => 0; + + lfsr_unmount(&lfs) => 0; +''' + +[cases.test_fmulti_rdrd_fuzz] +defines.RW = 4 +# 0 => no sync, readers not updated +# 1 => sync via lfsr_file_sync +defines.SYNC = [0, 1] +defines.SEED = 'range(10)' +defines.N = 20 +defines.SIZE = [ + '0', + 'CACHE_SIZE/2', + '2*CACHE_SIZE', + 'BLOCK_SIZE/2', + 'BLOCK_SIZE', + '2*BLOCK_SIZE', + '4*BLOCK_SIZE', +] +defines.CHUNK = 32 +if = 'CHUNK <= SIZE' +code = ''' + lfs_t lfs; + lfsr_format(&lfs, CFG) => 0; + lfsr_mount(&lfs, CFG) => 0; + + // create a file + uint32_t prng = 42; + uint8_t before[SIZE]; + for (lfs_size_t i = 0; i < SIZE; i++) { + before[i] = 'a' + (TEST_PRNG(&prng) % 26); + } + uint8_t between[RW][SIZE]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + memcpy(between[rw], before, SIZE); + } + uint8_t after[SIZE]; + memcpy(after, before, SIZE); + + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "jello", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + lfsr_file_write(&lfs, &file, before, SIZE) => SIZE; + lfsr_file_close(&lfs, &file) => 0; + + // write RW rdwrs in parallel + lfsr_file_t rdwrs[RW]; + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_open(&lfs, &rdwrs[rw], "jello", + LFS_O_RDWR | LFS_O_DESYNC) => 0; + } + for (lfs_size_t i = 0; i < SIZE; i += CHUNK) { + for (lfs_size_t rw = 0; rw < RW; rw++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off; + + // write + uint8_t wbuf[CHUNK]; + for (lfs_size_t j = 0; j < size; j++) { + wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); + } + lfsr_file_write(&lfs, &rdwrs[rw], wbuf, size) => size; + memcpy(&between[rw][off], wbuf, size); + if (SYNC == 0) { + if (rw == 0) { + memcpy(&after[off], wbuf, size); + } + } else { + lfsr_file_sync(&lfs, &rdwrs[rw]) => 0; + if (i == 0) { + memcpy(after, between[rw], SIZE); + } else { + memcpy(&after[off], wbuf, size); + } + } + } + for (lfs_size_t rw = 0; rw < RW; rw++) { + // choose a random offset + lfs_off_t off = TEST_PRNG(&prng) % SIZE; + lfs_size_t size = lfs_min32(CHUNK, SIZE - off); + lfsr_file_seek(&lfs, &rdwrs[rw], off, LFS_SEEK_SET) => off; + + // read + uint8_t rbuf[CHUNK]; + lfsr_file_read(&lfs, &rdwrs[rw], rbuf, CHUNK) => size; + if (SYNC == 0) { + assert(memcmp(rbuf, &between[rw][off], size) == 0); + } else { + assert(memcmp(rbuf, &after[off], size) == 0); + } + } + } + for (lfs_size_t rw = 0; rw < RW; rw++) { + lfsr_file_close(&lfs, &rdwrs[rw]) => 0; + } + + // check that file was written as expected + lfsr_file_open(&lfs, &file, "jello", LFS_O_RDONLY) => 0; + uint8_t rbuf[SIZE]; + lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; + if (SYNC == 0) { + assert(memcmp(rbuf, before, SIZE) == 0); + } else { + assert(memcmp(rbuf, after, SIZE) == 0); + } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0;