Skip to content

Commit

Permalink
Added support for writing on-disk version lfs2.0
Browse files Browse the repository at this point in the history
The intention is to help interop with older minor versions of littlefs.

Unfortunately, since lfs2.0 drivers cannot mount lfs2.1 images, there are
situations where it would be useful to write to write strictly lfs2.0
compatible images. The solution here adds a "disk_version" configuration
option which determines the behavior of lfs2.1 dependent features.

Normally you would expect this to only change write behavior. But since the
main change in lfs2.1 increased validation of erased data, we also need to
skip this extra validation (fcrc) or see terrible slowdowns when writing.
  • Loading branch information
geky committed Jun 29, 2023
1 parent 265692e commit b72c96d
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 61 deletions.
130 changes: 87 additions & 43 deletions lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,24 @@ static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) {
lfs->mlist = mlist;
}

// some other filesystem operations
static uint32_t lfs_fs_disk_version(lfs_t *lfs) {
if (lfs->cfg->disk_version) {
return lfs->cfg->disk_version;
} else {
return LFS_DISK_VERSION;
}
}

static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) {
return 0xffff & (lfs_fs_disk_version(lfs) >> 16);

}

static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) {
return 0xffff & (lfs_fs_disk_version(lfs) >> 0);
}


/// Internal operations predeclared here ///
#ifndef LFS_READONLY
Expand Down Expand Up @@ -1111,7 +1129,8 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,

// next commit not yet programmed?
if (!lfs_tag_isvalid(tag)) {
maybeerased = true;
// we only might be erased if the last tag was a crc
maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC);
break;
// out of range?
} else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
Expand Down Expand Up @@ -1156,14 +1175,11 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
dir->tail[1] = temptail[1];
dir->split = tempsplit;

// reset crc
// reset crc, hasfcrc
crc = 0xffffffff;
continue;
}

// fcrc is only valid when last tag was a crc
hasfcrc = false;

// crc the entry first, hopefully leaving it in the cache
err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
Expand Down Expand Up @@ -1257,20 +1273,30 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,

// did we end on a valid commit? we may have an erased block
dir->erased = false;
if (maybeerased && hasfcrc && dir->off % 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_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], dir->off, fcrc.size, &fcrc_);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
if (maybeerased && dir->off % lfs->cfg->prog_size == 0) {
// note versions < lfs2.1 did not have fcrc tags, if
// we're < lfs2.1 treat missing fcrc as erased data
//
// we don't strictly need to do this, but otherwise writing
// to lfs2.0 disks becomes very inefficient
if (lfs_fs_disk_version(lfs) < 0x00020001) {
dir->erased = true;

} else if (hasfcrc) {
// 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_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->block_size,
dir->pair[0], dir->off, fcrc.size, &fcrc_);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}

// found beginning of erased part?
dir->erased = (fcrc_ == fcrc.crc);
// found beginning of erased part?
dir->erased = (fcrc_ == fcrc.crc);
}
}

// synthetic move
Expand Down Expand Up @@ -1606,22 +1632,29 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
return err;
}

// find the expected fcrc, don't bother avoiding a reread
// of the eperturb, it should still be in our cache
struct lfs_fcrc fcrc = {.size=lfs->cfg->prog_size, .crc=0xffffffff};
err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->prog_size,
commit->block, noff, fcrc.size, &fcrc.crc);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
// unfortunately fcrcs break mdir fetching < lfs2.1, so only write
// these if we're a >= lfs2.1 filesystem
if (lfs_fs_disk_version(lfs) >= 0x00020001) {
// find the expected fcrc, don't bother avoiding a reread
// of the eperturb, it should still be in our cache
struct lfs_fcrc fcrc = {
.size = lfs->cfg->prog_size,
.crc = 0xffffffff
};
err = lfs_bd_crc(lfs,
NULL, &lfs->rcache, lfs->cfg->prog_size,
commit->block, noff, fcrc.size, &fcrc.crc);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}

lfs_fcrc_tole32(&fcrc);
err = lfs_dir_commitattr(lfs, commit,
LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
&fcrc);
if (err) {
return err;
lfs_fcrc_tole32(&fcrc);
err = lfs_dir_commitattr(lfs, commit,
LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
&fcrc);
if (err) {
return err;
}
}
}

Expand Down Expand Up @@ -4052,6 +4085,13 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
lfs->cfg = cfg;
int err = 0;

// this driver only supports minor version < current minor version
LFS_ASSERT(!lfs->cfg->disk_version || (
(0xffff & (lfs->cfg->disk_version >> 16))
== LFS_DISK_VERSION_MAJOR
&& (0xffff & (lfs->cfg->disk_version >> 0))
<= LFS_DISK_VERSION_MINOR));

// check that bool is a truthy-preserving type
//
// note the most common reason for this failure is a before-c99 compiler,
Expand Down Expand Up @@ -4209,7 +4249,7 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {

// write one superblock
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.version = lfs_fs_disk_version(lfs),
.block_size = lfs->cfg->block_size,
.block_count = lfs->cfg->block_count,
.name_max = lfs->name_max,
Expand Down Expand Up @@ -4307,24 +4347,28 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
// check version
uint16_t major_version = (0xffff & (superblock.version >> 16));
uint16_t minor_version = (0xffff & (superblock.version >> 0));
if ((major_version != LFS_DISK_VERSION_MAJOR ||
minor_version > LFS_DISK_VERSION_MINOR)) {
if (major_version != lfs_fs_disk_version_major(lfs)
|| minor_version > lfs_fs_disk_version_minor(lfs)) {
LFS_ERROR("Invalid version "
"v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
major_version, minor_version,
LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
major_version,
minor_version,
lfs_fs_disk_version_major(lfs),
lfs_fs_disk_version_minor(lfs));
err = LFS_ERR_INVAL;
goto cleanup;
}

// found older minor version? set an in-device only bit in the
// gstate so we know we need to rewrite the superblock before
// the first write
if (minor_version < LFS_DISK_VERSION_MINOR) {
if (minor_version < lfs_fs_disk_version_minor(lfs)) {
LFS_DEBUG("Found older minor version "
"v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
major_version, minor_version,
LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR);
major_version,
minor_version,
lfs_fs_disk_version_major(lfs),
lfs_fs_disk_version_minor(lfs));
// note this bit is reserved on disk, so fetching more gstate
// will not interfere here
lfs_fs_prepsuperblock(lfs, true);
Expand Down Expand Up @@ -4424,7 +4468,7 @@ static int lfs_fs_rawstat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
// if the superblock is up-to-date, we must be on the most recent
// minor version of littlefs
if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
fsinfo->disk_version = LFS_DISK_VERSION;
fsinfo->disk_version = lfs_fs_disk_version(lfs);

// otherwise we need to read the minor version on disk
} else {
Expand Down Expand Up @@ -4711,7 +4755,7 @@ static int lfs_fs_desuperblock(lfs_t *lfs) {

// write a new superblock
lfs_superblock_t superblock = {
.version = LFS_DISK_VERSION,
.version = lfs_fs_disk_version(lfs),
.block_size = lfs->cfg->block_size,
.block_count = lfs->cfg->block_count,
.name_max = lfs->name_max,
Expand Down
6 changes: 6 additions & 0 deletions lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ struct lfs_config {
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
lfs_size_t metadata_max;

// On-disk version to use when writing in the form of 16-bit major version
// + 16-bit minor version. This limiting metadata to what is supported by
// older minor versions. Note that some features will be lost. Defaults to
// to the most recent minor version when zero.
uint32_t disk_version;
};

// File info structure
Expand Down
5 changes: 5 additions & 0 deletions runners/test_runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@ static void run_powerloss_none(
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.disk_version = DISK_VERSION,
};

struct lfs_emubd_config bdcfg = {
Expand Down Expand Up @@ -1415,6 +1416,7 @@ static void run_powerloss_linear(
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.disk_version = DISK_VERSION,
};

struct lfs_emubd_config bdcfg = {
Expand Down Expand Up @@ -1501,6 +1503,7 @@ static void run_powerloss_log(
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.disk_version = DISK_VERSION,
};

struct lfs_emubd_config bdcfg = {
Expand Down Expand Up @@ -1585,6 +1588,7 @@ static void run_powerloss_cycles(
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.disk_version = DISK_VERSION,
};

struct lfs_emubd_config bdcfg = {
Expand Down Expand Up @@ -1767,6 +1771,7 @@ static void run_powerloss_exhaustive(
.block_cycles = BLOCK_CYCLES,
.cache_size = CACHE_SIZE,
.lookahead_size = LOOKAHEAD_SIZE,
.disk_version = DISK_VERSION,
};

struct lfs_emubd_config bdcfg = {
Expand Down
7 changes: 5 additions & 2 deletions runners/test_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ intmax_t test_define(size_t define);
#define ERASE_CYCLES_i 8
#define BADBLOCK_BEHAVIOR_i 9
#define POWERLOSS_BEHAVIOR_i 10
#define DISK_VERSION_i 11

#define READ_SIZE TEST_DEFINE(READ_SIZE_i)
#define PROG_SIZE TEST_DEFINE(PROG_SIZE_i)
Expand All @@ -103,6 +104,7 @@ intmax_t test_define(size_t define);
#define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i)
#define BADBLOCK_BEHAVIOR TEST_DEFINE(BADBLOCK_BEHAVIOR_i)
#define POWERLOSS_BEHAVIOR TEST_DEFINE(POWERLOSS_BEHAVIOR_i)
#define DISK_VERSION TEST_DEFINE(DISK_VERSION_i)

#define TEST_IMPLICIT_DEFINES \
TEST_DEF(READ_SIZE, PROG_SIZE) \
Expand All @@ -115,9 +117,10 @@ intmax_t test_define(size_t define);
TEST_DEF(ERASE_VALUE, 0xff) \
TEST_DEF(ERASE_CYCLES, 0) \
TEST_DEF(BADBLOCK_BEHAVIOR, LFS_EMUBD_BADBLOCK_PROGERROR) \
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP)
TEST_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) \
TEST_DEF(DISK_VERSION, 0)

#define TEST_IMPLICIT_DEFINE_COUNT 11
#define TEST_IMPLICIT_DEFINE_COUNT 12
#define TEST_GEOMETRY_DEFINE_COUNT 4


Expand Down

0 comments on commit b72c96d

Please sign in to comment.