Limited ckcksums to check data cksums
So... Long store short, checking metadata cksums is just intractably
slow.
But data cksums?
Yes checking data cksums is still O(b^2), but unlike metadata lookups,
which involve many small backwards reads, data reads are very easy to
cache. So instead of O(b^2), it's more like O(b^2/c), where c is your
rcache size.
Still O(b^2) when c << b, but I'm not sure that's avoidable without
adding more cksums.
At the very least, if you have enough RAM, c == b reduces this to O(b),
which is nice for "large" systems that want hardened reads without a
performance loss.
---
But why bother checking data cksums if we still have a read-hole with
metadata cksums?
Well, while considering the problem in the context of future features, I
noticed something _really interesting_:
- ckredund + metadata - reasonable ✓
- ckredund + data - impractical ✗, parity fanout + O(f+r) is bad
- ckcksums + metadata - impractical ✗, small reads + O(b^2) is bad
- ckcksums + data - reasonable ✓, assuming enough rcache
The current planned design for data redundancy makes it also intractably
slow to check every read, since it would require xoring all blocks that
contribute to the relevant parity block, but this isn't a problem for
metadata redundancy.
So while neither ckredund nor ckcksums can tractably close the read-hole
on their own, it looks like together they will be able to cover
everything without completely sacrificing performance. Neat!
Of course this isn't possible if ckcksums/ckredund imply checking both
metadata and data, so they need to be split apart.
And I don't really see a point in keeping the intractable variants
around in the codebase.
---
Dropping metadata ckcksums also means we can get rid of the ugly
lfsr_bd_ckrbydprefix and lfsr_bd_ckrbydsuffix functions, which were
basically duplicating all of lfsr_rbyd_fetch. That was quite a wart!
This saves a nice chunk of code when ckcksums is enabled:
code stack ctx
default before: 38128 2624 752
default after: 38128 (+0.0%) 2624 (+0.0%) 752 (+0.0%)
ckparity before: 39724 3048 764
ckparity after: 39700 (-0.1%) 3048 (+0.0%) 760 (-0.5%)
ckcksums before: 40612 3184 772
ckcksums after: 39396 (-3.0%) 3096 (-2.8%) 760 (-1.6%)
This commit is contained in:
@@ -767,11 +767,23 @@ static inline lfs_size_t lfsr_ck_ckoff(lfsr_ck_t ck) {
|
||||
// tailck tracks the most recent trunk's parity so we can parity-check
|
||||
// if it hasn't been written to disk yet
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
#define LFSR_TAILCK_PARITY 0x80000000
|
||||
#endif
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
static inline bool lfsr_tailck_parity(const lfsr_tailck_t *tailck) {
|
||||
return tailck->ckoff & LFSR_TAILCK_PARITY;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LFS_CKPARITY
|
||||
static inline lfs_size_t lfsr_tailck_ckoff(const lfsr_tailck_t *tailck) {
|
||||
return tailck->ckoff & ~LFSR_TAILCK_PARITY;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LFS_CKPARITY
|
||||
// read the pesky parity byte from disk
|
||||
static lfs_sbool_t lfsr_bd_readparity(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off, lfs_size_t hint) {
|
||||
@@ -783,12 +795,13 @@ static lfs_sbool_t lfsr_bd_readparity(lfs_t *lfs,
|
||||
//
|
||||
// unless we're in the middle of building a commit, where things get
|
||||
// tricky... to avoid problems with not-yet-written parity bits
|
||||
// tailck tracks the most recent trunk's cksum
|
||||
// tailck tracks the most recent trunk's parity
|
||||
//
|
||||
|
||||
// parity in in tailck?
|
||||
if (block == lfs->tailck.ckblock && off == lfs->tailck.ckoff) {
|
||||
return lfs_parity(lfs->tailck.cksum);
|
||||
if (block == lfs->tailck.ckblock
|
||||
&& off == lfsr_tailck_ckoff(&lfs->tailck)) {
|
||||
return lfsr_tailck_parity(&lfs->tailck);
|
||||
|
||||
// parity on disk?
|
||||
} else {
|
||||
@@ -807,20 +820,6 @@ static lfs_sbool_t lfsr_bd_readparity(lfs_t *lfs,
|
||||
|
||||
// checked read helpers
|
||||
|
||||
// needed in lfsr_bd_ckprefix
|
||||
#ifdef LFS_CKCKSUMS
|
||||
static inline bool lfsr_m_isckcksums(uint32_t flags);
|
||||
static int lfsr_bd_ckrbydprefix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off,
|
||||
lfsr_ck_t ck,
|
||||
lfs_size_t *hint_,
|
||||
uint32_t *cksum);
|
||||
static int lfsr_bd_ckrbydsuffix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off,
|
||||
lfsr_ck_t ck,
|
||||
uint32_t cksum);
|
||||
#endif
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
static int lfsr_bd_ckprefix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
|
||||
@@ -831,15 +830,6 @@ static int lfsr_bd_ckprefix(lfs_t *lfs,
|
||||
LFS_ASSERT(block < lfs->block_count);
|
||||
LFS_ASSERT(lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck) <= lfs->cfg->block_size);
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
// in rbyd? checking checksums? this gets a bit more complicated
|
||||
if (lfsr_m_isckcksums(lfs->flags) && lfsr_ck_isparity(ck)) {
|
||||
return lfsr_bd_ckrbydprefix(lfs, block, off, ck,
|
||||
hint_,
|
||||
cksum);
|
||||
}
|
||||
#endif
|
||||
|
||||
// make sure hint includes our prefix/suffix
|
||||
lfs_size_t hint__ = lfs_max(
|
||||
// watch out for overflow when hint=-1!
|
||||
@@ -864,6 +854,14 @@ static int lfsr_bd_ckprefix(lfs_t *lfs,
|
||||
}
|
||||
#endif
|
||||
|
||||
// needed in lfsr_bd_cksuffix
|
||||
#ifdef LFS_CKPARITY
|
||||
static inline bool lfsr_m_isckparity(uint32_t flags);
|
||||
#endif
|
||||
#ifdef LFS_CKCKSUMS
|
||||
static inline bool lfsr_m_isckcksums(uint32_t flags);
|
||||
#endif
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
static int lfsr_bd_cksuffix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
|
||||
@@ -873,21 +871,6 @@ static int lfsr_bd_cksuffix(lfs_t *lfs,
|
||||
LFS_ASSERT(block < lfs->block_count);
|
||||
LFS_ASSERT(lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck) <= lfs->cfg->block_size);
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
// in rbyd? checking checksums? this gets a bit more complicated
|
||||
if (lfsr_m_isckcksums(lfs->flags) && lfsr_ck_isparity(ck)) {
|
||||
int err = lfsr_bd_ckrbydsuffix(lfs, block, off, ck,
|
||||
cksum);
|
||||
if (err) {
|
||||
LFS_ERROR("Found ckcksums rbyd mismatch "
|
||||
"0x%"PRIx32".%"PRIx32" %"PRId32,
|
||||
block, lfsr_ck_ckoff(ck), lfsr_ck_cksize(ck));
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// checksum any suffixed data
|
||||
int err = lfsr_bd_cksum(lfs,
|
||||
block, off, hint,
|
||||
@@ -899,6 +882,7 @@ static int lfsr_bd_cksuffix(lfs_t *lfs,
|
||||
|
||||
if (lfsr_ck_isparity(ck)) {
|
||||
#ifdef LFS_CKPARITY
|
||||
LFS_ASSERT(lfsr_m_isckparity(lfs->flags));
|
||||
// does parity match?
|
||||
if (lfs_parity(cksum) != lfsr_ck_parity(ck)) {
|
||||
LFS_ERROR("Found ckparity mismatch "
|
||||
@@ -912,6 +896,7 @@ static int lfsr_bd_cksuffix(lfs_t *lfs,
|
||||
|
||||
} else {
|
||||
#ifdef LFS_CKCKSUMS
|
||||
LFS_ASSERT(lfsr_m_isckcksums(lfs->flags));
|
||||
// do checksums match?
|
||||
if (cksum != ck.u.cksum) {
|
||||
LFS_ERROR("Found ckcksums mismatch "
|
||||
@@ -980,11 +965,6 @@ static int lfsr_bd_readck_(lfs_t *lfs,
|
||||
}
|
||||
#endif
|
||||
|
||||
// needed in lfsr_bd_readck
|
||||
#ifdef LFS_CKPARITY
|
||||
static inline bool lfsr_m_isckparity(uint32_t flags);
|
||||
#endif
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
static int lfsr_bd_readck(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off, lfs_size_t hint,
|
||||
@@ -996,7 +976,8 @@ static int lfsr_bd_readck(lfs_t *lfs,
|
||||
&& lfsr_ck_isparity(ck),
|
||||
false)
|
||||
|| LFS_IFDEF_CKCKSUMS(
|
||||
lfsr_m_isckcksums(lfs->flags),
|
||||
lfsr_m_isckcksums(lfs->flags)
|
||||
&& lfsr_ck_iscksum(ck),
|
||||
false)) {
|
||||
return lfsr_bd_readck_(lfs,
|
||||
block, off, hint,
|
||||
@@ -1099,7 +1080,8 @@ static lfs_scmp_t lfsr_bd_cmpck(lfs_t *lfs,
|
||||
&& lfsr_ck_isparity(ck),
|
||||
false)
|
||||
|| LFS_IFDEF_CKCKSUMS(
|
||||
lfsr_m_isckcksums(lfs->flags),
|
||||
lfsr_m_isckcksums(lfs->flags)
|
||||
&& lfsr_ck_iscksum(ck),
|
||||
false)) {
|
||||
return lfsr_bd_cmpck_(lfs,
|
||||
block, off, hint,
|
||||
@@ -1209,7 +1191,8 @@ static int lfsr_bd_cpyck(lfs_t *lfs,
|
||||
&& lfsr_ck_isparity(ck),
|
||||
false)
|
||||
|| LFS_IFDEF_CKCKSUMS(
|
||||
lfsr_m_isckcksums(lfs->flags),
|
||||
lfsr_m_isckcksums(lfs->flags)
|
||||
&& lfsr_ck_iscksum(ck),
|
||||
false)) {
|
||||
return lfsr_bd_cpyck_(lfs,
|
||||
dst_block, dst_off,
|
||||
@@ -1704,22 +1687,6 @@ static lfs_ssize_t lfsr_bd_readtag(lfs_t *lfs,
|
||||
uint32_t *cksum) {
|
||||
(void)parity_;
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
// if we're checking checksums and not already calculating a
|
||||
// checksum, we first need to checksum any prefixed data
|
||||
uint32_t cksum_ = 0;
|
||||
if (lfsr_m_isckcksums(lfs->flags) && !cksum) {
|
||||
int err = lfsr_bd_ckprefix(lfs, block, off, hint,
|
||||
// the actual parity doesn't really matter here
|
||||
LFSR_CK_PARITY_(off, 0, 0),
|
||||
&hint,
|
||||
&cksum_);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// read the largest possible tag size
|
||||
uint8_t tag_buf[LFSR_TAG_DSIZE];
|
||||
lfs_size_t tag_dsize = lfs_min(LFSR_TAG_DSIZE, lfs->cfg->block_size-off);
|
||||
@@ -1842,29 +1809,6 @@ static lfs_ssize_t lfsr_bd_readtag(lfs_t *lfs,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
// if we're checking checksums and not already calculating a
|
||||
// checksum, checksum any suffixed data
|
||||
//
|
||||
// this uses the rbyd's checksum to check things
|
||||
if (lfsr_m_isckcksums(lfs->flags) && !cksum) {
|
||||
// exclude valid bit from checksum
|
||||
cksum_ ^= tag_buf[0] & 0x00000080;
|
||||
// calculate checksum
|
||||
cksum_ = lfs_crc32c(cksum_, tag_buf, d);
|
||||
|
||||
err = lfsr_bd_cksuffix(lfs, block, off+d, hint,
|
||||
// the actual parity doesn't really matter here
|
||||
LFSR_CK_PARITY_(off,
|
||||
d + ((!lfsr_tag_isalt(tag)) ? size : 0),
|
||||
0),
|
||||
cksum_);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// save what we found, clearing the valid bit, we don't need it
|
||||
// anymore
|
||||
*tag_ = tag & 0x7fff;
|
||||
@@ -3383,276 +3327,6 @@ static int lfsr_rbyd_suplookup(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
}
|
||||
|
||||
|
||||
// checked read rbyd helpers
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
// a weaker lfsr_bd_readtag that is just used for ckcksums
|
||||
//
|
||||
// we can't call lfsr_bd_readtag in lfsr_bd_ckrbydprefix/suffix or else
|
||||
// things would end up recursive
|
||||
static int lfsr_bd_ckrbydtag(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off,
|
||||
lfsr_tag_t *tag_, lfsr_rid_t *weight_, lfs_size_t *size_,
|
||||
uint32_t *cksum) {
|
||||
// read the largest possible tag size
|
||||
uint8_t tag_buf[LFSR_TAG_DSIZE];
|
||||
lfs_size_t tag_dsize = lfs_min(LFSR_TAG_DSIZE, lfs->cfg->block_size-off);
|
||||
if (tag_dsize < 4) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
int err = lfsr_bd_read(lfs, block, off, -1,
|
||||
tag_buf, tag_dsize);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// check the valid bit
|
||||
if ((tag_buf[0] >> 7) != lfs_parity(*cksum)) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
lfsr_tag_t tag
|
||||
= ((lfsr_tag_t)tag_buf[0] << 8)
|
||||
| ((lfsr_tag_t)tag_buf[1] << 0);
|
||||
lfs_ssize_t d = 2;
|
||||
|
||||
lfsr_rid_t weight;
|
||||
lfs_ssize_t d_ = lfs_fromleb128(&weight, &tag_buf[d], tag_dsize-d);
|
||||
if (d_ < 0) {
|
||||
return d_;
|
||||
}
|
||||
// weights should be limited to 31-bits
|
||||
if (weight > 0x7fffffff) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
d += d_;
|
||||
|
||||
lfs_size_t size;
|
||||
d_ = lfs_fromleb128(&size, &tag_buf[d], tag_dsize-d);
|
||||
if (d_ < 0) {
|
||||
return d_;
|
||||
}
|
||||
// sizes should be limited to 28-bits
|
||||
if (size > 0x0fffffff) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
d += d_;
|
||||
|
||||
// check our tag does not go out of bounds
|
||||
if (!lfsr_tag_isalt(tag) && off+d + size > lfs->cfg->block_size) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// exclude valid bit from checksum
|
||||
*cksum ^= tag_buf[0] & 0x00000080;
|
||||
// calculate checksum
|
||||
*cksum = lfs_crc32c(*cksum, tag_buf, d);
|
||||
|
||||
// save what we found, clearing the valid bit, we don't need it
|
||||
// anymore
|
||||
*tag_ = tag & 0x7fff;
|
||||
*weight_ = weight;
|
||||
*size_ = size;
|
||||
return d;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
static int lfsr_bd_ckrbydprefix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off,
|
||||
lfsr_ck_t ck,
|
||||
lfs_size_t *hint_,
|
||||
uint32_t *cksum) {
|
||||
// must be in-bounds
|
||||
LFS_ASSERT(block < lfs->block_count);
|
||||
LFS_ASSERT(lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck) <= lfs->cfg->block_size);
|
||||
// doesn't make sense to be here if not checking parity
|
||||
LFS_ASSERT(lfsr_ck_isparity(ck));
|
||||
|
||||
// this is basically just a small rbyd parser
|
||||
|
||||
// checksum the revision count
|
||||
uint32_t cksum_ = *cksum;
|
||||
int err = lfsr_bd_cksum(lfs, block, 0, -1, sizeof(uint32_t),
|
||||
&cksum_);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
uint32_t cksum__ = cksum_;
|
||||
bool perturb = false;
|
||||
lfs_size_t off_ = sizeof(uint32_t);
|
||||
while (off_ < off) {
|
||||
// read next tag
|
||||
lfsr_tag_t tag;
|
||||
lfsr_rid_t weight;
|
||||
lfs_size_t size;
|
||||
lfs_ssize_t d = lfsr_bd_ckrbydtag(lfs, block, off_,
|
||||
&tag, &weight, &size,
|
||||
&cksum__);
|
||||
if (d < 0) {
|
||||
return d;
|
||||
}
|
||||
lfs_size_t off__ = off_ + d;
|
||||
|
||||
// ckrbydtag should already check we're in-bounds
|
||||
LFS_ASSERT(lfsr_tag_isalt(tag)
|
||||
|| off__ + size <= lfs->cfg->block_size);
|
||||
|
||||
// take care of cksum
|
||||
if (!lfsr_tag_isalt(tag)) {
|
||||
// not an end-of-commit cksum
|
||||
if (lfsr_tag_suptype(tag) != LFSR_TAG_CKSUM) {
|
||||
// checksum the entry
|
||||
err = lfsr_bd_cksum(lfs, block, off__, -1,
|
||||
// stop early if we hit our data
|
||||
lfs_min(size, off-off__),
|
||||
&cksum__);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// is an end-of-commit cksum
|
||||
} else {
|
||||
// check checksum
|
||||
uint32_t cksum___ = 0;
|
||||
err = lfsr_bd_read(lfs, block, off__, -1,
|
||||
&cksum___, sizeof(uint32_t));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
cksum___ = lfs_fromle32_(&cksum___);
|
||||
|
||||
if (cksum__ != cksum___) {
|
||||
// uh oh, checksums don't match
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// revert to canonical checksum and perturb if necessary
|
||||
perturb = lfsr_tag_p(tag);
|
||||
cksum__ = cksum_ ^ ((perturb) ? LFS_CRC32C_ODDZERO : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// found a trunk?
|
||||
if (lfsr_tag_istrunk(tag)) {
|
||||
// update canonical checksum, xoring out any perturb
|
||||
// state
|
||||
cksum_ = cksum__ ^ ((perturb) ? LFS_CRC32C_ODDZERO : 0);
|
||||
}
|
||||
|
||||
// skip data
|
||||
if (!lfsr_tag_isalt(tag)) {
|
||||
off__ += size;
|
||||
}
|
||||
|
||||
off_ = off__;
|
||||
}
|
||||
|
||||
*hint_ = -1;
|
||||
*cksum = cksum__;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef LFS_CKCKSUMS
|
||||
static int lfsr_bd_ckrbydsuffix(lfs_t *lfs,
|
||||
lfs_block_t block, lfs_size_t off,
|
||||
lfsr_ck_t ck,
|
||||
uint32_t cksum) {
|
||||
// must be in-bounds
|
||||
LFS_ASSERT(block < lfs->block_count);
|
||||
LFS_ASSERT(lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck) <= lfs->cfg->block_size);
|
||||
// doesn't make sense to be here if not checking parity
|
||||
LFS_ASSERT(lfsr_ck_isparity(ck));
|
||||
|
||||
// checksum any suffixed data
|
||||
int err = lfsr_bd_cksum(lfs, block, off, -1,
|
||||
(lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck)) - off,
|
||||
&cksum);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_size_t off_ = lfsr_ck_ckoff(ck)+lfsr_ck_cksize(ck);
|
||||
while (off_ < lfs->cfg->block_size) {
|
||||
// wait did we hit our tailck? tailck provides a temporary
|
||||
// cksum if we're in the middle of building a commit
|
||||
if (block == lfs->tailck.ckblock && off_ == lfs->tailck.ckoff) {
|
||||
if (cksum != lfs->tailck.cksum) {
|
||||
// uh oh, checksums don't match
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// if we get here we've checked the commit containing
|
||||
// our data
|
||||
return 0;
|
||||
}
|
||||
|
||||
// read next tag
|
||||
lfsr_tag_t tag;
|
||||
lfsr_rid_t weight;
|
||||
lfs_size_t size;
|
||||
lfs_ssize_t d = lfsr_bd_ckrbydtag(lfs, block, off_,
|
||||
&tag, &weight, &size,
|
||||
&cksum);
|
||||
if (d < 0) {
|
||||
return d;
|
||||
}
|
||||
lfs_size_t off__ = off_ + d;
|
||||
|
||||
// ckrbydtag should already check we're in-bounds
|
||||
LFS_ASSERT(lfsr_tag_isalt(tag)
|
||||
|| off__ + size <= lfs->cfg->block_size);
|
||||
|
||||
// take care of cksum
|
||||
if (!lfsr_tag_isalt(tag)) {
|
||||
// not an end-of-commit cksum
|
||||
if (lfsr_tag_suptype(tag) != LFSR_TAG_CKSUM) {
|
||||
// checksum the entry
|
||||
err = lfsr_bd_cksum(lfs, block, off__, -1, size,
|
||||
&cksum);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// is an end-of-commit cksum
|
||||
} else {
|
||||
// check checksum
|
||||
uint32_t cksum_ = 0;
|
||||
err = lfsr_bd_read(lfs, block, off__, -1,
|
||||
&cksum_, sizeof(uint32_t));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
cksum_ = lfs_fromle32_(&cksum_);
|
||||
|
||||
if (cksum != cksum_) {
|
||||
// uh oh, checksums don't match
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// if we get here we've checked the commit containing
|
||||
// our data
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// skip data
|
||||
if (!lfsr_tag_isalt(tag)) {
|
||||
off__ += size;
|
||||
}
|
||||
|
||||
off_ = off__;
|
||||
}
|
||||
|
||||
// didn't find a checksum tag? that's weird
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// rbyd append operations
|
||||
|
||||
@@ -3700,12 +3374,14 @@ static int lfsr_rbyd_appendtag(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
|
||||
rbyd->eoff += d;
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
// keep track of most recent cksum
|
||||
#ifdef LFS_CKPARITY
|
||||
// keep track of most recent parity
|
||||
lfs->tailck.ckblock = rbyd->blocks[0];
|
||||
lfs->tailck.ckoff = lfsr_rbyd_eoff(rbyd);
|
||||
lfs->tailck.cksum = rbyd->cksum
|
||||
^ ((lfsr_rbyd_isperturb(rbyd)) ? LFS_CRC32C_ODDZERO : 0);
|
||||
lfs->tailck.ckoff
|
||||
= ((lfs_size_t)(
|
||||
lfs_parity(rbyd->cksum) ^ lfsr_rbyd_isperturb(rbyd)
|
||||
) << (8*sizeof(lfs_size_t)-1))
|
||||
| lfsr_rbyd_eoff(rbyd);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
@@ -3729,12 +3405,14 @@ static int lfsr_rbyd_appendcat(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
|
||||
rbyd->eoff += lfsr_cat_size(cat, count);
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
// keep track of most recent parity
|
||||
lfs->tailck.ckblock = rbyd->blocks[0];
|
||||
lfs->tailck.ckoff = lfsr_rbyd_eoff(rbyd);
|
||||
lfs->tailck.cksum = rbyd->cksum
|
||||
^ ((lfsr_rbyd_isperturb(rbyd)) ? LFS_CRC32C_ODDZERO : 0);
|
||||
lfs->tailck.ckoff
|
||||
= ((lfs_size_t)(
|
||||
lfs_parity(rbyd->cksum) ^ lfsr_rbyd_isperturb(rbyd)
|
||||
) << (8*sizeof(lfs_size_t)-1))
|
||||
| lfsr_rbyd_eoff(rbyd);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
@@ -13757,11 +13435,10 @@ static int lfs_init(lfs_t *lfs, uint32_t flags,
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
// setup tailck, nothing should actually check off=0
|
||||
lfs->tailck.ckblock = 0;
|
||||
lfs->tailck.ckoff = 0;
|
||||
lfs->tailck.cksum = 0;
|
||||
#endif
|
||||
|
||||
// setup lookahead buffer, note mount finishes initializing this after
|
||||
|
||||
@@ -169,7 +169,7 @@ enum lfs_type {
|
||||
#define LFS_F_CKPARITY 0x00400000 // Check tag parity bits on reads
|
||||
#endif
|
||||
#ifdef LFS_CKCKSUMS
|
||||
#define LFS_F_CKCKSUMS 0x00800000 // Check checksums on reads (expensive!)
|
||||
#define LFS_F_CKCKSUMS 0x00800000 // Check data checksums on reads
|
||||
#endif
|
||||
|
||||
#define LFS_F_MTREEONLY 0x00000800 // Only traverse the mtree
|
||||
@@ -192,7 +192,7 @@ enum lfs_type {
|
||||
#define LFS_M_CKPARITY 0x00400000 // Check tag parity bits on reads
|
||||
#endif
|
||||
#ifdef LFS_CKCKSUMS
|
||||
#define LFS_M_CKCKSUMS 0x00800000 // Check checksums on reads (expensive!)
|
||||
#define LFS_M_CKCKSUMS 0x00800000 // Check data checksums on reads
|
||||
#endif
|
||||
|
||||
#define LFS_M_MTREEONLY 0x00000800 // Only traverse the mtree
|
||||
@@ -808,11 +808,11 @@ typedef struct lfsr_grm {
|
||||
lfsr_smid_t mids[2];
|
||||
} lfsr_grm_t;
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
typedef struct lfsr_tailck {
|
||||
lfs_block_t ckblock;
|
||||
// sign(ckoff) => tail parity
|
||||
lfs_size_t ckoff;
|
||||
uint32_t cksum;
|
||||
} lfsr_tailck_t;
|
||||
#endif
|
||||
|
||||
@@ -848,7 +848,7 @@ typedef struct lfs {
|
||||
uint8_t *buffer;
|
||||
} pcache;
|
||||
|
||||
#if defined(LFS_CKPARITY) || defined(LFS_CKCKSUMS)
|
||||
#ifdef LFS_CKPARITY
|
||||
lfsr_tailck_t tailck;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1348,117 +1348,6 @@ code = '''
|
||||
|
||||
# Some simple ckcksums tests
|
||||
|
||||
# test every single-bit error in block 0/1
|
||||
[cases.test_ck_ckcksums_mroot]
|
||||
defines.BADBLOCK = [0, 1]
|
||||
defines.BADBIT = -1
|
||||
defines.BADBLOCK_BEHAVIOR = [
|
||||
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
||||
'LFS_EMUBD_BADBLOCK_READFLIP',
|
||||
]
|
||||
# this should stay inlined
|
||||
defines.SIZE = 'BLOCK_SIZE/16'
|
||||
ifdef = 'LFS_CKCKSUMS'
|
||||
code = '''
|
||||
// test all bad bits in the mroot
|
||||
for (lfs_size_t i = 0;
|
||||
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
||||
i++) {
|
||||
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
||||
|
||||
// reset the bd prng every run for reproducibility
|
||||
lfs_emubd_seed(CFG, 42);
|
||||
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
||||
(lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8);
|
||||
|
||||
// mark our badbit as bad
|
||||
lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0;
|
||||
|
||||
// With metastability, basically any filesystem operation can
|
||||
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
||||
// for is no internal/external asserts failing.
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
int err = lfsr_format(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
|
||||
{
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
|
||||
assert(!err || err == LFS_ERR_CORRUPT);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
err = lfsr_file_close(&lfs, &file);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
|
||||
// try to read our file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt;
|
||||
}
|
||||
}
|
||||
|
||||
// yes reads can fail here
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
||||
assert(!err
|
||||
|| err == LFS_ERR_CORRUPT
|
||||
// bit errors can also cause our fs state to "rollback",
|
||||
// which is not great but we can't solve this with ckreads
|
||||
// alone
|
||||
|| err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint8_t rbuf[SIZE];
|
||||
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
corrupt_mounted:;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
corrupt:;
|
||||
// reset badbit
|
||||
lfs_emubd_markgood(CFG, BADBLOCK) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
# test every single-bit error in a file's data block
|
||||
[cases.test_ck_ckcksums_data]
|
||||
defines.BADBIT = -1
|
||||
@@ -1590,140 +1479,6 @@ code = '''
|
||||
}
|
||||
'''
|
||||
|
||||
# test every single-bit error in a file's btree node
|
||||
[cases.test_ck_ckcksums_btree]
|
||||
defines.BADBIT = -1
|
||||
defines.BADBLOCK_BEHAVIOR = [
|
||||
'LFS_EMUBD_BADBLOCK_PROGFLIP',
|
||||
'LFS_EMUBD_BADBLOCK_READFLIP',
|
||||
]
|
||||
# force the file to create a btree
|
||||
defines.INLINE_SIZE = 0
|
||||
defines.CRYSTAL_THRESH = -1
|
||||
defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8'
|
||||
defines.SIZE = '2*FRAGMENT_SIZE'
|
||||
ifdef = 'LFS_CKCKSUMS'
|
||||
code = '''
|
||||
// first we need to figure out where the btree block will actually
|
||||
// end up, fortunately our block randomization is intentionally
|
||||
// consistent
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKCKSUMS, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// find the btree block
|
||||
lfsr_traversal_t t;
|
||||
lfsr_traversal_open(&lfs, &t, 0) => 0;
|
||||
lfs_block_t badblock;
|
||||
while (true) {
|
||||
struct lfs_tinfo tinfo;
|
||||
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
|
||||
if (tinfo.btype == LFS_BTYPE_BTREE) {
|
||||
badblock = tinfo.block;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfsr_traversal_close(&lfs, &t) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// now test all bad bits in the btree block
|
||||
for (lfs_size_t i = 0;
|
||||
i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1);
|
||||
i++) {
|
||||
lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT;
|
||||
|
||||
// reset the bd prng every run for reproducibility
|
||||
lfs_emubd_seed(CFG, 42);
|
||||
printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n",
|
||||
badblock, badbit/8, badbit, badbit/8, badbit%8);
|
||||
|
||||
// mark our badbit as bad
|
||||
lfs_emubd_markbadbit(CFG, badblock, badbit) => 0;
|
||||
|
||||
// With metastability, basically any filesystem operation can
|
||||
// return LFS_ERR_CORRUPT. This is ok, what we're really testing
|
||||
// for is no internal/external asserts failing.
|
||||
|
||||
// format
|
||||
lfs_t lfs;
|
||||
lfsr_format(&lfs, LFS_F_RDWR | LFS_F_CKCKSUMS, CFG) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
|
||||
{
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "bathykorus",
|
||||
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
|
||||
uint32_t prng = 42;
|
||||
uint8_t wbuf[SIZE];
|
||||
for (lfs_size_t j = 0; j < SIZE; j++) {
|
||||
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
|
||||
}
|
||||
lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
int err = lfsr_file_close(&lfs, &file);
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
|
||||
// try to read our file
|
||||
for (int remount = 0; remount < 2; remount++) {
|
||||
// remount?
|
||||
if (remount) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKCKSUMS, CFG) => 0;
|
||||
}
|
||||
|
||||
// yes reads can fail here
|
||||
err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY);
|
||||
assert(!err
|
||||
|| err == LFS_ERR_CORRUPT
|
||||
// bit errors can also cause our fs state to "rollback",
|
||||
// which is not great but we can't solve this with ckreads
|
||||
// alone
|
||||
|| err == LFS_ERR_NOENT);
|
||||
if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) {
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
uint8_t rbuf[SIZE];
|
||||
lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE);
|
||||
assert(res == SIZE || res == LFS_ERR_CORRUPT);
|
||||
if (res == LFS_ERR_CORRUPT) {
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
goto corrupt_mounted;
|
||||
}
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
}
|
||||
}
|
||||
|
||||
corrupt_mounted:;
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
|
||||
// reset badbit
|
||||
lfs_emubd_markgood(CFG, badblock) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user