Compare commits

...

3 Commits

Author SHA1 Message Date
Christopher Haster
97b5d04bf4 Switched to separate-tag encoding of forward-looking CRCs
Previously forward-looking CRCs was just two new CRC types, one for
commits with forward-looking CRCs, one without. These both contained the
CRC needed to complete the current commit (note that the commit CRC
must come last!).

         [--   32   --|--   32   --|--   32   --|--   32   --]
with:    [  crc3 tag  | nprog size |  nprog crc | commit crc ]
without: [  crc2 tag  | commit crc ]

This meant there had to be several checks for the two possible structure
sizes, messying up the implementation.

         [--   32   --|--   32   --|--   32   --|--   32   --|--   32   --]
with:    [nprogcrc tag| nprog size |  nprog crc | commit tag | commit crc ]
without: [ commit tag | commit crc ]

But we already have a mechanism for storing optional metadata! The
different metadata tags! So why not use a separate tage for the
forward-looking CRC, separate from the commit CRC?

I wasn't sure this would actually help that much, there are still
necessary conditions for wether or not a forward-looking CRC is there,
but in the end it simplified the code quite nicely, and resulted in a ~200 byte
code-cost saving.
2021-01-15 02:00:01 -06:00
Christopher Haster
7535795a44 Cleaned up a few additional commit corner cases
- General cleanup from integration, including cleaning up some older
  commit code
- Partial-prog tests do not make sense when prog_size == block_size
  (there can't be partial-progs!)
- Fixed signed-comparison issue in modified filebd
2020-12-07 01:10:28 -06:00
Christopher Haster
01a3b1f5f7 Initial implementation of forward-looking erase-state CRCs
This change is necessary to handle out-of-order writes found by pjsg's
fuzzing work.

The problem is that it is possible for (non-NOR) block devices to write
pages in any order, or to even write random data in the case of a
power-loss. This breaks littlefs's use of the first bit in a page to
indicate the erase-state.

pjsg notes this behavior is documented in the W25Q here:
https://community.cypress.com/docs/DOC-10507

---

The basic idea here is to CRC the next page, and use this "erase-state CRC" to
check if the next page is erased and ready to accept programs.

.------------------. \   commit
|     metadata     | |
|                  | +---.
|                  | |   |
|------------------| |   |
| erase-state CRC -----. |
|------------------| | | |
|   commit CRC    ---|-|-'
|------------------| / |
|     padding      |   | padding (doesn't need CRC)
|                  |   |
|------------------| \ | next prog
|     erased?      | +-'
|        |         | |
|        v         | /
|                  |
|                  |
'------------------'

This is made a bit annoying since littlefs doesn't actually store the
page (prog_size) in the superblock, since it doesn't need to know the
size for any other operation. We can work around this by storing both
the CRC and size of the next page when necessary.

Another interesting note is that we don't need to any bit tweaking
information, since we read the next page every time we would need to
know how to clobber the erase-state CRC. And since we only read
prog_size, this works really well with our caching, since the caches
must be a multiple of prog_size.

This also brings back the internal lfs_bd_crc function, in which we can
use some optimizations added to lfs_bd_cmp.

Needs some cleanup but the idea is passing most relevant tests.
2020-12-07 00:06:17 -06:00
6 changed files with 480 additions and 155 deletions

View File

@@ -80,11 +80,6 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
LFS_ASSERT(size % cfg->read_size == 0); LFS_ASSERT(size % cfg->read_size == 0);
LFS_ASSERT(block < cfg->block_count); LFS_ASSERT(block < cfg->block_count);
// zero for reproducability (in case file is truncated)
if (bd->cfg->erase_value != -1) {
memset(buffer, bd->cfg->erase_value, size);
}
// read // read
off_t res1 = lseek(bd->fd, off_t res1 = lseek(bd->fd,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET); (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
@@ -101,6 +96,11 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
return err; return err;
} }
// file truncated? zero for reproducability
if ((lfs_size_t)res2 < size) {
memset((uint8_t*)buffer + res2, 0, size-res2);
}
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0); LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0);
return 0; return 0;
} }

View File

@@ -32,11 +32,8 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
} }
} }
// zero for reproducability? // zero for reproducability (this matches filebd)
if (bd->cfg->erase_value != -1) { memset(bd->buffer, 0, cfg->block_size * cfg->block_count);
memset(bd->buffer, bd->cfg->erase_value,
cfg->block_size * cfg->block_count);
}
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0); LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0);
return 0; return 0;

267
lfs.c
View File

@@ -122,16 +122,15 @@ static int lfs_bd_cmp(lfs_t *lfs,
for (lfs_off_t i = 0; i < size; i += diff) { for (lfs_off_t i = 0; i < size; i += diff) {
uint8_t dat[8]; uint8_t dat[8];
diff = lfs_min(size-i, sizeof(dat)); diff = lfs_min(size-i, sizeof(dat));
int res = lfs_bd_read(lfs, int err = lfs_bd_read(lfs,
pcache, rcache, hint-i, pcache, rcache, hint-i,
block, off+i, &dat, diff); block, off+i, &dat, diff);
if (res) { if (err) {
return res; return err;
} }
res = memcmp(dat, data + i, diff); int res = memcmp(dat, data + i, diff);
if (res) { if (res) {
return res < 0 ? LFS_CMP_LT : LFS_CMP_GT; return res < 0 ? LFS_CMP_LT : LFS_CMP_GT;
} }
@@ -140,6 +139,27 @@ static int lfs_bd_cmp(lfs_t *lfs,
return LFS_CMP_EQ; return LFS_CMP_EQ;
} }
static int lfs_bd_crc(lfs_t *lfs,
const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) {
lfs_size_t diff = 0;
for (lfs_off_t i = 0; i < size; i += diff) {
uint8_t dat[8];
diff = lfs_min(size-i, sizeof(dat));
int err = lfs_bd_read(lfs,
pcache, rcache, hint-i,
block, off+i, &dat, diff);
if (err) {
return err;
}
*crc = lfs_crc(*crc, &dat, diff);
}
return 0;
}
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_bd_flush(lfs_t *lfs, static int lfs_bd_flush(lfs_t *lfs,
lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
@@ -310,6 +330,10 @@ static inline uint16_t lfs_tag_type1(lfs_tag_t tag) {
return (tag & 0x70000000) >> 20; return (tag & 0x70000000) >> 20;
} }
static inline uint16_t lfs_tag_type2(lfs_tag_t tag) {
return (tag & 0x78000000) >> 20;
}
static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { static inline uint16_t lfs_tag_type3(lfs_tag_t tag) {
return (tag & 0x7ff00000) >> 20; return (tag & 0x7ff00000) >> 20;
} }
@@ -394,6 +418,22 @@ static inline void lfs_gstate_tole32(lfs_gstate_t *a) {
a->pair[1] = lfs_tole32(a->pair[1]); a->pair[1] = lfs_tole32(a->pair[1]);
} }
// operations on estate in CRC tags
struct lfs_estate {
lfs_size_t size;
uint32_t crc;
};
static void lfs_estate_fromle32(struct lfs_estate *estate) {
estate->size = lfs_fromle32(estate->size);
estate->crc = lfs_fromle32(estate->crc);
}
static void lfs_estate_tole32(struct lfs_estate *estate) {
estate->size = lfs_tole32(estate->size);
estate->crc = lfs_tole32(estate->crc);
}
// other endianness operations // other endianness operations
static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { static void lfs_ctz_fromle32(struct lfs_ctz *ctz) {
ctz->head = lfs_fromle32(ctz->head); ctz->head = lfs_fromle32(ctz->head);
@@ -880,6 +920,9 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
bool tempsplit = false; bool tempsplit = false;
lfs_stag_t tempbesttag = besttag; lfs_stag_t tempbesttag = besttag;
bool hasestate = false;
struct lfs_estate estate;
dir->rev = lfs_tole32(dir->rev); dir->rev = lfs_tole32(dir->rev);
uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev));
dir->rev = lfs_fromle32(dir->rev); dir->rev = lfs_fromle32(dir->rev);
@@ -901,21 +944,17 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
} }
crc = lfs_crc(crc, &tag, sizeof(tag)); crc = lfs_crc(crc, &tag, sizeof(tag));
tag = lfs_frombe32(tag) ^ ptag; tag = (lfs_frombe32(tag) ^ ptag) & 0x7fffffff;
// next commit not yet programmed or we're not in valid range // out of range?
if (!lfs_tag_isvalid(tag)) { if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC &&
dir->off % lfs->cfg->prog_size == 0);
break;
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
dir->erased = false; dir->erased = false;
break; break;
} }
ptag = tag; ptag = tag;
if (lfs_tag_type1(tag) == LFS_TYPE_CRC) { if (lfs_tag_type2(tag) == LFS_TYPE_CRC) {
// check the crc attr // check the crc attr
uint32_t dcrc; uint32_t dcrc;
err = lfs_bd_read(lfs, err = lfs_bd_read(lfs,
@@ -935,9 +974,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
break; break;
} }
// reset the next bit if we need to
ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31;
// toss our crc into the filesystem seed for // toss our crc into the filesystem seed for
// pseudorandom numbers, note we use another crc here // pseudorandom numbers, note we use another crc here
// as a collection function because it is sufficiently // as a collection function because it is sufficiently
@@ -953,17 +989,61 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
dir->tail[1] = temptail[1]; dir->tail[1] = temptail[1];
dir->split = tempsplit; dir->split = tempsplit;
// reset crc if (hasestate) {
crc = 0xffffffff; // check if the next page is erased
continue; //
} // this may look inefficient, but since cache_size is
// probably > prog_size, the data will always remain in
// cache for the next iteration
// crc the entry first, hopefully leaving it in the cache // first we get a tag-worth of bits, this is so we can
for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) { // tweak our current tag to force future writes to be
uint8_t dat; // different than the erased state
lfs_tag_t etag;
err = lfs_bd_read(lfs, err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size, NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], off+j, &dat, 1); dir->pair[0], dir->off, &etag, sizeof(etag));
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
// perturb valid bit?
dir->etag |= 0x80000000 & ~lfs_frombe32(etag);
// crc the rest the full prog_size, including etag in case
// the actual esize is < tag size (though this shouldn't
// happen normally)
uint32_t tcrc = 0xffffffff;
err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], dir->off, estate.size, &tcrc);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
if (tcrc == estate.crc) {
dir->erased = true;
break;
}
} else if (lfs_tag_chunk(tag) < 2) {
// for backwards compatibility we fall through here on
// unrecognized tags, leaving it up to the CRC to reject
// bad commits
} else {
// end of block commit
dir->erased = false;
break;
}
// reset crc
crc = 0xffffffff;
hasestate = false;
} else {
// crc the entry first, hopefully leaving it in the cache
err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], off+sizeof(tag),
lfs_tag_dsize(tag)-sizeof(tag), &crc);
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
dir->erased = false; dir->erased = false;
@@ -972,9 +1052,6 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
return err; return err;
} }
crc = lfs_crc(crc, &dat, 1);
}
// directory modification tags? // directory modification tags?
if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { if (lfs_tag_type1(tag) == LFS_TYPE_NAME) {
// increase count of files if necessary // increase count of files if necessary
@@ -996,7 +1073,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
err = lfs_bd_read(lfs, err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size, NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], off+sizeof(tag), &temptail, 8); dir->pair[0], off+sizeof(tag),
&temptail, sizeof(temptail));
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
dir->erased = false; dir->erased = false;
@@ -1004,6 +1082,19 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
} }
} }
lfs_pair_fromle32(temptail); lfs_pair_fromle32(temptail);
} else if (lfs_tag_type1(tag) == LFS_TYPE_NPROGCRC) {
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], off+sizeof(tag),
&estate, sizeof(estate));
if (err) {
if (err == LFS_ERR_CORRUPT) {
dir->erased = false;
break;
}
}
lfs_estate_fromle32(&estate);
hasestate = true;
} }
// found a match for our fetcher? // found a match for our fetcher?
@@ -1028,11 +1119,13 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
tempbesttag = -1; tempbesttag = -1;
} else if (res == LFS_CMP_GT && } else if (res == LFS_CMP_GT &&
lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) {
// found a greater match, keep track to keep things sorted // found a greater match, keep track to keep
// things sorted
tempbesttag = tag | 0x80000000; tempbesttag = tag | 0x80000000;
} }
} }
} }
}
// consider what we have good enough // consider what we have good enough
if (dir->off > 0) { if (dir->off > 0) {
@@ -1338,7 +1431,12 @@ static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit,
#ifndef LFS_READONLY #ifndef LFS_READONLY
static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
// align to program units // align to program units
const lfs_off_t end = lfs_alignup(commit->off + 2*sizeof(uint32_t), //
// this gets a bit complex as we have two types of crcs:
// - 4-word crc with estate to check following prog (middle of block)
// - 2-word crc with no following prog (end of block)
const lfs_off_t end = lfs_alignup(
lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size),
lfs->cfg->prog_size); lfs->cfg->prog_size);
lfs_off_t off1 = 0; lfs_off_t off1 = 0;
@@ -1354,40 +1452,71 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
noff = lfs_min(noff, end - 2*sizeof(uint32_t)); noff = lfs_min(noff, end - 2*sizeof(uint32_t));
} }
// read erased state from next program unit // clamp erase size to tag size, this gives us the full tag as potential
lfs_tag_t tag = 0xffffffff; // to intentionally invalidate erase CRCs
const lfs_size_t esize = lfs_max(
lfs->cfg->prog_size, sizeof(lfs_tag_t));
lfs_tag_t etag = 0;
// space for estate? also only emit on last commit in padding commits
if (noff <= lfs->cfg->block_size - esize) {
// first we get a tag-worth of bits, this is so we can
// tweak our current tag to force future writes to be
// different than the erased state
int err = lfs_bd_read(lfs, int err = lfs_bd_read(lfs,
NULL, &lfs->rcache, sizeof(tag), NULL, &lfs->rcache, esize,
commit->block, noff, &tag, sizeof(tag)); commit->block, noff, &etag, sizeof(etag));
if (err && err != LFS_ERR_CORRUPT) { if (err && err != LFS_ERR_CORRUPT) {
return err; return err;
} }
// build crc tag // find expected erased state
bool reset = ~lfs_frombe32(tag) >> 31; uint32_t ecrc = 0xffffffff;
tag = LFS_MKTAG(LFS_TYPE_CRC + reset, 0x3ff, noff - off); err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, esize,
commit->block, noff, esize, &ecrc);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
// write out crc struct lfs_estate estate = {.size = esize, .crc = ecrc};
uint32_t footer[2]; lfs_estate_tole32(&estate);
footer[0] = lfs_tobe32(tag ^ commit->ptag); err = lfs_dir_commitattr(lfs, commit,
commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0])); LFS_MKTAG(LFS_TYPE_NPROGCRC, 0x3ff, sizeof(estate)),
footer[1] = lfs_tole32(commit->crc); &estate);
err = lfs_bd_prog(lfs, if (err) {
return err;
}
}
// build crc state
off = commit->off + sizeof(lfs_tag_t);
struct {
lfs_tag_t tag;
uint32_t crc;
} cstate;
lfs_tag_t ctag = LFS_MKTAG(LFS_TYPE_COMMITCRC, 0x3ff, noff-off);
cstate.tag = lfs_tobe32(ctag ^ commit->ptag);
commit->crc = lfs_crc(commit->crc, &cstate.tag, sizeof(cstate.tag));
cstate.crc = lfs_tole32(commit->crc);
int err = lfs_bd_prog(lfs,
&lfs->pcache, &lfs->rcache, false, &lfs->pcache, &lfs->rcache, false,
commit->block, commit->off, &footer, sizeof(footer)); commit->block, commit->off, &cstate, sizeof(cstate));
if (err) { if (err) {
return err; return err;
} }
// keep track of non-padding checksum to verify // keep track of non-padding checksum to verify
if (off1 == 0) { if (off1 == 0) {
off1 = commit->off + sizeof(uint32_t); off1 = off;
crc1 = commit->crc; crc1 = commit->crc;
} }
commit->off += sizeof(tag)+lfs_tag_size(tag); commit->off = noff;
commit->ptag = tag ^ ((lfs_tag_t)reset << 31); // perturb valid bit?
commit->crc = 0xffffffff; // reset crc for next "commit" commit->ptag = ctag | (0x80000000 & ~lfs_frombe32(etag));
// reset crc for next commit
commit->crc = 0xffffffff;
} }
// flush buffers // flush buffers
@@ -1397,40 +1526,34 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
} }
// successful commit, check checksums to make sure // successful commit, check checksums to make sure
//
// note that we don't need to check padding commits, worst
// case if they are corrupted we would have had to compact anyways
lfs_off_t off = commit->begin; lfs_off_t off = commit->begin;
lfs_off_t noff = off1;
while (off < end) {
uint32_t crc = 0xffffffff; uint32_t crc = 0xffffffff;
for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) { err = lfs_bd_crc(lfs,
// check against written crc, may catch blocks that NULL, &lfs->rcache, off1+sizeof(uint32_t),
// become readonly and match our commit size exactly commit->block, off, off1-off, &crc);
if (i == off1 && crc != crc1) {
return LFS_ERR_CORRUPT;
}
// leave it up to caching to make this efficient
uint8_t dat;
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, noff+sizeof(uint32_t)-i,
commit->block, i, &dat, 1);
if (err) { if (err) {
return err; return err;
} }
crc = lfs_crc(crc, &dat, 1); // check against known crc for non-padding commits
} if (crc != crc1) {
// detected write error?
if (crc != 0) {
return LFS_ERR_CORRUPT; return LFS_ERR_CORRUPT;
} }
// skip padding // make sure to check crc in case we happened to pick
off = lfs_min(end - noff, 0x3fe) + noff; // up an unrelated crc (frozen block?)
if (off < end) { err = lfs_bd_crc(lfs,
off = lfs_min(off, end - 2*sizeof(uint32_t)); NULL, &lfs->rcache, sizeof(uint32_t),
commit->block, off1, sizeof(uint32_t), &crc);
if (err) {
return err;
} }
noff = off + sizeof(uint32_t);
if (crc != 0) {
return LFS_ERR_CORRUPT;
} }
return 0; return 0;

2
lfs.h
View File

@@ -113,6 +113,8 @@ enum lfs_type {
LFS_TYPE_SOFTTAIL = 0x600, LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601, LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_MOVESTATE = 0x7ff, LFS_TYPE_MOVESTATE = 0x7ff,
LFS_TYPE_COMMITCRC = 0x502,
LFS_TYPE_NPROGCRC = 0x5ff,
// internal chip sources // internal chip sources
LFS_FROM_NOOP = 0x000, LFS_FROM_NOOP = 0x000,

View File

@@ -24,6 +24,7 @@ TAG_TYPES = {
'gstate': (0x700, 0x700), 'gstate': (0x700, 0x700),
'movestate': (0x7ff, 0x7ff), 'movestate': (0x7ff, 0x7ff),
'crc': (0x700, 0x500), 'crc': (0x700, 0x500),
'nprogcrc': (0x7ff, 0x5ff),
} }
class Tag: class Tag:
@@ -99,6 +100,15 @@ class Tag:
return struct.unpack('b', struct.pack('B', self.chunk))[0] return struct.unpack('b', struct.pack('B', self.chunk))[0]
def is_(self, type): def is_(self, type):
try:
if ' ' in type:
type1, type3 = type.split()
return (self.is_(type1) and
(self.type & ~TAG_TYPES[type1][0]) == int(type3, 0))
return self.type == int(type, 0)
except (ValueError, KeyError):
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1] return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
def mkmask(self): def mkmask(self):
@@ -112,11 +122,17 @@ class Tag:
if hasattr(self, 'off'): ntag.off = self.off if hasattr(self, 'off'): ntag.off = self.off
if hasattr(self, 'data'): ntag.data = self.data if hasattr(self, 'data'): ntag.data = self.data
if hasattr(self, 'crc'): ntag.crc = self.crc if hasattr(self, 'crc'): ntag.crc = self.crc
if hasattr(self, 'erased'): ntag.erased = self.erased
return ntag return ntag
def typerepr(self): def typerepr(self):
if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff: if (self.is_('crc') and not self.is_('nprogcrc') and
return 'crc (bad)' getattr(self, 'crc', 0xffffffff) != 0xffffffff):
crc_status = ' (bad)'
elif self.is_('nprogcrc') and getattr(self, 'erased', False):
crc_status = ' (era)'
else:
crc_status = ''
reverse_types = {v: k for k, v in TAG_TYPES.items()} reverse_types = {v: k for k, v in TAG_TYPES.items()}
for prefix in range(12): for prefix in range(12):
@@ -124,12 +140,12 @@ class Tag:
if (mask, self.type & mask) in reverse_types: if (mask, self.type & mask) in reverse_types:
type = reverse_types[mask, self.type & mask] type = reverse_types[mask, self.type & mask]
if prefix > 0: if prefix > 0:
return '%s %#0*x' % ( return '%s %#x%s' % (
type, prefix//4, self.type & ((1 << prefix)-1)) type, self.type & ((1 << prefix)-1), crc_status)
else: else:
return type return '%s%s' % (type, crc_status)
else: else:
return '%02x' % self.type return '%02x%s' % (self.type, crc_status)
def idrepr(self): def idrepr(self):
return repr(self.id) if self.id != 0x3ff else '.' return repr(self.id) if self.id != 0x3ff else '.'
@@ -172,6 +188,8 @@ class MetadataPair:
self.rev, = struct.unpack('<I', block[0:4]) self.rev, = struct.unpack('<I', block[0:4])
crc = binascii.crc32(block[0:4]) crc = binascii.crc32(block[0:4])
etag = None
estate = None
# parse tags # parse tags
corrupt = False corrupt = False
@@ -182,11 +200,11 @@ class MetadataPair:
while len(block) - off >= 4: while len(block) - off >= 4:
ntag, = struct.unpack('>I', block[off:off+4]) ntag, = struct.unpack('>I', block[off:off+4])
tag = Tag(int(tag) ^ ntag) tag = Tag((int(tag) ^ ntag) & 0x7fffffff)
tag.off = off + 4 tag.off = off + 4
tag.data = block[off+4:off+tag.dsize] tag.data = block[off+4:off+tag.dsize]
if tag.is_('crc'): if tag.is_('crc') and not tag.is_('nprogcrc'):
crc = binascii.crc32(block[off:off+4+4], crc) crc = binascii.crc32(block[off:off+2*4], crc)
else: else:
crc = binascii.crc32(block[off:off+tag.dsize], crc) crc = binascii.crc32(block[off:off+tag.dsize], crc)
tag.crc = crc tag.crc = crc
@@ -194,16 +212,29 @@ class MetadataPair:
self.all_.append(tag) self.all_.append(tag)
if tag.is_('crc'): if tag.is_('nprogcrc') and len(tag.data) == 8:
etag = tag
estate = struct.unpack('<II', tag.data)
elif tag.is_('crc'):
# is valid commit? # is valid commit?
if crc != 0xffffffff: if crc != 0xffffffff:
corrupt = True corrupt = True
if not corrupt: if not corrupt:
self.log = self.all_.copy() self.log = self.all_.copy()
# end of commit?
if estate:
esize, ecrc = estate
dcrc = 0xffffffff ^ binascii.crc32(block[off:off+esize])
if ecrc == dcrc:
etag.erased = True
corrupt = True
elif not (tag.is_('crc 0x0') or tag.is_('crc 0x1')):
corrupt = True
# reset tag parsing # reset tag parsing
crc = 0 crc = 0
tag = Tag(int(tag) ^ ((tag.type & 1) << 31)) etag = None
estate = None
# find active ids # find active ids
self.ids = list(it.takewhile( self.ids = list(it.takewhile(
@@ -280,7 +311,7 @@ class MetadataPair:
f.write('\n') f.write('\n')
for tag in tags: for tag in tags:
f.write("%08x: %08x %-13s %4s %4s" % ( f.write("%08x: %08x %-14s %3s %4s" % (
tag.off, tag, tag.off, tag,
tag.typerepr(), tag.idrepr(), tag.sizerepr())) tag.typerepr(), tag.idrepr(), tag.sizerepr()))
if truncate: if truncate:

172
tests/test_powerloss.toml Normal file
View File

@@ -0,0 +1,172 @@
# There are already a number of tests that test general operations under
# power-loss (see the reentrant attribute). These tests are for explicitly
# testing specific corner cases.
[[case]] # only a revision count
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "notebook") => 0;
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
strcpy((char*)buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
char rbuffer[256];
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// get pair/rev count
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir, "notebook") => 0;
lfs_block_t pair[2] = {dir.m.pair[0], dir.m.pair[1]};
uint32_t rev = dir.m.rev;
lfs_dir_close(&lfs, &dir) => 0;
lfs_unmount(&lfs) => 0;
// write just the revision count
uint8_t bbuffer[LFS_BLOCK_SIZE];
cfg.read(&cfg, pair[1], 0, bbuffer, LFS_BLOCK_SIZE) => 0;
memcpy(bbuffer, &(uint32_t){lfs_tole32(rev+1)}, sizeof(uint32_t));
cfg.erase(&cfg, pair[1]) => 0;
cfg.prog(&cfg, pair[1], 0, bbuffer, LFS_BLOCK_SIZE) => 0;
lfs_mount(&lfs, &cfg) => 0;
// can read?
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
// can write?
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_APPEND) => 0;
strcpy((char*)buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
strcpy((char*)buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
strcpy((char*)buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # partial prog, may not be byte in order!
if = "LFS_PROG_SIZE < LFS_BLOCK_SIZE"
define.BYTE_OFF = ["0", "LFS_PROG_SIZE-1", "LFS_PROG_SIZE/2"]
define.BYTE_VALUE = [0x33, 0xcc]
in = "lfs.c"
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "notebook") => 0;
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
strcpy((char*)buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
char rbuffer[256];
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// imitate a partial prog, value should not matter, if littlefs
// doesn't notice the partial prog testbd will assert
// get offset to next prog
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir, "notebook") => 0;
lfs_block_t block = dir.m.pair[0];
lfs_off_t off = dir.m.off;
lfs_dir_close(&lfs, &dir) => 0;
lfs_unmount(&lfs) => 0;
// tweak byte
uint8_t bbuffer[LFS_BLOCK_SIZE];
cfg.read(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
bbuffer[off + BYTE_OFF] = BYTE_VALUE;
cfg.erase(&cfg, block) => 0;
cfg.prog(&cfg, block, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
lfs_mount(&lfs, &cfg) => 0;
// can read?
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
// can write?
lfs_file_open(&lfs, &file, "notebook/paper",
LFS_O_WRONLY | LFS_O_APPEND) => 0;
strcpy((char*)buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_file_open(&lfs, &file, "notebook/paper", LFS_O_RDONLY) => 0;
strcpy((char*)buffer, "hello");
size = strlen("hello");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
strcpy((char*)buffer, "goodbye");
size = strlen("goodbye");
for (int i = 0; i < 5; i++) {
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''