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; +'''