Initial groundwork for rbyd trees
- primitive lfs_rbyd_fetch
- primitive lfs_rbyd_commit
- tag reading/progging and encoding machinery
The tag encoding scheme here uses pairs of leb128s, encoding either
a normal tag:
iiii iiiiiii iiiiiTT TTTTTTt ttttt0v
^--------^------^-^- 16-bit id
'------|-|- 8-bit type2
'-|- 6-bit type1
'- valid bit
llll lllllll lllllll lllllll lllllll
^- n-bit length
Or an alt pointer:
wwww wwwwwww wwwwwww wwwwwww wwwcd1v
^^^-^- 28-bit weight
'|-|- color bit
'-|- direction bit
'- valid bit
jjjj jjjjjjj jjjjjjj jjjjjjj jjjjjjj
^- n-bit jump
Note that two bits overlap the alt pointer dir/color encoding, this
is actually not a problem at all since some tags (crcs/fcrcs) don't
participate in the rbyd tree and can use these bits.
There's a number of benefits to using leb128s, which should probably
be written about, most notably is the abstraction of the device's
word-size. The "n-bits" above can be whatever word size works on the
device, trading off code-size for storage capabilities without breaking
compatibility with other devices. This will eventually be negotiated via
the superblock.
This commit is contained in:
@@ -172,6 +172,27 @@ static int lfs_bd_crc(lfs_t *lfs,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lfs_bd_crc32c(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_crc32c(*crc, &dat, diff);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef LFS_READONLY
|
||||
static int lfs_bd_flush(lfs_t *lfs,
|
||||
lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
|
||||
@@ -282,6 +303,7 @@ static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
|
||||
|
||||
|
||||
/// Small type-level utilities ///
|
||||
|
||||
// operations on block pairs
|
||||
static inline void lfs_pair_swap(lfs_block_t pair[2]) {
|
||||
lfs_block_t t = pair[0];
|
||||
@@ -372,6 +394,108 @@ static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) {
|
||||
return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag));
|
||||
}
|
||||
|
||||
typedef uint32_t lfs_rtag_t;
|
||||
typedef int32_t lfs_srtag_t;
|
||||
|
||||
enum lfs_rtag_type1 {
|
||||
LFS_TYPE1_CREATE = 0x40,
|
||||
LFS_TYPE1_DELETE = 0x48,
|
||||
LFS_TYPE1_STRUCT = 0x50,
|
||||
LFS_TYPE1_UATTR = 0x60,
|
||||
|
||||
LFS_TYPE1_TAIL = 0x08,
|
||||
LFS_TYPE1_GSTATE = 0x10,
|
||||
|
||||
LFS_TYPE1_CRC0 = 0x02,
|
||||
LFS_TYPE1_CRC1 = 0x0a,
|
||||
LFS_TYPE1_FCRC = 0x12,
|
||||
|
||||
LFS_TYPE1_ALT = 0x01,
|
||||
};
|
||||
|
||||
enum lfs_rtag_color {
|
||||
LFS_COLOR_B = 0,
|
||||
LFS_COLOR_R = 1,
|
||||
};
|
||||
|
||||
enum lfs_rtag_dir {
|
||||
LFS_DIR_LT = 0,
|
||||
LFS_DIR_GT = 1,
|
||||
};
|
||||
|
||||
#define LFS_MKRTAG_(type1, type2, id) \
|
||||
(((0x7f & (lfs_rtag_t)(type1)) << 0) \
|
||||
| ((0xff & (lfs_rtag_t)(type2)) << 7) \
|
||||
| ((0xffff & (lfs_rtag_t)(id)) << 15))
|
||||
|
||||
#define LFS_MKRTAG(type1, type2, id) \
|
||||
LFS_MKRTAG_(LFS_TYPE1_##type1, type2, id)
|
||||
|
||||
#define LFS_MKRALT_(color, dir, weight) \
|
||||
(0x1 \
|
||||
| ((0x1 & (lfs_rtag_t)(color)) << 2) \
|
||||
| ((0x1 & (lfs_rtag_t)(dir)) << 1) \
|
||||
| ((0xfffffff & (lfs_rtag_t)(weight)) << 3))
|
||||
|
||||
#define LFS_MKRALT(color, dir, weight) \
|
||||
LFS_MKRALT_(LFS_COLOR_##color, LFS_DIR_##dir, weight)
|
||||
|
||||
static inline bool lfs_rtag_isvalid(lfs_rtag_t tag) {
|
||||
return !(tag & 0x80000000);
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_isalt(lfs_rtag_t tag) {
|
||||
return tag & 0x1;
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_intree(lfs_rtag_t tag) {
|
||||
return tag & 0x2;
|
||||
}
|
||||
|
||||
static inline uint8_t lfs_rtag_type1(lfs_rtag_t tag) {
|
||||
return tag & 0x7f;
|
||||
}
|
||||
|
||||
static inline uint8_t lfs_rtag_type2(lfs_rtag_t tag) {
|
||||
return (tag >> 7) & 0xff;
|
||||
}
|
||||
|
||||
static inline uint16_t lfs_rtag_id(lfs_rtag_t tag) {
|
||||
return (tag >> 15) & 0xffff;
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_islt(lfs_rtag_t tag) {
|
||||
return !(tag & 0x2);
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_isgt(lfs_rtag_t tag) {
|
||||
return tag & 0x2;
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_isblack(lfs_rtag_t tag) {
|
||||
return !(tag & 0x4);
|
||||
}
|
||||
|
||||
static inline bool lfs_rtag_isred(lfs_rtag_t tag) {
|
||||
return tag & 0x4;
|
||||
}
|
||||
|
||||
static inline lfs_rtag_t lfs_rtag_weight(lfs_rtag_t tag) {
|
||||
return (tag >> 3) & 0xfffffff;
|
||||
}
|
||||
|
||||
static inline lfs_rtag_t lfs_rtag_red(lfs_rtag_t tag) {
|
||||
return tag | 0x4;
|
||||
}
|
||||
|
||||
static inline lfs_rtag_t lfs_rtag_black(lfs_rtag_t tag) {
|
||||
return tag & ~0x4;
|
||||
}
|
||||
|
||||
static inline lfs_rtag_t lfs_rtag_parallel(lfs_rtag_t a, lfs_rtag_t b) {
|
||||
return (a & 0x2) == (b & 0x2);
|
||||
}
|
||||
|
||||
// operations on attributes in attribute lists
|
||||
struct lfs_mattr {
|
||||
lfs_tag_t tag;
|
||||
@@ -387,6 +511,19 @@ struct lfs_diskoff {
|
||||
(struct lfs_mattr[]){__VA_ARGS__}, \
|
||||
sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr)
|
||||
|
||||
struct lfs_rattr {
|
||||
lfs_rtag_t tag;
|
||||
const void *buffer;
|
||||
lfs_size_t size;
|
||||
struct lfs_rattr *next;
|
||||
};
|
||||
|
||||
#define LFS_MKRATTR_(...) \
|
||||
(&(struct lfs_rattr){__VA_ARGS__})
|
||||
|
||||
#define LFS_MKRATTR(type1, type2, id, buffer, size, next) \
|
||||
(&(struct lfs_rattr){LFS_MKRTAG(type1, type2, id), buffer, size, next})
|
||||
|
||||
// operations on global state
|
||||
static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -454,6 +591,35 @@ static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) {
|
||||
}
|
||||
#endif
|
||||
|
||||
struct lfs_rfcrc {
|
||||
uint32_t crc;
|
||||
lfs_size_t size;
|
||||
// extra space for leb128 encoding
|
||||
uint8_t spill[1];
|
||||
};
|
||||
|
||||
static lfs_ssize_t lfs_rfcrc_todisk(struct lfs_rfcrc *fcrc) {
|
||||
lfs_tole32_(fcrc->crc, &fcrc->crc);
|
||||
|
||||
lfs_ssize_t delta = lfs_toleb128(fcrc->size, (uint8_t*)&fcrc->size, 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
return sizeof(uint32_t) + delta;
|
||||
}
|
||||
|
||||
static lfs_ssize_t lfs_rfcrc_fromdisk(struct lfs_rfcrc *fcrc) {
|
||||
fcrc->crc = lfs_fromle32_(&fcrc->crc);
|
||||
|
||||
lfs_ssize_t delta = lfs_fromleb128(&fcrc->size, (uint8_t*)&fcrc->size, 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
return sizeof(uint32_t) + delta;
|
||||
}
|
||||
|
||||
// other endianness operations
|
||||
static void lfs_ctz_fromle32(struct lfs_ctz *ctz) {
|
||||
ctz->head = lfs_fromle32(ctz->head);
|
||||
@@ -642,6 +808,683 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/// Red-black-yellow Dhara tree operations ///
|
||||
|
||||
static lfs_ssize_t lfs_rbyd_readtag(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_rtag_t *tag, lfs_size_t *size, uint32_t *crc) {
|
||||
// needed to quiet an uninitialized warning, zeroing tag on error is
|
||||
// probably a good idea anyways
|
||||
*tag = 0;
|
||||
|
||||
// read a pair of leb128s
|
||||
uint8_t buffer[2*5];
|
||||
lfs_size_t i = 0;
|
||||
|
||||
// TODO allow different hint for lookup? bench this? does our hint work backwards?
|
||||
// TODO should lfs_bd_read allow a range for reads?
|
||||
int err = lfs_bd_read(lfs,
|
||||
pcache, rcache, hint,
|
||||
block, off, &buffer, sizeof(buffer));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_rtag_t tag_;
|
||||
ssize_t delta = lfs_fromleb128(&tag_, &buffer[i], 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
i += delta;
|
||||
|
||||
lfs_size_t size_;
|
||||
delta = lfs_fromleb128(&size_, &buffer[i], 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
i += delta;
|
||||
|
||||
// optionally crc
|
||||
if (crc) {
|
||||
uint32_t crc_ = *crc;
|
||||
|
||||
// on-disk, the tags valid bit must reflect the parity of the
|
||||
// preceding data, fortunately for crc32c this is the same as the
|
||||
// parity of the crc
|
||||
if ((tag_ & 1) != (lfs_popc(crc_) & 1)) {
|
||||
return LFS_ERR_INVAL;
|
||||
}
|
||||
|
||||
*crc = lfs_crc32c(crc_, buffer, i);
|
||||
}
|
||||
|
||||
// convert to in-device tag repr, we want the valid bit in the sign bit
|
||||
tag_ >>= 1;
|
||||
*tag = tag_;
|
||||
*size = size_;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int lfs_rbyd_fetch(lfs_t *lfs, lfs_rbyd_t *rbyd, lfs_block_t block) {
|
||||
// read the revision count and get the crc started
|
||||
uint32_t rev;
|
||||
int err = lfs_bd_read(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, 0, &rev, sizeof(uint32_t));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// calculate crc before endian conversion
|
||||
uint32_t crc = lfs_crc32c(0, &rev, sizeof(uint32_t));
|
||||
lfs_off_t off = sizeof(uint32_t);
|
||||
lfs_off_t trunk = 0;
|
||||
bool wastrunk = false;
|
||||
|
||||
rbyd->block = block;
|
||||
rbyd->rev = lfs_fromle32_(&rev);
|
||||
rbyd->trunk = 0;
|
||||
rbyd->noff = 0;
|
||||
|
||||
// assume unerased until proven otherwise
|
||||
bool maybeerased = false;
|
||||
bool hasfcrc = false;
|
||||
struct lfs_rfcrc fcrc;
|
||||
|
||||
// scan tags, checking valid bits, crcs, etc
|
||||
while (true) {
|
||||
lfs_rtag_t tag;
|
||||
lfs_size_t size;
|
||||
lfs_ssize_t delta = lfs_rbyd_readtag(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, 0, &tag, &size, &crc);
|
||||
if (delta < 0) {
|
||||
if (delta == LFS_ERR_INVAL
|
||||
|| delta == LFS_ERR_CORRUPT
|
||||
|| delta == LFS_ERR_OVERFLOW) {
|
||||
maybeerased = (delta == LFS_ERR_INVAL);
|
||||
break;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
// found trunk of tree?
|
||||
if (!wastrunk && (lfs_rtag_isalt(tag) || lfs_rtag_intree(tag))) {
|
||||
trunk = off;
|
||||
wastrunk = true;
|
||||
}
|
||||
|
||||
off += delta;
|
||||
|
||||
// we mostly just skip alt pointers here
|
||||
if (lfs_rtag_isalt(tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// trunk ends at non-alt tag
|
||||
wastrunk = false;
|
||||
|
||||
// tag goes out of range?
|
||||
if (off + size > lfs->cfg->block_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
// not an end-of-commit crc
|
||||
if ((lfs_rtag_type1(tag) & ~0x8) != LFS_TYPE1_CRC0) {
|
||||
// fcrc is only valid if the last tag was a crc
|
||||
hasfcrc = false;
|
||||
|
||||
// crc the entry first, hopefully leaving it in the cache
|
||||
err = lfs_bd_crc32c(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, off, size, &crc);
|
||||
if (err) {
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
// found an fcrc?
|
||||
if (lfs_rtag_type1(tag) == LFS_TYPE1_FCRC) {
|
||||
err = lfs_bd_read(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, off, &fcrc,
|
||||
lfs_min(size, sizeof(struct lfs_rfcrc)));
|
||||
if (err) {
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_ssize_t delta = lfs_rfcrc_fromdisk(&fcrc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
hasfcrc = true;
|
||||
}
|
||||
// is an end-of-commit crc
|
||||
} else {
|
||||
uint32_t crc_ = 0;
|
||||
err = lfs_bd_read(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, off, &crc_, sizeof(uint32_t));
|
||||
if (err) {
|
||||
if (err == LFS_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
crc_ = lfs_fromle32_(&crc_);
|
||||
|
||||
if (crc != crc_) {
|
||||
// uh oh, crcs don't match
|
||||
break;
|
||||
}
|
||||
|
||||
// toss our crc into the filesystem seed for
|
||||
// pseudorandom numbers, note we use another crc here
|
||||
// as a collection function because it is sufficiently
|
||||
// random and convenient
|
||||
lfs->seed = lfs_crc32c(lfs->seed, &crc, sizeof(uint32_t));
|
||||
|
||||
// save what we've found so far
|
||||
rbyd->trunk = trunk;
|
||||
rbyd->noff = off;
|
||||
rbyd->crc = crc;
|
||||
}
|
||||
}
|
||||
|
||||
// no valid commits at all?
|
||||
if (rbyd->noff == 0) {
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// did we end on a valid commit? we may have an erased block
|
||||
rbyd->erased = false;
|
||||
if (maybeerased && hasfcrc && rbyd->noff % lfs->cfg->prog_size == 0) {
|
||||
// check for an fcrc matching the next prog's erased state, if
|
||||
// this failed most likely a previous prog was interrupted, we
|
||||
// need a new erase
|
||||
uint32_t fcrc_ = 0xffffffff;
|
||||
int err = lfs_bd_crc32c(lfs,
|
||||
NULL, &lfs->rcache, lfs->cfg->block_size,
|
||||
rbyd->block, rbyd->noff, fcrc.size, &fcrc_);
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// found beginning of erased part?
|
||||
rbyd->erased = (fcrc_ == fcrc.crc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//static lfs_ssize_t lfs_rbyd_lookup(lfs_t *lfs, lfs_rbyd_t *rbyd,
|
||||
// lfs_rtag_t tag, lfs_off_t *off, lfs_rtag_t *ntag) {
|
||||
// // TODO
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
static int lfs_rbyd_prog(lfs_t *lfs,
|
||||
lfs_cache_t *pcache, lfs_cache_t *rcache,
|
||||
lfs_block_t block, lfs_off_t off,
|
||||
const void *buffer, lfs_size_t size,
|
||||
uint32_t *crc) {
|
||||
int err = lfs_bd_prog(lfs,
|
||||
pcache, rcache, false,
|
||||
block, off, buffer, size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// optionally crc
|
||||
if (crc) {
|
||||
*crc = lfs_crc32c(*crc, buffer, size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static lfs_ssize_t lfs_rbyd_progtag(lfs_t *lfs,
|
||||
lfs_cache_t *pcache, lfs_cache_t *rcache,
|
||||
lfs_block_t block, lfs_off_t off,
|
||||
lfs_rtag_t tag, lfs_size_t size, uint32_t *crc) {
|
||||
// convert to on-disk repr
|
||||
tag <<= 1;
|
||||
|
||||
// make sure to include the parity of the current crc
|
||||
uint32_t crc_ = *crc;
|
||||
tag |= lfs_popc(crc_) & 1;
|
||||
|
||||
// compress into pair of leb128s
|
||||
uint8_t buffer[2*5];
|
||||
lfs_size_t i = 0;
|
||||
|
||||
ssize_t delta = lfs_toleb128(tag, &buffer[i], 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
i += delta;
|
||||
|
||||
delta = lfs_toleb128(size, &buffer[i], 5);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
i += delta;
|
||||
|
||||
int err = lfs_rbyd_prog(lfs,
|
||||
pcache, rcache,
|
||||
block, off, &buffer, i, crc);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// crc
|
||||
*crc = lfs_crc32c(crc_, buffer, i);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static lfs_ssize_t lfs_rbyd_p_push(lfs_t *lfs,
|
||||
lfs_cache_t *pcache, lfs_cache_t *rcache,
|
||||
lfs_block_t block, lfs_off_t off,
|
||||
lfs_rtag_t p_alts[static 3],
|
||||
lfs_off_t p_jumps[static 3],
|
||||
lfs_rtag_t alt, lfs_off_t jump, uint32_t *crc) {
|
||||
lfs_ssize_t delta = 0;
|
||||
|
||||
// too many alts, need to write one out?
|
||||
if (p_alts[2]) {
|
||||
// change to relative jump at the last minute
|
||||
lfs_rtag_t alt_ = p_alts[2];
|
||||
lfs_off_t jump_ = off - p_jumps[2];
|
||||
|
||||
delta = lfs_rbyd_progtag(lfs,
|
||||
pcache, rcache,
|
||||
block, off,
|
||||
alt_, jump_, crc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
|
||||
// push the alts
|
||||
p_alts[2] = p_alts[1];
|
||||
p_jumps[2] = p_jumps[1];
|
||||
p_alts[1] = p_alts[0];
|
||||
p_jumps[1] = p_jumps[0];
|
||||
p_alts[0] = alt;
|
||||
p_jumps[0] = jump;
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
static lfs_ssize_t lfs_rbyd_p_flush(lfs_t *lfs,
|
||||
lfs_cache_t *pcache, lfs_cache_t *rcache,
|
||||
lfs_block_t block, lfs_off_t off,
|
||||
lfs_rtag_t p_alts[static 3],
|
||||
lfs_rtag_t p_jumps[static 3], uint32_t *crc) {
|
||||
lfs_ssize_t delta = 0;
|
||||
|
||||
for (unsigned i = 0; i < 3; i++) {
|
||||
lfs_ssize_t delta_ = lfs_rbyd_p_push(lfs,
|
||||
pcache, rcache,
|
||||
block, off,
|
||||
p_alts, p_jumps, 0, 0, crc);
|
||||
if (delta_ < 0) {
|
||||
return delta_;
|
||||
}
|
||||
delta += delta_;
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
static void lfs_rbyd_p_red(
|
||||
lfs_rtag_t p_alts[static 3],
|
||||
lfs_off_t p_jumps[static 3]) {
|
||||
LFS_ASSERT(lfs_rtag_isblack(p_alts[0]));
|
||||
|
||||
// recolor with red edge
|
||||
if (p_alts[1]) {
|
||||
p_alts[1] = lfs_rtag_red(p_alts[1]);
|
||||
|
||||
// reorder so that top two edges always go in the same direction
|
||||
if (p_alts[2] && lfs_rtag_isred(p_alts[2])) {
|
||||
if (lfs_rtag_parallel(p_alts[1], p_alts[2])) {
|
||||
// no reorder needed
|
||||
} else if (lfs_rtag_parallel(p_alts[0], p_alts[2])) {
|
||||
lfs_rtag_t alt_ = p_alts[1];
|
||||
lfs_off_t jump_ = p_jumps[1];
|
||||
p_alts[1] = p_alts[0];
|
||||
p_jumps[1] = p_jumps[0];
|
||||
p_alts[0] = alt_;
|
||||
p_jumps[0] = jump_;
|
||||
} else if (lfs_rtag_parallel(p_alts[0], p_alts[1])) {
|
||||
lfs_rtag_t alt_ = p_alts[2];
|
||||
lfs_off_t jump_ = p_jumps[2];
|
||||
p_alts[2] = p_alts[1];
|
||||
p_jumps[2] = p_jumps[1];
|
||||
p_alts[1] = p_alts[0];
|
||||
p_jumps[1] = p_jumps[0];
|
||||
p_alts[0] = alt_;
|
||||
p_jumps[0] = jump_;
|
||||
} else {
|
||||
LFS_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd,
|
||||
const struct lfs_rattr *attrs) {
|
||||
LFS_ASSERT(rbyd->erased);
|
||||
|
||||
// setup commit state
|
||||
const lfs_block_t block = rbyd->block;
|
||||
lfs_off_t trunk = rbyd->trunk;
|
||||
uint16_t count = rbyd->count;
|
||||
lfs_off_t off = rbyd->noff;
|
||||
uint32_t crc = rbyd->crc;
|
||||
bool erased = false;
|
||||
|
||||
// mark as unerased in case we fail
|
||||
rbyd->erased = false;
|
||||
|
||||
// include revision count?
|
||||
if (!off) {
|
||||
uint32_t rev;
|
||||
lfs_tole32_(rbyd->rev, &rev);
|
||||
int err = lfs_rbyd_prog(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off, &rev, sizeof(uint32_t), &crc);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
off += sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// append each tag to the tree
|
||||
for (const struct lfs_rattr *attr = attrs; attr; attr = attr->next) {
|
||||
// assume we'll update our trunk
|
||||
lfs_off_t branch = trunk;
|
||||
trunk = off;
|
||||
|
||||
// no trunk yet?
|
||||
if (!branch) {
|
||||
goto leaf;
|
||||
}
|
||||
|
||||
// weights for pruning
|
||||
lfs_rtag_t lo = lfs_rtag_weight(attr->tag);
|
||||
lfs_rtag_t hi = ((count << 12)+0xfff)-1 - lfs_rtag_weight(attr->tag);
|
||||
printf("lo, hi = (%x, %x)\n", lo, hi);
|
||||
|
||||
// queue of pending alts we can emulate rotations with
|
||||
lfs_rtag_t p_alts[3] = {0, 0, 0};
|
||||
lfs_off_t p_jumps[3] = {0, 0, 0};
|
||||
|
||||
// descend down tree, building alt pointers
|
||||
while (true) {
|
||||
lfs_rtag_t alt;
|
||||
lfs_off_t jump;
|
||||
lfs_ssize_t delta = lfs_rbyd_readtag(lfs,
|
||||
&lfs->pcache, &lfs->rcache, lfs->cfg->block_size,
|
||||
block, branch, &alt, &jump, NULL);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
// make jump absolute
|
||||
jump = branch - jump;
|
||||
|
||||
// found an alt?
|
||||
if (lfs_rtag_isalt(alt)) {
|
||||
LFS_ASSERT(false); // TODO
|
||||
|
||||
// // prune?
|
||||
// if (lfs_rtag_weight(alt) >= lo+hi+1) {
|
||||
// LFS_ASSERT(p_alts[0]);
|
||||
// alt = lfs_rtag_black(p_alts[0]);
|
||||
// jump = p_jumps[0];
|
||||
// branch = ?;
|
||||
// lfs_rbyd_p_pop(p_alts, p_jumps);
|
||||
// }
|
||||
//
|
||||
// // split?
|
||||
// if (p_alts[0] && lfs_rtag_isred(alt) && lfs_rtag_isred(p_alts[0])) {
|
||||
// LFS_ASSERT(lfs_rtag_parallel(alt, p_alts[0]));
|
||||
// if (lfs_rbyd_follow(lo, hi, alt)) {
|
||||
// lfs_rbyd_trim(&lo, &hi, alt[0]);
|
||||
// lfs_rbyd_trim(&lo, &hi, alt);
|
||||
// lfs_rtag_t alt_ = lfs_rtag_black(p_alts[0]);
|
||||
// lfs_off_t jump_ = p_jumps[0];
|
||||
//
|
||||
//
|
||||
// p_jumps[0] = branch;
|
||||
//
|
||||
//
|
||||
// lfs_rbyd_p_red(p_alts, p_jumps);
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// p_alts[0] = lfs_rbyd_black(p_alts[0]);
|
||||
// p_jumps[0] = ?; // TODO where does this come from?
|
||||
// lfs_rbyd_p_red(p_alts, p_jumps);
|
||||
// lfs_rbyd_trim(&lo, &hi, alt);
|
||||
//
|
||||
// branch += delta;
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
//branch += delta;
|
||||
|
||||
// found end of tree?
|
||||
} else {
|
||||
// split leaf?
|
||||
if (alt != attr->tag) {
|
||||
lfs_rtag_t alt_;
|
||||
if (lfs_rtag_weight(alt) < lfs_rtag_weight(attr->tag)) {
|
||||
alt_ = LFS_MKRALT(B, LT, lo);
|
||||
} else {
|
||||
alt_ = LFS_MKRALT(B, GT, hi);
|
||||
}
|
||||
|
||||
lfs_ssize_t delta = lfs_rbyd_p_push(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
p_alts, p_jumps, alt_, branch, &crc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
off += delta;
|
||||
|
||||
lfs_rbyd_p_red(p_alts, p_jumps);
|
||||
}
|
||||
|
||||
// flush any pending alts
|
||||
delta = lfs_rbyd_p_flush(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
p_alts, p_jumps, &crc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
off += delta;
|
||||
|
||||
// done! lets get out of here
|
||||
goto leaf;
|
||||
}
|
||||
}
|
||||
|
||||
leaf:;
|
||||
// write the tag
|
||||
lfs_ssize_t delta = lfs_rbyd_progtag(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
attr->tag, attr->size, &crc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
off += delta;
|
||||
|
||||
// don't forget the actual data!
|
||||
int err = lfs_rbyd_prog(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
attr->buffer, attr->size, &crc);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
off += attr->size;
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// align to the next prog unit
|
||||
//
|
||||
// this gets a bit complicated as we have two types of crcs:
|
||||
// - 7-word crc with fcrc to check following prog (middle of block)
|
||||
// 1-byte fcrc tag
|
||||
// + 1-byte fcrc len (worst case)
|
||||
// + 4-byte fcrc crc
|
||||
// + 5-byte fcrc crc-len (worst case)
|
||||
// + 1-byte crc tag
|
||||
// + 5-byte crc len (worst case)
|
||||
// + 4-byte crc crc
|
||||
// = 21 bytes
|
||||
// - 3-word crc with no following prog (end of block)
|
||||
// 1-byte crc tag
|
||||
// + 5-byte crc len (worst case)
|
||||
// + 4-byte crc crc
|
||||
// = 10 bytes
|
||||
//
|
||||
const lfs_off_t aligned = lfs_alignup(
|
||||
lfs_min(off + 1+1+4+5 + 1+5+4, lfs->cfg->block_size),
|
||||
lfs->cfg->prog_size);
|
||||
|
||||
// space for fcrc?
|
||||
uint8_t perturb = 0;
|
||||
if (aligned <= lfs->cfg->block_size - lfs->cfg->prog_size) {
|
||||
// read the leading byte in case we need to change the expected
|
||||
// value of the next tag's valid bit
|
||||
int err = lfs_bd_read(lfs,
|
||||
&lfs->pcache, &lfs->rcache, lfs->cfg->prog_size,
|
||||
block, aligned, &perturb, 1);
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// find the expected fcrc, don't bother avoiding a reread of the
|
||||
// perturb byte, as it should still be in our cache
|
||||
struct lfs_rfcrc fcrc = {.crc=0, .size=lfs->cfg->prog_size};
|
||||
err = lfs_bd_crc32c(lfs,
|
||||
&lfs->pcache, &lfs->rcache, lfs->cfg->prog_size,
|
||||
block, aligned, fcrc.size, &fcrc.crc);
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_size_t fcrc_delta = lfs_rfcrc_todisk(&fcrc);
|
||||
lfs_ssize_t delta = lfs_rbyd_progtag(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
LFS_MKRTAG(FCRC, 0, 0), fcrc_delta, &crc);
|
||||
if (delta < 0) {
|
||||
return delta;
|
||||
}
|
||||
off += delta;
|
||||
|
||||
err = lfs_rbyd_prog(lfs,
|
||||
&lfs->pcache, &lfs->rcache,
|
||||
block, off,
|
||||
&fcrc, fcrc_delta, &crc);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
off += fcrc_delta;
|
||||
|
||||
erased = true;
|
||||
}
|
||||
|
||||
// build end-of-commit crc
|
||||
//
|
||||
// note padding-size depends on leb-encoding depends on padding-size, to
|
||||
// get around this catch-22 we just always write a fully-expanded leb128
|
||||
// encoding
|
||||
uint8_t buffer[1+5+4];
|
||||
buffer[0] = LFS_MKRTAG(CRC0, 0, 0) << 1;
|
||||
|
||||
lfs_off_t padding = aligned - (off + 1+5);
|
||||
buffer[1] = 0x80 | (0x7f & (padding >> 0));
|
||||
buffer[2] = 0x80 | (0x7f & (padding >> 7));
|
||||
buffer[3] = 0x80 | (0x7f & (padding >> 14));
|
||||
buffer[4] = 0x00 | (0x7f & (padding >> 21));
|
||||
buffer[5] = 0x00 | (0x7f & (padding >> 28));
|
||||
|
||||
crc = lfs_crc32c(crc, buffer, 1+5);
|
||||
// we can't let the next tag appear as valid, so intentionally perturb the
|
||||
// commit if this happens, note parity(crc(m)) == parity(m) with crc32c,
|
||||
// so we can really change any bit to make this happen, we've reserved a bit
|
||||
// in crc tags just for this purpose
|
||||
if ((lfs_popc(crc) & 1) == (perturb & 1)) {
|
||||
buffer[0] ^= 0x10;
|
||||
crc ^= 0xc00c303e; // note crc(a ^ b) == crc(a) ^ crc(b)
|
||||
}
|
||||
lfs_tole32_(crc, &buffer[1+5]);
|
||||
|
||||
int err = lfs_bd_prog(lfs,
|
||||
&lfs->pcache, &lfs->rcache, false,
|
||||
block, off, buffer, 1+5+4);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
off += 1+5+4;
|
||||
|
||||
// flush our caches, finalizing the commit on-disk
|
||||
err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// succesful commit, check checksum to make sure
|
||||
uint32_t crc_ = 0;
|
||||
err = lfs_bd_crc32c(lfs,
|
||||
NULL, &lfs->rcache, off-4,
|
||||
block, rbyd->noff, off-4 - rbyd->noff, &crc_);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
printf("%08x == %08x\n", crc_, crc);
|
||||
assert(crc_ == crc);
|
||||
|
||||
if (crc_ != crc) {
|
||||
// oh no, something went wrong
|
||||
return LFS_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// ok, everything is good, save what we've committed
|
||||
rbyd->trunk = trunk;
|
||||
rbyd->noff = aligned;
|
||||
rbyd->crc = crc;
|
||||
rbyd->erased = erased;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// Metadata pair and directory operations ///
|
||||
static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
|
||||
lfs_tag_t gmask, lfs_tag_t gtag,
|
||||
|
||||
@@ -84,6 +84,7 @@ enum lfs_error {
|
||||
LFS_ERR_NOMEM = -12, // No more memory available
|
||||
LFS_ERR_NOATTR = -61, // No data/attr available
|
||||
LFS_ERR_NAMETOOLONG = -36, // File name too long
|
||||
LFS_ERR_OVERFLOW = -75, // Value too large for defined data type
|
||||
};
|
||||
|
||||
// File types
|
||||
@@ -326,6 +327,16 @@ typedef struct lfs_cache {
|
||||
uint8_t *buffer;
|
||||
} lfs_cache_t;
|
||||
|
||||
typedef struct lfs_rbyd {
|
||||
lfs_block_t block;
|
||||
lfs_off_t trunk;
|
||||
lfs_off_t noff;
|
||||
uint32_t rev;
|
||||
uint32_t crc;
|
||||
uint16_t count;
|
||||
bool erased;
|
||||
} lfs_rbyd_t;
|
||||
|
||||
typedef struct lfs_mdir {
|
||||
lfs_block_t pair[2];
|
||||
uint32_t rev;
|
||||
|
||||
+39
@@ -10,6 +10,45 @@
|
||||
// Only compile if user does not provide custom config
|
||||
#ifndef LFS_CONFIG
|
||||
|
||||
// Need lfs.h for error codes
|
||||
// TODO should we actually move the error codes to lfs_util.h?
|
||||
#include "lfs.h"
|
||||
|
||||
|
||||
// Convert to/from leb128 encoding
|
||||
ssize_t lfs_toleb128(uint32_t word, void *buffer, size_t size) {
|
||||
uint8_t *data = buffer;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
uint8_t dat = word & 0x7f;
|
||||
word >>= 7;
|
||||
if (word != 0) {
|
||||
data[i] = dat | 0x80;
|
||||
} else {
|
||||
data[i] = dat | 0x00;
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
|
||||
return LFS_ERR_OVERFLOW;
|
||||
}
|
||||
|
||||
ssize_t lfs_fromleb128(uint32_t *word, const void *buffer, size_t size) {
|
||||
const uint8_t *data = buffer;
|
||||
|
||||
uint32_t word_ = 0;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
uint8_t dat = data[i];
|
||||
word_ |= (dat & 0x7f) << 7*i;
|
||||
if (!(dat & 0x80)) {
|
||||
*word = word_;
|
||||
return i+1;
|
||||
}
|
||||
}
|
||||
|
||||
return LFS_ERR_OVERFLOW;
|
||||
}
|
||||
|
||||
|
||||
// Software CRC implementation with small lookup table
|
||||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
|
||||
|
||||
+22
@@ -212,6 +212,28 @@ static inline uint32_t lfs_tobe32(uint32_t a) {
|
||||
return lfs_frombe32(a);
|
||||
}
|
||||
|
||||
// Convert to/from 32-bit little-endian
|
||||
static inline void lfs_tole32_(uint32_t word, void *buffer) {
|
||||
((uint8_t*)buffer)[0] = word >> 0;
|
||||
((uint8_t*)buffer)[1] = word >> 8;
|
||||
((uint8_t*)buffer)[2] = word >> 16;
|
||||
((uint8_t*)buffer)[3] = word >> 24;
|
||||
}
|
||||
|
||||
static inline uint32_t lfs_fromle32_(const void *buffer) {
|
||||
return (((uint8_t*)buffer)[0] << 0)
|
||||
| (((uint8_t*)buffer)[1] << 8)
|
||||
| (((uint8_t*)buffer)[2] << 16)
|
||||
| (((uint8_t*)buffer)[3] << 24);
|
||||
}
|
||||
|
||||
// Convert to/from leb128 encoding
|
||||
ssize_t lfs_toleb128(uint32_t word, void *buffer, size_t size);
|
||||
|
||||
ssize_t lfs_fromleb128(uint32_t *word, const void *buffer, size_t size);
|
||||
|
||||
|
||||
|
||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
# Test this inner rbyd data-structure
|
||||
|
||||
[cases.rbyd_create]
|
||||
in = 'lfs.c'
|
||||
code = '''
|
||||
lfs_t lfs;
|
||||
lfs_init(&lfs, cfg) => 0;
|
||||
|
||||
lfs_rbyd_t rbyd = {
|
||||
.block = 0,
|
||||
.trunk = 0,
|
||||
.noff = 0,
|
||||
.rev = 1,
|
||||
.crc = 0,
|
||||
.count = 0,
|
||||
.erased = true,
|
||||
};
|
||||
|
||||
//lfs_rbyd_commit(&lfs, &rbyd, NULL) => 0;
|
||||
|
||||
uint32_t data = 0xa5dfa5df;
|
||||
lfs_rbyd_commit(&lfs, &rbyd,
|
||||
LFS_MKRATTR(UATTR, 1, 0, &data, 4,
|
||||
LFS_MKRATTR(UATTR, 2, 0, &data, 4,
|
||||
NULL))) => 0;
|
||||
|
||||
'''
|
||||
Reference in New Issue
Block a user