From de7564e4481b0e58e0015e4e06a9903bfbd9411f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 29 Apr 2025 17:31:12 -0500 Subject: [PATCH] Added phase bits to cksum tags This carves out two more bits in cksum tags to store the "phase" of the rbyd block (maybe the name is too fancy, this is just the lowest 2 bits of the block address): LFSR_TAG_CKSUM 0x300p v-11 ---- ---- -pqq ^ ^ | '-- phase bits '---- perturb bit The intention here is to catch mrootanchors that are "out-of-phase", i.e. they've been shifted by a small number of blocks. This can happen if we find the wrong mrootanchor (after, say, a magic scan), and risks filesystem corruption: formatted .-----------------'-----------------. mounted .-----------------'-----------------. .--------+--------+--------+--------+ ... |(erased)| mroot | | | anchor | ... | | | '--------+--------+--------+--------+ ... Including the lower 2 bits of the block address in cksum tags avoids this, for up to a 3 block shift (the maximum number of redund mrootanchors). --- Note that cksum tags really are the only place we could put these bits. Anywhere else and they would interfere with the canonical cksum, which would break error correction. By definition these need to be different per block. We include these phase bits in every cksum tag (because it's easier), but these don't really say much about mdirs that are not the mrootanchor. Non-anchor mdirs can have arbitrary block addresses, therefore arbitrary phase bits. You _might_ be able to do something interesting if you sort the rbyd addresses and use the index as the phase bits, but that would add quite a bit of code for questionable benefit... You could argue this adds noise to our cksums, but: 1. 2 bits seems like a really small amount of noise 2. our cksums are just crc32cs 3. the phase bits humorously never change when you rewrite a block --- As with any feature this adds code, but only a small amount. I think it's worth the extra protection: code stack ctx before: 35792 2368 636 after: 35824 (+0.1%) 2368 (+0.0%) 636 (+0.0%) Also added test_mount_incompat_out_of_phase to test this. The dbg scripts _don't_ error (block mismatch seems likely when debugging), but dbgrbyd.py at least adds phase mismatch notes in -l/--log mode. --- lfs.c | 28 +++++++++++++++++------ scripts/dbgbmap.py | 16 +++++++------ scripts/dbgbmapd3.py | 16 +++++++------ scripts/dbgbtree.py | 16 +++++++------ scripts/dbglfs.py | 16 +++++++------ scripts/dbgmtree.py | 16 +++++++------ scripts/dbgrbyd.py | 23 ++++++++++++------- scripts/dbgtag.py | 14 +++++++----- tests/test_mount.toml | 52 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 141 insertions(+), 56 deletions(-) diff --git a/lfs.c b/lfs.c index 7076ffe6..527390d6 100644 --- a/lfs.c +++ b/lfs.c @@ -1154,7 +1154,8 @@ enum lfsr_tag { // checksum tags LFSR_TAG_CKSUM = 0x3000, - LFSR_TAG_P = 0x0001, + LFSR_TAG_PHASE = 0x0003, + LFSR_TAG_PERTURB = 0x0004, LFSR_TAG_NOTE = 0x3100, LFSR_TAG_ECKSUM = 0x3200, LFSR_TAG_GCKSUMDELTA = 0x3300, @@ -1213,7 +1214,7 @@ static inline lfsr_tag_t lfsr_tag_subkey(lfsr_tag_t tag) { return tag & 0x00ff; } -static inline lfsr_tag_t lfsr_tag_redund(lfsr_tag_t tag) { +static inline uint8_t lfsr_tag_redund(lfsr_tag_t tag) { return tag & 0x0003; } @@ -1229,8 +1230,12 @@ static inline bool lfsr_tag_istrunk(lfsr_tag_t tag) { return lfsr_tag_mode(tag) != LFSR_TAG_CKSUM; } -static inline bool lfsr_tag_p(lfsr_tag_t tag) { - return tag & LFSR_TAG_P; +static inline uint8_t lfsr_tag_phase(lfsr_tag_t tag) { + return tag & LFSR_TAG_PHASE; +} + +static inline bool lfsr_tag_perturb(lfsr_tag_t tag) { + return tag & LFSR_TAG_PERTURB; } static inline bool lfsr_tag_isinternal(lfsr_tag_t tag) { @@ -3013,11 +3018,17 @@ static int lfsr_rbyd_fetch_(lfs_t *lfs, // is an end-of-commit cksum } else { - // truncate checksum? + // truncated checksum? if (size < sizeof(uint32_t)) { break; } + // check phase + if (lfsr_tag_phase(tag) != (block & 0x3)) { + // uh oh, phase doesn't match, mounted incorrectly? + break; + } + // check checksum uint32_t cksum__ = 0; err = lfsr_bd_read(lfs, block, off_, -1, @@ -3037,7 +3048,7 @@ static int lfsr_rbyd_fetch_(lfs_t *lfs, // save what we've found so far rbyd->eoff - = ((lfs_size_t)lfsr_tag_p(tag) + = ((lfs_size_t)lfsr_tag_perturb(tag) << (8*sizeof(lfs_size_t)-1)) | (off_ + size); rbyd->cksum = cksum; @@ -4488,7 +4499,10 @@ static int lfsr_rbyd_appendcksum_(lfs_t *lfs, lfsr_rbyd_t *rbyd, | ((uint8_t)v << 7); cksum_buf[1] = (uint8_t)(LFSR_TAG_CKSUM >> 0) // set the perturb bit so next commit is invalid - | ((uint8_t)perturb << 0); + | ((uint8_t)perturb << 2) + // include the lower 2 bits of the block address to help + // with resynchronization + | (rbyd->blocks[0] & 0x3); cksum_buf[2] = 0; lfs_size_t padding = off_ - (lfsr_rbyd_eoff(rbyd) + 2+1+4); diff --git a/scripts/dbgbmap.py b/scripts/dbgbmap.py index adf3aab3..21d1eb6d 100755 --- a/scripts/dbgbmap.py +++ b/scripts/dbgbmap.py @@ -67,8 +67,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -346,7 +347,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -362,9 +363,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -684,7 +686,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) diff --git a/scripts/dbgbmapd3.py b/scripts/dbgbmapd3.py index 7b18fbe7..10e3bc7d 100755 --- a/scripts/dbgbmapd3.py +++ b/scripts/dbgbmapd3.py @@ -65,8 +65,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -376,7 +377,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -392,9 +393,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -714,7 +716,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 99660d30..80b0da5d 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -56,8 +56,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -254,7 +255,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -270,9 +271,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -592,7 +594,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index 91ca6ce1..c8c58320 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -57,8 +57,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -303,7 +304,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -319,9 +320,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -641,7 +643,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index f4499e1d..248cc5d5 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -56,8 +56,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -269,7 +270,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -285,9 +286,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -607,7 +609,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index e7379c3b..4c295989 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -66,8 +66,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -257,7 +258,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -273,9 +274,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags @@ -575,7 +577,7 @@ class Rbyd: gcksumdelta = gcksumdelta_ gcksumdelta_ = None # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum__ = cksum_ ^ (0xfca42daf if perturb else 0) @@ -1548,6 +1550,7 @@ def dbg_log(rbyd, *, # read next tag j = j_ v, tag, w, size, d = fromtag(data, j_) + # note if parity doesn't match if v != parity(cksum_): notes.append('v!=%x' % parity(cksum_)) cksum_ ^= 0x00000080 if v else 0 @@ -1560,12 +1563,16 @@ def dbg_log(rbyd, *, cksum_ = crc32c(data[j_:j_+size], cksum_) # found a cksum? else: + # note if phase doesn't match + if (tag & 0x3) != (rbyd.block & 0x3): + notes.append('q!=%x' % (rbyd.block & 0x3)) # check cksum cksum__ = fromle32(data, j_) + # note if cksum doesn't match if cksum_ != cksum__: notes.append('cksum!=%08x' % cksum__) # update perturb bit - perturb = tag & TAG_P + perturb = bool(tag & TAG_PERTURB) # revert to data cksum and perturb cksum_ = cksum ^ (0xfca42daf if perturb else 0) j_ += size diff --git a/scripts/dbgtag.py b/scripts/dbgtag.py index 37e10207..36953d46 100755 --- a/scripts/dbgtag.py +++ b/scripts/dbgtag.py @@ -49,8 +49,9 @@ TAG_B = 0x0000 TAG_R = 0x2000 TAG_LE = 0x0000 TAG_GT = 0x1000 -TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- ---p -TAG_P = 0x0001 +TAG_CKSUM = 0x3000 ## 0x300p v-11 ---- ---- -pqq +TAG_PHASE = 0x0003 +TAG_PERTURB = 0x0004 TAG_NOTE = 0x3100 ## 0x3100 v-11 ---1 ---- ---- TAG_ECKSUM = 0x3200 ## 0x3200 v-11 --1- ---- ---- TAG_GCKSUMDELTA = 0x3300 ## 0x3300 v-11 --11 ---- ---- @@ -161,7 +162,7 @@ def tagrepr(tag, weight=None, size=None, *, return '%s%sattr 0x%02x%s%s' % ( 'shrub' if tag & TAG_SHRUB else '', 's' if tag & 0x100 else 'u', - ((tag & 0x100) >> 1) ^ (tag & 0xff), + ((tag & 0x100) >> 1) + (tag & 0xff), ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # alt pointers @@ -177,9 +178,10 @@ def tagrepr(tag, weight=None, size=None, *, else '') # checksum tags elif (tag & 0x7f00) == TAG_CKSUM: - return 'cksum%s%s%s%s' % ( - 'p' if not tag & 0xfe and tag & TAG_P else '', - ' 0x%02x' % (tag & 0xff) if tag & 0xfe else '', + return 'cksum%s%s%s%s%s' % ( + 'q%d' % (tag & 0x3), + 'p' if tag & TAG_PERTURB else '', + ' 0x%02x' % (tag & 0xff) if tag & 0xf8 else '', ' w%d' % weight if weight else '', ' %s' % size if size is not None else '') # note tags diff --git a/tests/test_mount.toml b/tests/test_mount.toml index 01a68692..348d734e 100644 --- a/tests/test_mount.toml +++ b/tests/test_mount.toml @@ -1963,3 +1963,55 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' + + +# Ok, here's an interesting one, test that we fail if we're +# "out-of-phase", i.e. the mrootanchor has been shifted by a small +# number of blocks. +# +# This can happen if we find the wrong mrootanchor (after, say, a magic +# scan), and risks filesystem corruption. To prevent this, we include 2 +# phase bits in cksum tags to detect up to a 3 block shift (the maximum +# number of redund mrootanchors) +# +[cases.test_mount_incompat_out_of_phase] +defines.PHASE = [1, 2, 3, 4] +in = 'lfs.c' +code = ''' + // create a superblock + lfs_t lfs; + lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; + + // with some files + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; + lfsr_file_t file; + lfsr_file_open(&lfs, &file, "r2d2", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + char wbuf[256]; + strcpy(wbuf, "beep boop"); + lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf); + lfsr_file_close(&lfs, &file) => 0; + lfsr_file_open(&lfs, &file, "c3po", + LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; + strcpy(wbuf, "we seem to be made to suffer"); + lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf); + lfsr_file_close(&lfs, &file) => 0; + + // shift the filesystem out-of-phase + uint8_t shift_buf[BLOCK_SIZE]; + for (lfs_size_t i = 0; i < 4; i++) { + CFG->read(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 0; + CFG->erase(CFG, 4-1-i + PHASE) => 0; + CFG->prog(CFG, 4-1-i + PHASE, 0, shift_buf, BLOCK_SIZE) => 0; + + memset(shift_buf, 0, BLOCK_SIZE); + strcpy((char*)shift_buf, + "these aren't the files you're looking for ;)"); + CFG->erase(CFG, 4-1-i) => 0; + CFG->prog(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 0; + } + + // mount should now fail + lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT; + lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT; +'''