Rough implementation of lfsr_format/mount/unmount

This work already indicates we need more data-related helper
functions. We shouldn't need this many function calls to do "simple"
operations such as fetch the superconfig if it exists.
This commit is contained in:
Christopher Haster
2023-04-22 14:06:29 -05:00
parent 5d68d7eccd
commit 70a3a2b16e
5 changed files with 730 additions and 211 deletions
+501 -16
View File
@@ -452,6 +452,10 @@ enum lfsr_tag_type {
LFSR_TAG_UNR = 0x0002,
LFSR_TAG_MKUNR = 0x0006, // in-device only
LFSR_TAG_SUPERMAGIC = 0x0030,
LFSR_TAG_SUPERCONFIG = 0x0040,
LFSR_TAG_SUPERMDIR = 0x0110,
LFSR_TAG_NAME = 0x1000,
LFSR_TAG_BNAME = 0x1000,
LFSR_TAG_MKBNAME = 0x1004, // in-device only
@@ -2589,7 +2593,7 @@ static lfs_ssize_t lfsr_branch_fromdisk(
branch->crc = lfs_fromle32_(&buffer[d]);
d += 4;
return 4;
return d;
}
@@ -3968,22 +3972,503 @@ static int lfsr_btree_split(lfs_t *lfs, lfsr_btree_t *btree,
/// Metadata pair operations ///
//static int lfsr_mdir_fetch(lfs_t *lfs, lfsr_mdir_t mdir,
// lfs_block_t block1, lfs_block_t block2,
// struct lfsr_pat *patterns) {
//}
typedef struct lfsr_mpair {
lfs_block_t blocks[2];
} lfsr_mpair_t;
#define LFSR_MPAIR(block0, block1) ((lfsr_mpair_t){.blocks={block0, block1}})
// 2 leb128 => 10 bytes (worst case)
#define LFSR_MPAIR_DSIZE (5+5)
static lfs_ssize_t lfsr_mpair_todisk(
lfsr_mpair_t mpair,
uint8_t buffer[static LFSR_MPAIR_DSIZE]) {
lfs_ssize_t d = 0;
for (int i = 0; i < 2; i++) {
lfs_ssize_t d_ = lfs_toleb128(mpair.blocks[i], &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
}
return d;
}
// TODO should our fromdisk functions accept an lfsr_data_t?
static lfs_ssize_t lfsr_mpair_fromdisk(
lfsr_mpair_t *mpair,
const uint8_t buffer[static LFSR_BRANCH_DSIZE]) {
lfs_ssize_t d = 0;
for (int i = 0; i < 2; i++) {
lfs_ssize_t d_ = lfs_fromleb128(&mpair->blocks[i], &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
}
return d;
}
static lfsr_mpair_t lfsr_mdir_mpair(const lfsr_mdir_t *mdir) {
return LFSR_MPAIR(mdir->rbyd.block, mdir->other_block);
}
static int lfsr_mdir_fetch(lfs_t *lfs, lfsr_mdir_t *mdir, lfsr_mpair_t mpair,
lfsr_find_t *find) {
// read both revision counts, try to figure out which block
// has the most recent revision
uint32_t revs[2] = {0, 0};
for (int i = 0; i < 2; i++) {
int err = lfs_bd_read(lfs,
NULL, &lfs->rcache, sizeof(revs[0]),
mpair.blocks[0], 0, &revs[0], sizeof(revs[0]));
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
if (i == 0
|| err == LFS_ERR_CORRUPT
|| lfs_scmp(revs[1], revs[0]) > 0) {
lfs_swap32(&mpair.blocks[0], &mpair.blocks[1]);
lfs_swap32(&revs[0], &revs[1]);
}
}
// try to fetch rbyds in the order of most recent to least recent
for (int i = 0;; i++) {
int err = lfsr_rbyd_fetch(lfs, &mdir->rbyd, mpair.blocks[0], 0, find);
if (err && err != LFS_ERR_CORRUPT) {
return err;
}
if (err) {
// could not find a non-corrupt rbyd
if (i >= 2-1) {
return LFS_ERR_CORRUPT;
}
lfs_swap32(&mpair.blocks[0], &mpair.blocks[1]);
lfs_swap32(&revs[0], &revs[1]);
continue;
}
break;
}
// keep track of other block for compactions
mdir->other_block = mpair.blocks[1];
return 0;
}
static int lfsr_mdir_lookup(lfs_t *lfs, const lfsr_mdir_t *mdir,
lfs_ssize_t id, lfsr_tag_t tag,
lfs_ssize_t *id_, lfsr_tag_t *tag_, lfs_size_t *weight_,
lfsr_data_t *data_) {
return lfsr_rbyd_lookup(lfs, &mdir->rbyd, id, tag,
id_, tag_, weight_, data_);
}
static lfs_ssize_t lfsr_mdir_get(lfs_t *lfs, const lfsr_mdir_t *mdir,
lfs_ssize_t id, lfsr_tag_t tag, void *buffer, lfs_size_t size) {
return lfsr_rbyd_get(lfs, &mdir->rbyd, id, tag, buffer, size);
}
static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
const lfsr_attr_t *attrs, lfs_size_t attr_count) {
// try to commit
int err = lfsr_rbyd_commit(lfs, &mdir->rbyd, attrs, attr_count);
if (err && err != LFS_ERR_RANGE) {
//TODO should we also move if there is corruption here?
return err;
}
// can't commit, try to compact
// TODO splits
// TODO relocations
if (err) {
// prepare the other block
// TODO rev quirks?
lfsr_rbyd_t rbyd_ = (lfsr_rbyd_t){
.block=mdir->other_block,
.rev=mdir->rbyd.rev+1,
.off=0,
.trunk=0
};
int err = lfs_bd_erase(lfs, rbyd_.block);
if (err) {
return err;
}
// try to copy over ids
lfs_ssize_t id = 0;
lfsr_tag_t tag = 0;
while (true) {
lfs_size_t w;
lfsr_data_t data;
err = lfsr_rbyd_lookup(lfs, &mdir->rbyd, id, lfsr_tag_next(tag),
&id, &tag, &w, &data);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT) {
break;
}
// append the attr
err = lfsr_rbyd_append(lfs, &rbyd_,
id-lfs_smax32(w-1, 0), lfsr_tag_setmk(tag), +w,
data);
if (err) {
return err;
}
// TODO split on exceeding 1/2 block size
// keep rbyd < our compaction threshold (1/2) to avoid
// degenerate cases
if (rbyd_.off > lfs->cfg->block_size/2) {
// TODO
}
}
// append any pending attrs, it's up to upper
// layers to make sure these always fit
err = lfsr_rbyd_commit(lfs, &rbyd_, attrs, attr_count);
if (err) {
LFS_ASSERT(err != LFS_ERR_RANGE);
return err;
}
// update our mdir
mdir->other_block = mdir->rbyd.block;
mdir->rbyd = rbyd_;
}
// done
return 0;
}
/// Superblock things ///
typedef struct lfsr_superconfig {
uint8_t major_version;
uint8_t minor_version;
uint8_t csum_type;
uint8_t flags;
lfs_size_t block_size;
lfs_off_t block_count;
uint8_t utag_limit;
lfs_size_t attr_limit;
lfs_size_t name_limit;
lfs_off_t file_limit;
} lfsr_superconfig_t;
// These are all leb128s, but we can expect smaller encodings
// if we assume the version.
//
//static lfsr_stag_t lfsr_mdir_lookup(lfs_t *lfs, const lfsr_mdir_t *mdir,
// lfsr_tag_t tag, lfs_off_t *off, lfs_size_t *size) {
//}
//
//static lfs_ssize_t lfsr_mdir_get(lfs_t *lfs, const lfsr_mdir_t *mdir,
// lfsr_tag_t tag, void *buffer, lfs_size_t size) {
//}
//
//static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// const struct lfsr_attr *attrs) {
//}
// - 7-bit major_version => 1 byte leb128 (worst case)
// - 7-bit minor_version => 1 byte leb128 (worst case)
// - 7-bit csum_type => 1 byte leb128 (worst case)
// - 7-bit flags => 1 byte leb128 (worst case)
// - 32-bit block_size => 5 byte leb128 (worst case)
// - 32-bit block_count => 5 byte leb128 (worst case)
// - 7-bit utag_limit => 1 byte leb128 (worst case)
// - 32-bit attr_limit => 5 byte leb128 (worst case)
// - 32-bit name_limit => 5 byte leb128 (worst case)
// - 32-bit file_limit => 5 byte leb128 (worst case)
// => 30 bytes total
//
#define LFSR_SUPERCONFIG_DSIZE (1+1+1+1+5+5+1+5+5+5)
static lfs_ssize_t lfsr_superconfig_todisk(
const lfsr_superconfig_t *superconfig,
uint8_t buffer[static LFSR_SUPERCONFIG_DSIZE]) {
// shortcut the single-byte lebs
buffer[0] = superconfig->major_version;
buffer[1] = superconfig->minor_version;
buffer[2] = superconfig->csum_type;
buffer[3] = superconfig->flags;
lfs_ssize_t d = 4;
lfs_ssize_t d_ = lfs_toleb128(superconfig->block_size, &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
d_ = lfs_toleb128(superconfig->block_count, &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
buffer[d] = superconfig->utag_limit;
d += 1;
d_ = lfs_toleb128(superconfig->attr_limit, &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
d_ = lfs_toleb128(superconfig->name_limit, &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
d_ = lfs_toleb128(superconfig->file_limit, &buffer[d], 5);
if (d_ < 0) {
return d_;
}
d += d_;
return d;
}
/// Filesystem init functions ///
static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg);
static int lfs_deinit(lfs_t *lfs);
int lfsr_format(lfs_t *lfs, const struct lfs_config *cfg) {
int err = lfs_init(lfs, cfg);
if (err) {
return err;
}
uint8_t buf[LFSR_SUPERCONFIG_DSIZE];
lfs_ssize_t d = lfsr_superconfig_todisk(
&(lfsr_superconfig_t){
.major_version=LFS_DISK_VERSION_MAJOR,
.minor_version=LFS_DISK_VERSION_MINOR,
.csum_type=2,
.flags=0,
.block_size=lfs->cfg->block_size,
.block_count=lfs->cfg->block_count,
// TODO these should be defines
.utag_limit=0x7f,
.attr_limit=0x7fffffff,
.name_limit=0xff,
.file_limit=0x7fffffff,
},
buf);
if (d < 0) {
err = d;
goto failed;
}
for (int i = 0; i < 2; i++) {
// write superblock to both rbyds in the root supermdir to hopefully
// avoid mounting an older filesystem on disk
lfsr_rbyd_t rbyd = {.block=i, .rev=1, .off=0, .trunk=0};
err = lfs_bd_erase(lfs, rbyd.block);
if (err) {
goto failed;
}
err = lfsr_rbyd_commit(lfs, &rbyd, LFSR_ATTRS(
LFSR_ATTR(-1, SUPERMAGIC, 0, "littlefs", 8),
LFSR_ATTR(-1, SUPERCONFIG, 0, buf, d)));
if (err) {
goto failed;
}
}
// try to fetch the supermdir to check if format completed successfully
lfsr_mdir_t mdir;
err = lfsr_mdir_fetch(lfs, &mdir, LFSR_MPAIR(0, 1), NULL);
if (err) {
goto failed;
}
return lfs_deinit(lfs);
failed:
lfs_deinit(lfs);
return err;
}
int lfsr_mount(lfs_t *lfs, const struct lfs_config *cfg) {
int err = lfs_init(lfs, cfg);
if (err) {
return err;
}
// scan for the first non-fake superblock
lfsr_mpair_t mpair = LFSR_MPAIR(0, 1);
lfsr_mdir_t mdir;
while (true) {
// TODO detect cycles with Brent's algorithm
// fetch next possible superblock
int err = lfsr_mdir_fetch(lfs, &mdir, mpair, NULL);
if (err) {
LFS_ERROR("No littlefs superblock found");
goto failed;
}
// has magic string?
lfs_ssize_t id;
lfsr_tag_t tag;
lfsr_data_t data;
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_SUPERMAGIC,
&id, &tag, NULL, &data);
if (err && err != LFS_ERR_NOENT) {
goto failed;
}
if (err
|| id != -1
|| tag != LFSR_TAG_SUPERMAGIC
|| lfsr_data_size(data) != 8) {
LFS_ERROR("No littlefs magic found");
err = LFS_ERR_INVAL;
goto failed;
}
// TODO we should have a function for this with lfsr_data_t
LFS_ASSERT(lfsr_data_ondisk(data));
int cmp = lfs_bd_cmp(lfs,
NULL, &lfs->rcache, 8,
data.disk.block, data.disk.off, "littlefs", 8);
if (cmp < 0) {
err = cmp;
goto failed;
}
if (cmp != LFS_CMP_EQ) {
LFS_ERROR("No littlefs magic found");
err = LFS_ERR_INVAL;
goto failed;
}
// lookup the superconfig
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_SUPERCONFIG,
&id, &tag, NULL, &data);
if (err && err != LFS_ERR_NOENT) {
goto failed;
}
if (!(err
|| id != -1
|| tag != LFSR_TAG_SUPERCONFIG)) {
// check the major/minor version
uint32_t major_version;
uint32_t minor_version;
// TODO leb128 decoder for lfsr_data_t?
uint8_t buf[5];
lfs_size_t d = 0;
LFS_ASSERT(lfsr_data_ondisk(data));
// TODO can we do this differently?
// force truncated to overflow
memset(buf, 0xff, 5);
int err = lfs_bd_read(lfs,
NULL, &lfs->rcache, 5,
data.disk.block, data.disk.off+d, buf,
// TODO this is a bit gross, and repeated a bunch,
// can we simplify this?
lfs_min32(5, lfs_max32(lfsr_data_size(data), d)-d));
if (err) {
goto failed;
}
lfs_ssize_t d_ = lfs_fromleb128(&major_version, buf, 5);
if (d_ < 0) {
// just treat overflow as an out-of-range value
major_version = -1;
d_ = 5;
}
d += d_;
// force truncated lebs to overflow
memset(buf, 0xff, 5);
err = lfs_bd_read(lfs,
NULL, &lfs->rcache, 5,
data.disk.block, data.disk.off+d, buf,
lfs_min32(5, lfs_max32(lfsr_data_size(data), d)-d));
if (err) {
goto failed;
}
d_ = lfs_fromleb128(&minor_version, buf, 5);
if (d_ < 0) {
// just treat overflow as an out-of-range value
minor_version = -1;
d_ = 5;
}
d += d_;
if (major_version != LFS_DISK_VERSION_MAJOR
|| minor_version > LFS_DISK_VERSION_MINOR) {
LFS_ERROR("Invalid version v%"PRIu32".%"PRIu32,
major_version, minor_version);
err = LFS_ERR_INVAL;
goto failed;
}
// TODO parse rest of the superblock
}
// lookup supermdir
//
// if we have a supermdir, this is actually a fake superblock and
// we need to parse the next superblock in the chain
err = lfsr_mdir_lookup(lfs, &mdir, -1, LFSR_TAG_SUPERMDIR,
&id, &tag, NULL, &data);
if (err && err != LFS_ERR_NOENT) {
goto failed;
}
// no more supermdirs means we found our real superblock!
if (err
|| id != -1
|| tag != LFSR_TAG_SUPERMDIR) {
break;
}
// TODO mdir dsize/mdir fromleb128
uint8_t buf_[LFSR_MPAIR_DSIZE];
LFS_ASSERT(lfsr_data_ondisk(data));
// force truncated lebs to overflow
memset(buf_, 0xff, LFSR_MPAIR_DSIZE);
err = lfs_bd_read(lfs,
NULL, &lfs->rcache,
lfs_min32(LFSR_MPAIR_DSIZE, lfsr_data_size(data)),
data.disk.block, data.disk.off, buf_,
lfs_min32(LFSR_MPAIR_DSIZE, lfsr_data_size(data)));
if (err) {
goto failed;
}
lfs_ssize_t d_ = lfsr_mpair_fromdisk(&mpair, buf_);
if (d_ < 0) {
err = d_;
goto failed;
}
}
return 0;
failed:
lfs_deinit(lfs);
return err;
}
int lfsr_unmount(lfs_t *lfs) {
return lfs_deinit(lfs);
}
/// Metadata pair and directory operations ///
static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
+8 -30
View File
@@ -344,16 +344,6 @@ typedef struct lfsr_rbyd {
uint32_t crc;
} lfsr_rbyd_t;
//typedef struct lfsr_btree {
// // TODO do we need this field? it's needed for inlined
// // btrees but is redundent when we have an rbyd
// // a weight of zero indicates no tree
// lfs_size_t weight;
// // a limit of zero indicates an inlined tree
// lfs_size_t limit;
// lfs_block_t trunk;
//} lfsr_btree_t;
// The maximum size of inlined pointers in a btree, this depends on littlefs's
// on-disk pointer representations (there are several), but doesn't change at
// runtime.
@@ -380,26 +370,11 @@ typedef union lfsr_btree {
} inlined;
} lfsr_btree_t;
//typedef struct lfsr_branch {
// lfs_block_t block;
// lfs_size_t limit;
//} lfsr_branch_t;
//
//typedef struct lfsr_btree {
// lfs_size_t weight;
// // TODO do we need full tag actually? this fits in a byte?
// lfsr_tag_t tag;
// // how can we take advantage of byte packing with union alignment?
// union {
// struct {
// uint8_t size;
// uint8_t buffer[LFSR_BTREE_INLINESIZE];
// } inlined;
//
// // if we're not inlined, point to the trunk rbyd block of the btree
// lfsr_branch_t trunk;
// } u;
//} lfsr_btree_t;
typedef struct lfsr_mdir {
lfsr_rbyd_t rbyd;
lfs_block_t other_block;
} lfsr_mdir_t;
typedef struct lfs_mdir {
lfs_block_t pair[2];
@@ -506,6 +481,7 @@ typedef struct lfs {
//
// Returns a negative error code on failure.
int lfs_format(lfs_t *lfs, const struct lfs_config *config);
int lfsr_format(lfs_t *lfs, const struct lfs_config *config);
#endif
// Mounts a littlefs
@@ -517,12 +493,14 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *config);
//
// Returns a negative error code on failure.
int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
int lfsr_mount(lfs_t *lfs, const struct lfs_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs_unmount(lfs_t *lfs);
int lfsr_unmount(lfs_t *lfs);
/// General operations ///
+32 -14
View File
@@ -7,20 +7,23 @@ import os
import struct
TAG_UNR = 0x0002
TAG_NAME = 0x1000
TAG_BNAME = 0x1000
TAG_REG = 0x1010
TAG_DIR = 0x1020
TAG_STRUCT = 0x3000
TAG_INLINED = 0x3000
TAG_BLOCK = 0x3100
TAG_BRANCH = 0x3200
TAG_BTREE = 0x3300
TAG_UATTR = 0x4000
TAG_ALT = 0x0008
TAG_CRC = 0x0004
TAG_FCRC = 0x1004
TAG_UNR = 0x0002
TAG_SUPERMAGIC = 0x0030
TAG_SUPERCONFIG = 0x0040
TAG_SUPERMDIR = 0x0110
TAG_NAME = 0x1000
TAG_BNAME = 0x1000
TAG_REG = 0x1010
TAG_DIR = 0x1020
TAG_STRUCT = 0x3000
TAG_INLINED = 0x3000
TAG_BLOCK = 0x3100
TAG_BRANCH = 0x3200
TAG_BTREE = 0x3300
TAG_UATTR = 0x4000
TAG_ALT = 0x0008
TAG_CRC = 0x0004
TAG_FCRC = 0x1004
def rbydaddr(s):
if '.' in s:
@@ -88,6 +91,21 @@ def tagrepr(tag, w, size, off=None):
return 'unr%s%s' % (
' w%d' % w if w else '',
' %d' % size if size else '')
elif (tag & 0xfffc) == TAG_SUPERMAGIC:
return '%ssupermagic%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xfffc) == TAG_SUPERCONFIG:
return '%ssuperconfig%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xfffc) == TAG_SUPERMDIR:
return '%ssupermdir%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xf00c) == TAG_NAME:
return '%s%s%s %d' % (
'rm' if tag & 0x2 else '',
+32 -14
View File
@@ -17,20 +17,23 @@ COLORS = [
]
TAG_UNR = 0x0002
TAG_NAME = 0x1000
TAG_BNAME = 0x1000
TAG_REG = 0x1010
TAG_DIR = 0x1020
TAG_STRUCT = 0x3000
TAG_INLINED = 0x3000
TAG_BLOCK = 0x3100
TAG_BRANCH = 0x3200
TAG_BTREE = 0x3300
TAG_UATTR = 0x4000
TAG_ALT = 0x0008
TAG_CRC = 0x0004
TAG_FCRC = 0x1004
TAG_UNR = 0x0002
TAG_SUPERMAGIC = 0x0030
TAG_SUPERCONFIG = 0x0040
TAG_SUPERMDIR = 0x0110
TAG_NAME = 0x1000
TAG_BNAME = 0x1000
TAG_REG = 0x1010
TAG_DIR = 0x1020
TAG_STRUCT = 0x3000
TAG_INLINED = 0x3000
TAG_BLOCK = 0x3100
TAG_BRANCH = 0x3200
TAG_BTREE = 0x3300
TAG_UATTR = 0x4000
TAG_ALT = 0x0008
TAG_CRC = 0x0004
TAG_FCRC = 0x1004
def rbydaddr(s):
if '.' in s:
@@ -98,6 +101,21 @@ def tagrepr(tag, w, size, off=None):
return 'unr%s%s' % (
' w%d' % w if w else '',
' %d' % size if size else '')
elif (tag & 0xfffc) == TAG_SUPERMAGIC:
return '%ssupermagic%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xfffc) == TAG_SUPERCONFIG:
return '%ssuperconfig%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xfffc) == TAG_SUPERMDIR:
return '%ssupermdir%s %d' % (
'rm' if tag & 0x2 else '',
' w%d' % w if w else '',
size)
elif (tag & 0xf00c) == TAG_NAME:
return '%s%s%s %d' % (
'rm' if tag & 0x2 else '',
+157 -137
View File
@@ -1,151 +1,171 @@
# simple formatting test
# simple format test
[cases.test_superblocks_format]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfsr_format(&lfs, cfg) => 0;
'''
# mount/unmount
# simple mount/unmount test
[cases.test_superblocks_mount]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
lfs_unmount(&lfs) => 0;
lfsr_format(&lfs, cfg) => 0;
lfsr_mount(&lfs, cfg) => 0;
lfsr_unmount(&lfs) => 0;
'''
# reentrant format
[cases.test_superblocks_reentrant_format]
reentrant = true
code = '''
lfs_t lfs;
int err = lfs_mount(&lfs, cfg);
if (err) {
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
}
lfs_unmount(&lfs) => 0;
'''
# invalid mount
[cases.test_superblocks_invalid_mount]
code = '''
lfs_t lfs;
lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
'''
# expanding superblock
[cases.test_superblocks_expand]
defines.BLOCK_CYCLES = [32, 33, 1]
defines.N = [10, 100, 1000]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
for (int i = 0; i < N; i++) {
lfs_file_t file;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_unmount(&lfs) => 0;
// one last check after power-cycle
lfs_mount(&lfs, cfg) => 0;
lfs_file_t file;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
struct lfs_info info;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''
# expanding superblock with power cycle
[cases.test_superblocks_expand_power_cycle]
defines.BLOCK_CYCLES = [32, 33, 1]
defines.N = [10, 100, 1000]
code = '''
lfs_t lfs;
lfs_format(&lfs, cfg) => 0;
for (int i = 0; i < N; i++) {
lfs_mount(&lfs, cfg) => 0;
// remove lingering dummy?
struct lfs_info info;
int err = lfs_stat(&lfs, "dummy", &info);
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_t file;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
}
// one last check after power-cycle
lfs_mount(&lfs, cfg) => 0;
struct lfs_info info;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''
# reentrant expanding superblock
[cases.test_superblocks_reentrant_expand]
defines.BLOCK_CYCLES = [2, 1]
defines.N = 24
reentrant = true
code = '''
lfs_t lfs;
int err = lfs_mount(&lfs, cfg);
if (err) {
lfs_format(&lfs, cfg) => 0;
lfs_mount(&lfs, cfg) => 0;
}
for (int i = 0; i < N; i++) {
// remove lingering dummy?
struct lfs_info info;
err = lfs_stat(&lfs, "dummy", &info);
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_t file;
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
}
lfs_unmount(&lfs) => 0;
// one last check after power-cycle
lfs_mount(&lfs, cfg) => 0;
struct lfs_info info;
lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0;
'''
## simple formatting test
#[cases.test_superblocks_format]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
#'''
#
## mount/unmount
#[cases.test_superblocks_mount]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_unmount(&lfs) => 0;
#'''
#
## reentrant format
#[cases.test_superblocks_reentrant_format]
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
# lfs_unmount(&lfs) => 0;
#'''
#
## invalid mount
#[cases.test_superblocks_invalid_mount]
#code = '''
# lfs_t lfs;
# lfs_mount(&lfs, cfg) => LFS_ERR_CORRUPT;
#'''
#
## expanding superblock
#[cases.test_superblocks_expand]
#defines.BLOCK_CYCLES = [32, 33, 1]
#defines.N = [10, 100, 1000]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "dummy",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# struct lfs_info info;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_remove(&lfs, "dummy") => 0;
# }
# lfs_unmount(&lfs) => 0;
#
# // one last check after power-cycle
# lfs_mount(&lfs, cfg) => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "dummy",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# struct lfs_info info;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_unmount(&lfs) => 0;
#'''
#
## expanding superblock with power cycle
#[cases.test_superblocks_expand_power_cycle]
#defines.BLOCK_CYCLES = [32, 33, 1]
#defines.N = [10, 100, 1000]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# for (int i = 0; i < N; i++) {
# lfs_mount(&lfs, cfg) => 0;
# // remove lingering dummy?
# struct lfs_info info;
# int err = lfs_stat(&lfs, "dummy", &info);
# assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
# if (!err) {
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_remove(&lfs, "dummy") => 0;
# }
#
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "dummy",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_unmount(&lfs) => 0;
# }
#
# // one last check after power-cycle
# lfs_mount(&lfs, cfg) => 0;
# struct lfs_info info;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_unmount(&lfs) => 0;
#'''
#
## reentrant expanding superblock
#[cases.test_superblocks_reentrant_expand]
#defines.BLOCK_CYCLES = [2, 1]
#defines.N = 24
#reentrant = true
#code = '''
# lfs_t lfs;
# int err = lfs_mount(&lfs, cfg);
# if (err) {
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# }
#
# for (int i = 0; i < N; i++) {
# // remove lingering dummy?
# struct lfs_info info;
# err = lfs_stat(&lfs, "dummy", &info);
# assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
# if (!err) {
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_remove(&lfs, "dummy") => 0;
# }
#
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "dummy",
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
# lfs_file_close(&lfs, &file) => 0;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# }
#
# lfs_unmount(&lfs) => 0;
#
# // one last check after power-cycle
# lfs_mount(&lfs, cfg) => 0;
# struct lfs_info info;
# lfs_stat(&lfs, "dummy", &info) => 0;
# assert(strcmp(info.name, "dummy") == 0);
# assert(info.type == LFS_TYPE_REG);
# lfs_unmount(&lfs) => 0;
#'''