mirror of
https://github.com/eledio-devices/thirdparty-littlefs.git
synced 2025-11-01 16:14:13 +01:00
WIP added wip journalling things for dirs
This commit is contained in:
901
lfs.c
901
lfs.c
@@ -221,9 +221,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
|
|||||||
/// General lfs block device operations ///
|
/// General lfs block device operations ///
|
||||||
static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
|
static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
|
||||||
lfs_off_t off, void *buffer, lfs_size_t size) {
|
lfs_off_t off, void *buffer, lfs_size_t size) {
|
||||||
// if we ever do more than writes to alternating pairs,
|
return lfs_cache_read(lfs, &lfs->rcache, &lfs->pcache,
|
||||||
// this may need to consider pcache
|
|
||||||
return lfs_cache_read(lfs, &lfs->rcache, NULL,
|
|
||||||
block, off, buffer, size);
|
block, off, buffer, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,6 +425,903 @@ static inline bool lfs_pairsync(
|
|||||||
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LFS_TAG_VALID = (int)0x80000000,
|
||||||
|
LFS_TAG_TYPE = (int)0x7fc00000,
|
||||||
|
LFS_TAG_ID = (int)0x001ff000,
|
||||||
|
LFS_TAG_SIZE = (int)0x00000fff,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline uint32_t lfs_mktag(uint16_t type, uint16_t id, uint16_t size) {
|
||||||
|
return (type << 22) | (id << 12) | size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool lfs_tag_valid(uint32_t tag) {
|
||||||
|
return !(tag & 0x80000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t lfs_tag_uid(uint32_t tag) {
|
||||||
|
return (tag & 0x7fdff000) >> 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t lfs_tag_type(uint32_t tag) {
|
||||||
|
return (tag & 0x7fc00000) >> 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t lfs_tag_id(uint32_t tag) {
|
||||||
|
return (tag & 0x001ff000) >> 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t lfs_tag_size(uint32_t tag) {
|
||||||
|
return tag & 0x00000fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_region__ {
|
||||||
|
uint32_t tag;
|
||||||
|
union {
|
||||||
|
void *buffer;
|
||||||
|
struct {
|
||||||
|
lfs_block_t block;
|
||||||
|
lfs_off_t off;
|
||||||
|
} d;
|
||||||
|
} u;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lfs_commit {
|
||||||
|
lfs_block_t block;
|
||||||
|
lfs_off_t off;
|
||||||
|
lfs_off_t begin;
|
||||||
|
lfs_off_t end;
|
||||||
|
|
||||||
|
uint32_t ptag;
|
||||||
|
uint32_t crc;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int16_t id;
|
||||||
|
uint16_t type;
|
||||||
|
} compact;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lfs_commit_traverse(lfs_t *lfs, struct lfs_commit *commit,
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region),
|
||||||
|
void *data) {
|
||||||
|
// iterate over dir block backwards (for faster lookups)
|
||||||
|
lfs_block_t block = commit->block;
|
||||||
|
lfs_off_t off = commit->off;
|
||||||
|
uint32_t tag = commit->ptag;
|
||||||
|
|
||||||
|
while (off != sizeof(uint32_t)) {
|
||||||
|
//printf("read %#010x at %x:%x\n", tag, block, off);
|
||||||
|
int err = cb(lfs, data, (struct lfs_region__){
|
||||||
|
.tag=(0x80000000 | tag),
|
||||||
|
.u.d.block=block,
|
||||||
|
.u.d.off=off-lfs_tag_size(tag)});
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
LFS_ASSERT(off > sizeof(uint32_t)+lfs_tag_size(tag));
|
||||||
|
off -= sizeof(uint32_t)+lfs_tag_size(tag);
|
||||||
|
|
||||||
|
uint32_t ntag;
|
||||||
|
err = lfs_bd_read(lfs, block, off, &ntag, sizeof(ntag));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag ^= lfs_fromle32(ntag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfs_commit_compactcheck(lfs_t *lfs, void *p,
|
||||||
|
struct lfs_region__ region) {
|
||||||
|
struct lfs_commit *commit = p;
|
||||||
|
if (lfs_tag_id(region.tag) != commit->compact.id) {
|
||||||
|
return 1;
|
||||||
|
} else if (lfs_tag_type(region.tag) == commit->compact.type) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfs_commit_commit(lfs_t *lfs,
|
||||||
|
struct lfs_commit *commit, struct lfs_region__ region) {
|
||||||
|
// request for compaction?
|
||||||
|
if (commit->compact.id >= 0) {
|
||||||
|
if (lfs_tag_id(region.tag) != commit->compact.id) {
|
||||||
|
// ignore non-matching ids
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
commit->compact.type = lfs_tag_type(region.tag);
|
||||||
|
int res = lfs_commit_traverse(lfs, commit,
|
||||||
|
lfs_commit_compactcheck, commit);
|
||||||
|
//printf("traverse(%d, %#010x) -> %d\n", commit->compact.id, region.tag, res);
|
||||||
|
if (res < 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 2) {
|
||||||
|
//printf("ignoring %#010x at %x:%x\n", region.tag, commit->block, commit->off);
|
||||||
|
// already committed
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we fit
|
||||||
|
lfs_size_t size = lfs_tag_size(region.tag);
|
||||||
|
//printf("writing %#010x at %x:%x\n", region.tag, commit->block, commit->off);
|
||||||
|
if (commit->off + sizeof(uint32_t)+size > commit->end) {
|
||||||
|
return LFS_ERR_NOSPC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out tag
|
||||||
|
uint32_t tag = lfs_tole32((region.tag & 0x7fffffff) ^ commit->ptag);
|
||||||
|
lfs_crc(&commit->crc, &tag, sizeof(tag));
|
||||||
|
int err = lfs_bd_prog(lfs, commit->block, commit->off, &tag, sizeof(tag));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
commit->off += sizeof(tag);
|
||||||
|
|
||||||
|
if (!(region.tag & 0x80000000)) {
|
||||||
|
// from memory
|
||||||
|
lfs_crc(&commit->crc, region.u.buffer, size);
|
||||||
|
err = lfs_bd_prog(lfs, commit->block, commit->off,
|
||||||
|
region.u.buffer, size);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// from disk
|
||||||
|
for (lfs_off_t i = 0; i < size; i++) {
|
||||||
|
uint8_t dat;
|
||||||
|
int err = lfs_bd_read(lfs,
|
||||||
|
region.u.d.block, region.u.d.off+i, &dat, 1);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_crc(&commit->crc, &dat, 1);
|
||||||
|
err = lfs_bd_prog(lfs, commit->block, commit->off+i, &dat, 1);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commit->off += size;
|
||||||
|
commit->ptag = region.tag;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) {
|
||||||
|
// align to program units
|
||||||
|
lfs_off_t noff = lfs_alignup(
|
||||||
|
commit->off + 2*sizeof(uint32_t), lfs->cfg->prog_size);
|
||||||
|
|
||||||
|
// read erased state from next program unit
|
||||||
|
uint32_t tag;
|
||||||
|
int err = lfs_bd_read(lfs, commit->block, noff, &tag, sizeof(tag));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build crc tag
|
||||||
|
tag = (0x80000000 & ~lfs_fromle32(tag)) |
|
||||||
|
lfs_mktag(LFS_TYPE_CRC_, 0x1ff,
|
||||||
|
noff - (commit->off+sizeof(uint32_t)));
|
||||||
|
|
||||||
|
// write out crc
|
||||||
|
uint32_t footer[2];
|
||||||
|
footer[0] = lfs_tole32(tag ^ commit->ptag);
|
||||||
|
lfs_crc(&commit->crc, &footer[0], sizeof(footer[0]));
|
||||||
|
footer[1] = lfs_tole32(commit->crc);
|
||||||
|
err = lfs_bd_prog(lfs, commit->block, commit->off,
|
||||||
|
footer, sizeof(footer));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
commit->off += sizeof(uint32_t)+lfs_tag_size(tag);
|
||||||
|
commit->ptag = tag;
|
||||||
|
|
||||||
|
// flush buffers
|
||||||
|
err = lfs_bd_sync(lfs);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful commit, check checksum to make sure
|
||||||
|
uint32_t crc = 0xffffffff;
|
||||||
|
err = lfs_bd_crc(lfs, commit->block, commit->begin,
|
||||||
|
commit->off-lfs_tag_size(tag) - commit->begin, &crc);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crc != commit->crc) {
|
||||||
|
return LFS_ERR_CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_alloc_(lfs_t *lfs, lfs_dir_t_ *dir) {
|
||||||
|
// allocate pair of dir blocks (backwards, so we write to block 1 first)
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rather than clobbering one of the blocks we just pretend
|
||||||
|
// the revision may be valid
|
||||||
|
int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->rev, 4);
|
||||||
|
dir->rev = lfs_fromle32(dir->rev);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set defaults
|
||||||
|
dir->off = sizeof(dir->rev);
|
||||||
|
dir->etag = 0;
|
||||||
|
dir->count = 0;
|
||||||
|
dir->erased = false;
|
||||||
|
dir->tail[0] = 0xffffffff;
|
||||||
|
dir->tail[1] = 0xffffffff;
|
||||||
|
|
||||||
|
// don't write out yet, let caller take care of that
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_fetch_(lfs_t *lfs,
|
||||||
|
lfs_dir_t_ *dir, const lfs_block_t pair[2],
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region),
|
||||||
|
void *data) {
|
||||||
|
dir->pair[0] = pair[0];
|
||||||
|
dir->pair[1] = pair[1];
|
||||||
|
|
||||||
|
// find the block with the most recent revision
|
||||||
|
uint32_t rev[2];
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int err = lfs_bd_read(lfs, dir->pair[i], 0, &rev[i], sizeof(rev[i]));
|
||||||
|
rev[i] = lfs_fromle32(rev[i]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_scmp(rev[1], rev[0]) > 0) {
|
||||||
|
lfs_pairswap(dir->pair);
|
||||||
|
lfs_pairswap(rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load blocks and check crc
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
lfs_off_t off = sizeof(dir->rev);
|
||||||
|
uint32_t ptag = 0;
|
||||||
|
uint32_t crc = 0xffffffff;
|
||||||
|
|
||||||
|
dir->rev = lfs_tole32(rev[0]);
|
||||||
|
lfs_crc(&crc, &dir->rev, sizeof(dir->rev));
|
||||||
|
dir->rev = lfs_fromle32(dir->rev);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// extract next tag
|
||||||
|
uint32_t tag;
|
||||||
|
int err = lfs_bd_read(lfs, dir->pair[0], off, &tag, sizeof(tag));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
lfs_crc(&crc, &tag, sizeof(tag));
|
||||||
|
tag = lfs_fromle32(tag) ^ ptag;
|
||||||
|
printf("tag %#010x (%x:%x %#010x)\n", tag, dir->pair[0], off, lfs_tag_type(ptag));
|
||||||
|
|
||||||
|
// next commit not yet programmed
|
||||||
|
if (lfs_tag_type(ptag) == LFS_TYPE_CRC_ && lfs_tag_valid(tag)) {
|
||||||
|
dir->erased = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check we're in valid range
|
||||||
|
if (off + sizeof(uint32_t)+lfs_tag_size(tag) >
|
||||||
|
lfs->cfg->block_size - 2*sizeof(uint32_t)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_tag_type(tag) == LFS_TYPE_CRC_) {
|
||||||
|
// check the crc entry
|
||||||
|
uint32_t dcrc;
|
||||||
|
int err = lfs_bd_read(lfs, dir->pair[0],
|
||||||
|
off+sizeof(uint32_t), &dcrc, sizeof(dcrc));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crc != lfs_fromle32(dcrc)) {
|
||||||
|
if (off == sizeof(dir->rev)) {
|
||||||
|
// try other block
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// consider what we have good enough
|
||||||
|
dir->erased = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->off = off + sizeof(uint32_t)+lfs_tag_size(tag);
|
||||||
|
dir->etag = tag;
|
||||||
|
crc = 0xffffffff;
|
||||||
|
} else {
|
||||||
|
err = lfs_bd_crc(lfs, dir->pair[0],
|
||||||
|
off+sizeof(uint32_t), lfs_tag_size(tag), &crc);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
err = cb(lfs, data, (struct lfs_region__){
|
||||||
|
.tag=(tag | 0x80000000),
|
||||||
|
.u.d.block=dir->pair[0],
|
||||||
|
.u.d.off=off+sizeof(uint32_t)});
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptag = tag;
|
||||||
|
off += sizeof(uint32_t)+lfs_tag_size(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// failed, try the other crc?
|
||||||
|
lfs_pairswap(dir->pair);
|
||||||
|
lfs_pairswap(rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
LFS_ERROR("Corrupted dir pair at %d %d", dir->pair[0], dir->pair[1]);
|
||||||
|
return LFS_ERR_CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lfs_dir_traverse2_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region),
|
||||||
|
void *data) {
|
||||||
|
return lfs_commit_traverse(lfs, &(struct lfs_commit){
|
||||||
|
.block=dir->pair[0], .off=dir->off, .ptag=dir->etag},
|
||||||
|
cb, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_dir_getter {
|
||||||
|
uint32_t tag;
|
||||||
|
uint32_t mask;
|
||||||
|
void *buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lfs_dir_getter(lfs_t *lfs, void *p, struct lfs_region__ region) {
|
||||||
|
struct lfs_dir_getter *getter = p;
|
||||||
|
if ((region.tag & getter->mask) == (getter->tag & getter->mask)) {
|
||||||
|
lfs_size_t size = lfs_tag_size(getter->tag);
|
||||||
|
if (lfs_tag_size(region.tag) > size) {
|
||||||
|
return LFS_ERR_RANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off,
|
||||||
|
getter->buffer, size);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset((uint8_t*)getter->buffer + size, 0,
|
||||||
|
lfs_tag_size(region.tag) - size);
|
||||||
|
getter->tag |= region.tag & ~0x80000fff;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int32_t lfs_dir_get_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
uint32_t tag, uint32_t mask, void *buffer) {
|
||||||
|
struct lfs_dir_getter getter = {tag, mask, buffer};
|
||||||
|
int res = lfs_dir_traverse2_(lfs, dir, lfs_dir_getter, &getter);
|
||||||
|
if (res < 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return LFS_ERR_NOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getter.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_dir_mover {
|
||||||
|
// traversal things
|
||||||
|
lfs_dir_t_ *dir;
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit);
|
||||||
|
void *data;
|
||||||
|
|
||||||
|
// ids to iterate through
|
||||||
|
uint16_t begin;
|
||||||
|
uint16_t end;
|
||||||
|
uint16_t ack;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lfs_dir_mover_commit(lfs_t *lfs, void *p,
|
||||||
|
struct lfs_region__ region) {
|
||||||
|
return lfs_commit_commit(lfs, p, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lfs_dir_mover(lfs_t *lfs, void *p, struct lfs_commit *commit) {
|
||||||
|
struct lfs_dir_mover *mover = p;
|
||||||
|
for (int i = mover->begin; i < mover->end; i++) {
|
||||||
|
// tell the committer to check for duplicates
|
||||||
|
uint16_t old = commit->compact.id;
|
||||||
|
if (commit->compact.id < 0) {
|
||||||
|
commit->compact.id = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit pending commits
|
||||||
|
int err = mover->cb(lfs, mover->data, commit);
|
||||||
|
if (err) {
|
||||||
|
commit->compact.id = old;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over on-disk regions
|
||||||
|
err = lfs_dir_traverse2_(lfs, mover->dir,
|
||||||
|
lfs_dir_mover_commit, commit);
|
||||||
|
if (err) {
|
||||||
|
commit->compact.id = old;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
mover->ack = i;
|
||||||
|
commit->compact.id = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_compact2_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit),
|
||||||
|
void *data) {
|
||||||
|
// save some state in case block is bad
|
||||||
|
const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
|
||||||
|
bool relocated = false;
|
||||||
|
|
||||||
|
// increment revision count
|
||||||
|
dir->rev += 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// setup mover
|
||||||
|
struct lfs_dir_mover mover = {
|
||||||
|
.dir = dir,
|
||||||
|
.cb = cb,
|
||||||
|
.data = data,
|
||||||
|
|
||||||
|
.begin = 0,
|
||||||
|
.end = dir->count,
|
||||||
|
.ack = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (true) {
|
||||||
|
// erase block to write to
|
||||||
|
int err = lfs_bd_erase(lfs, dir->pair[1]);
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_CORRUPT) {
|
||||||
|
goto relocate;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out header
|
||||||
|
uint32_t crc = 0xffffffff;
|
||||||
|
uint32_t rev = lfs_tole32(dir->rev);
|
||||||
|
lfs_crc(&crc, &rev, sizeof(rev));
|
||||||
|
err = lfs_bd_prog(lfs, dir->pair[1], 0, &rev, sizeof(rev));
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_CORRUPT) {
|
||||||
|
goto relocate;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup compaction
|
||||||
|
struct lfs_commit commit = {
|
||||||
|
.block = dir->pair[1],
|
||||||
|
.off = sizeof(dir->rev),
|
||||||
|
// leave space for tail pointer
|
||||||
|
.begin = 0,
|
||||||
|
.end = lfs_min(lfs->cfg->block_size - 5*sizeof(uint32_t),
|
||||||
|
lfs_alignup(lfs->cfg->block_size / 2,
|
||||||
|
lfs->cfg->prog_size)),
|
||||||
|
.crc = crc,
|
||||||
|
.ptag = 0,
|
||||||
|
.compact.id = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// run compaction over mover
|
||||||
|
err = lfs_dir_mover(lfs, &mover, &commit);
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_NOSPC) {
|
||||||
|
goto split;
|
||||||
|
} else if (err == LFS_ERR_CORRUPT) {
|
||||||
|
goto relocate;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lfs_pairisnull(dir->tail)) {
|
||||||
|
// TODO le32
|
||||||
|
commit.end = lfs->cfg->block_size - 2*sizeof(uint32_t),
|
||||||
|
err = lfs_commit_commit(lfs, &commit, (struct lfs_region__){
|
||||||
|
.tag=lfs_mktag(LFS_TYPE_TAIL_, 0x1ff,
|
||||||
|
sizeof(dir->tail)),
|
||||||
|
.u.buffer=dir->tail});
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_CORRUPT) {
|
||||||
|
goto relocate;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfs_commit_crc(lfs, &commit);
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_CORRUPT) {
|
||||||
|
goto relocate;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful compaction, swap dir pair to indicate most recent
|
||||||
|
lfs_pairswap(dir->pair);
|
||||||
|
dir->off = commit.off;
|
||||||
|
dir->etag = commit.ptag;
|
||||||
|
dir->erased = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
split:
|
||||||
|
// commit no longer fits, need to split dir
|
||||||
|
dir->count = mover.ack;
|
||||||
|
mover.begin = mover.ack+1;
|
||||||
|
|
||||||
|
// drop caches and create tail
|
||||||
|
lfs->pcache.block = 0xffffffff;
|
||||||
|
|
||||||
|
lfs_dir_t_ tail;
|
||||||
|
int err = lfs_dir_alloc_(lfs, &tail);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail.tail[0] = dir->tail[0];
|
||||||
|
tail.tail[1] = dir->tail[1];
|
||||||
|
|
||||||
|
err = lfs_dir_compact2_(lfs, &tail, lfs_dir_mover, &mover);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->tail[0] = tail.pair[0];
|
||||||
|
dir->tail[1] = tail.pair[1];
|
||||||
|
continue;
|
||||||
|
|
||||||
|
relocate:
|
||||||
|
//commit was corrupted
|
||||||
|
LFS_DEBUG("Bad block at %d", dir->pair[1]);
|
||||||
|
|
||||||
|
// drop caches and prepare to relocate block
|
||||||
|
relocated = true;
|
||||||
|
lfs->pcache.block = 0xffffffff;
|
||||||
|
|
||||||
|
// can't relocate superblock, filesystem is now frozen
|
||||||
|
if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) {
|
||||||
|
LFS_WARN("Superblock %d has become unwritable", oldpair[1]);
|
||||||
|
return LFS_ERR_CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// relocate half of pair
|
||||||
|
err = lfs_alloc(lfs, &dir->pair[1]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relocated) {
|
||||||
|
// update references if we relocated
|
||||||
|
LFS_DEBUG("Relocating %d %d to %d %d",
|
||||||
|
oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
|
||||||
|
int err = lfs_relocate(lfs, oldpair, dir->pair);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift over any directories that are affected
|
||||||
|
for (lfs_dir_t *d = lfs->dirs; d; d = d->next) {
|
||||||
|
if (lfs_paircmp(d->pair, dir->pair) == 0) {
|
||||||
|
d->pair[0] = dir->pair[0];
|
||||||
|
d->pair[1] = dir->pair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_commit2_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit),
|
||||||
|
void *data) {
|
||||||
|
if (!dir->erased) {
|
||||||
|
// not erased, must compact
|
||||||
|
return lfs_dir_compact2_(lfs, dir, cb, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_commit commit = {
|
||||||
|
.block = dir->pair[0],
|
||||||
|
.begin = dir->off,
|
||||||
|
.off = dir->off,
|
||||||
|
.end = lfs->cfg->block_size - 2*sizeof(uint32_t),
|
||||||
|
.crc = 0xffffffff,
|
||||||
|
.ptag = dir->etag,
|
||||||
|
.compact.id = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
int err = cb(lfs, data, &commit);
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
||||||
|
return lfs_dir_compact2_(lfs, dir, cb, data);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lfs_commit_crc(lfs, &commit);
|
||||||
|
if (err) {
|
||||||
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
||||||
|
return lfs_dir_compact2_(lfs, dir, cb, data);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful commit, lets update dir
|
||||||
|
dir->off = commit.off;
|
||||||
|
dir->etag = commit.ptag;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_dir_commit_regions {
|
||||||
|
const struct lfs_region__ *regions;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
int lfs_dir_commit_regions(lfs_t *lfs, void *p, struct lfs_commit *commit) {
|
||||||
|
struct lfs_dir_commit_regions *region = p;
|
||||||
|
for (int i = 0; i < region->count; i++) {
|
||||||
|
int err = lfs_commit_commit(lfs, commit, region->regions[i]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO rm me, just for testing
|
||||||
|
/*static*/ int lfs_dir_compact_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
const struct lfs_region__ *regions, int count) {
|
||||||
|
return lfs_dir_compact2_(lfs, dir, lfs_dir_commit_regions,
|
||||||
|
&(struct lfs_dir_commit_regions){regions, count});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_commit_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
const struct lfs_region__ *regions, int count) {
|
||||||
|
return lfs_dir_commit2_(lfs, dir, lfs_dir_commit_regions,
|
||||||
|
&(struct lfs_dir_commit_regions){regions, count});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_add(lfs_t *lfs, lfs_dir_t_ *dir) {
|
||||||
|
uint16_t id = dir->count;
|
||||||
|
dir->count += 1;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_drop(lfs_t *lfs, lfs_dir_t_ *dir, uint16_t id) {
|
||||||
|
dir->count -= 1;
|
||||||
|
// TODO compact during traverse when compacting?
|
||||||
|
return lfs_dir_commit_(lfs, dir, (struct lfs_region__[]){{
|
||||||
|
lfs_mktag(LFS_TYPE_DROP_, id, 0)}}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_dir_setter {
|
||||||
|
const struct lfs_region__ *regions;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
int lfs_dir_setter(lfs_t *lfs, void *p, struct lfs_commit *commit) {
|
||||||
|
struct lfs_dir_setter *setter = p;
|
||||||
|
for (int i = 0; i < setter->count; i++) {
|
||||||
|
int err = lfs_commit_commit(lfs, commit, setter->regions[i]);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int lfs_dir_set_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
const struct lfs_region__ *regions, int count) {
|
||||||
|
return lfs_dir_commit2_(lfs, dir, lfs_dir_setter,
|
||||||
|
&(struct lfs_dir_setter){regions, count});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct lfs_dir_lookuper {
|
||||||
|
const char *name;
|
||||||
|
lfs_size_t size;
|
||||||
|
lfs_block_t pair[2];
|
||||||
|
uint32_t tag;
|
||||||
|
lfs_block_t tail[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int lfs_dir_lookuper(lfs_t *lfs, void *p, struct lfs_region__ region) {
|
||||||
|
struct lfs_dir_lookuper *lookup = p;
|
||||||
|
|
||||||
|
if (lfs_tag_type(region.tag) == LFS_TYPE_NAME_ &&
|
||||||
|
lfs_tag_size(region.tag) == lookup->size) {
|
||||||
|
int res = lfs_bd_cmp(lfs, region.u.d.block, region.u.d.off,
|
||||||
|
lookup->name, lookup->size);
|
||||||
|
if (res < 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
// found a match
|
||||||
|
lookup->tag = region.tag & ~0x80000000;
|
||||||
|
lookup->pair[0] = 0xffffffff;
|
||||||
|
lookup->pair[1] = 0xffffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_tag_valid(lookup->tag) &&
|
||||||
|
lfs_tag_id(region.tag) == lfs_tag_id(lookup->tag) &&
|
||||||
|
lfs_tag_type(region.tag) == LFS_TYPE_DIR_) {
|
||||||
|
int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off,
|
||||||
|
lookup->pair, sizeof(lookup->pair));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_tag_type(lookup->tag) == LFS_TYPE_TAIL_) {
|
||||||
|
int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off,
|
||||||
|
lookup->tail, sizeof(lookup->tail));
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int32_t lfs_dir_lookup_(lfs_t *lfs, lfs_dir_t_ *dir,
|
||||||
|
const char **path) {
|
||||||
|
struct lfs_dir_lookuper lookup = {
|
||||||
|
.name = *path,
|
||||||
|
.pair[0] = 4, // TODO make superblock
|
||||||
|
.pair[1] = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
nextname:
|
||||||
|
// skip slashes
|
||||||
|
lookup.name += strspn(lookup.name, "/");
|
||||||
|
lookup.size = strcspn(lookup.name, "/");
|
||||||
|
|
||||||
|
// special case for root dir
|
||||||
|
if (lookup.name[0] == '\0') {
|
||||||
|
// TODO set up root?
|
||||||
|
// *entry = (lfs_entry_t){
|
||||||
|
// .d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR,
|
||||||
|
// .d.u.dir[0] = lfs->root[0],
|
||||||
|
// .d.u.dir[1] = lfs->root[1],
|
||||||
|
// };
|
||||||
|
return lfs_mktag(LFS_TYPE_DIR_, 0x1ff, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '.' and root '..'
|
||||||
|
if ((lookup.size == 1 && memcmp(lookup.name, ".", 1) == 0) ||
|
||||||
|
(lookup.size == 2 && memcmp(lookup.name, "..", 2) == 0)) {
|
||||||
|
lookup.name += lookup.size;
|
||||||
|
goto nextname;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if matched by '..' in name
|
||||||
|
const char *suffix = lookup.name + lookup.size;
|
||||||
|
lfs_size_t suffsize;
|
||||||
|
int depth = 1;
|
||||||
|
while (true) {
|
||||||
|
suffix += strspn(suffix, "/");
|
||||||
|
suffsize = strcspn(suffix, "/");
|
||||||
|
if (suffsize == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suffsize == 2 && memcmp(suffix, "..", 2) == 0) {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth == 0) {
|
||||||
|
lookup.name = suffix + suffsize;
|
||||||
|
goto nextname;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix += suffsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update what we've found
|
||||||
|
*path = lookup.name;
|
||||||
|
|
||||||
|
// find path // TODO handle tails
|
||||||
|
while (true) {
|
||||||
|
lookup.tag = 0xffffffff;
|
||||||
|
lookup.tail[0] = 0xffffffff;
|
||||||
|
lookup.tail[1] = 0xffffffff;
|
||||||
|
int err = lfs_dir_fetch_(lfs, dir, lookup.pair,
|
||||||
|
lfs_dir_lookuper, &lookup);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_tag_valid(lookup.tag)) {
|
||||||
|
// found it
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lfs_pairisnull(lookup.tail)) {
|
||||||
|
return LFS_ERR_NOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup.pair[0] = lookup.tail[0];
|
||||||
|
lookup.pair[1] = lookup.tail[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle moves
|
||||||
|
// // check that entry has not been moved
|
||||||
|
// if (entry->d.type & LFS_STRUCT_MOVED) {
|
||||||
|
// int moved = lfs_moved(lfs, &entry->d.u);
|
||||||
|
// if (moved < 0 || moved) {
|
||||||
|
// return (moved < 0) ? moved : LFS_ERR_NOENT;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// entry->d.type &= ~LFS_STRUCT_MOVED;
|
||||||
|
// }
|
||||||
|
|
||||||
|
lookup.name += lookup.size;
|
||||||
|
lookup.name += strspn(lookup.name, "/");
|
||||||
|
if (lookup.name[0] == '\0') {
|
||||||
|
return lookup.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue on if we hit a directory
|
||||||
|
// TODO update with what's on master?
|
||||||
|
if (lfs_tag_type(lookup.tag) != LFS_TYPE_DIR_) {
|
||||||
|
return LFS_ERR_NOTDIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
|
static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
|
||||||
// allocate pair of dir blocks
|
// allocate pair of dir blocks
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
|
|||||||
29
lfs.h
29
lfs.h
@@ -105,6 +105,24 @@ enum lfs_type {
|
|||||||
LFS_STRUCT_DIR = 0x20,
|
LFS_STRUCT_DIR = 0x20,
|
||||||
LFS_STRUCT_INLINE = 0x30,
|
LFS_STRUCT_INLINE = 0x30,
|
||||||
LFS_STRUCT_MOVED = 0x80,
|
LFS_STRUCT_MOVED = 0x80,
|
||||||
|
|
||||||
|
LFS_TYPE_CTZ_ = 0x1,
|
||||||
|
LFS_TYPE_DIR_ = 0x2,
|
||||||
|
LFS_TYPE_INLINE_ = 0x3,
|
||||||
|
LFS_TYPE_ATTR_ = 0x4,
|
||||||
|
LFS_TYPE_NAME_ = 0x5,
|
||||||
|
LFS_TYPE_DROP_ = 0x7,
|
||||||
|
LFS_TYPE_TAIL_ = 0xd,
|
||||||
|
LFS_TYPE_CRC_ = 0xe,
|
||||||
|
|
||||||
|
// LFS_TYPE_DIR_ = 0x002,
|
||||||
|
// LFS_TYPE_SUPERBLOCK_ = 0xff2,
|
||||||
|
|
||||||
|
// LFS_MASK_ID_ = 0xff000000,
|
||||||
|
// LFS_MASK_TYPE_ = 0x00fff000,
|
||||||
|
// LFS_MASK_ATTR_ = 0x00ff0000,
|
||||||
|
// LFS_MASK_STRUCT_ = 0x0000f000,
|
||||||
|
// LFS_MASK_SIZE_ = 0x00000fff,
|
||||||
};
|
};
|
||||||
|
|
||||||
// File open flags
|
// File open flags
|
||||||
@@ -314,6 +332,17 @@ typedef struct lfs_dir {
|
|||||||
} d;
|
} d;
|
||||||
} lfs_dir_t;
|
} lfs_dir_t;
|
||||||
|
|
||||||
|
typedef struct lfs_dir_ {
|
||||||
|
lfs_block_t pair[2];
|
||||||
|
lfs_block_t tail[2];
|
||||||
|
|
||||||
|
uint32_t rev;
|
||||||
|
lfs_off_t off;
|
||||||
|
uint32_t etag;
|
||||||
|
uint16_t count;
|
||||||
|
bool erased;
|
||||||
|
} lfs_dir_t_;
|
||||||
|
|
||||||
typedef struct lfs_superblock {
|
typedef struct lfs_superblock {
|
||||||
struct lfs_disk_superblock {
|
struct lfs_disk_superblock {
|
||||||
lfs_block_t root[2];
|
lfs_block_t root[2];
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ static inline uint32_t lfs_tole32(uint32_t a) {
|
|||||||
return lfs_fromle32(a);
|
return lfs_fromle32(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Align to nearest multiple of a size
|
||||||
|
static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
|
||||||
|
return (a + alignment-1) - ((a + alignment-1) % alignment);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||||
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
|
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user