Some progress, with deferred attributes taking shape
Ran into an interesting macro-related bug. Turns out the way we are
doing implicit prefixing in TAG/ATTR macros sort of breaks how C macros
work a bit. The following does not compile:
lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
LFSR_ATTR(file->m.mdir.mid, DEFER, 0, DEFER(
(lfsr_rbyd_t*)&file->inlined,
LFSR_ATTR(file->buffer_pos,
DEFERRED(INLINED), +file->buffer_size, BUF(
file->buffer, file->buffer_size))))));
Or to distill it down, this does not compile:
#define LFSR_ATTR(_data) (LFSR_##_data)
#define LFSR_DEFER(_data) (LFSR_##_data)
#define LFSR_DATA(_data) (_data)
int a = LFSR_ATTR(DEFER(ATTR(DATA(1))));
But this does:
#define LFSR_ATTR(_data) (_data)
#define LFSR_DEFER(_data) (_data)
#define LFSR_DATA(_data) (_data)
int a = LFSR_ATTR(LFSR_DEFER(LFSR_ATTR(LFSR_DATA(1))));
Why? Well it turns out the whole way nested C macro's work is a big
hack.
A very reasonable design decision in C is to disallow recursive macro
expansions. Unlike C++, we don't want our preprocessor to suddenly stack
overflow. This rule is enforced by stopping macro expansion when a macro
contains itself. For example:
#define A() B()
#define B() A()
A()
Expands to:
A()
-> B()
-> A() (stops, probably erroring with 'A' undeclared)
But it _is_ common to want to recursively expand macro arguments. Macros
are a part of C's syntax after all, and users usually expect
expressions, such as arguments, to be context-free:
#define A(x) (x) + 1
A(A(A(A(A(0)))))
Naively this would expand to:
A(A(A(A(A(0)))))
-> (A(A(A(A(0))))) + 1 (stops)
The big hack that makes this work in C's preprocessor is the "Argument
prescan". Instead of expanding the "called" macro first, we expand any macro
inside our argument list, _then_ expand the "called" macro, and _then_
expand any new macros produced as a result of the expansion again just
for good measure.
So the above actually expands to:
A(A(A(A(A(0)))))
-> A(A(A(A((0) + 1))))
-> A(A(A(((0) + 1) + 1)))
-> A(A((((0) + 1) + 1) + 1))
-> A(((((0) + 1) + 1) + 1) + 1)
-> (((((0) + 1) + 1) + 1) + 1) + 1
This is still recursive actually! But the recursion is limited to the
actual length of the source code, so the developers likely thought this
was a reasonable tradeoff.
But what does this mean for our implicit prefixing?
#define P_A(x) P_##x
#define P_B(x) P_##x
#define P_C(x) (x)
P_A(B(A(C(0))))
None of A, B, C are in scope without prefixes, so they get expanded
after the "called" macro's expansion:
P_A(B(A(C)))
-> P_B(A(C(0)))
-> P_A(C(0)) (stops)
But this breaks when we hit the nested P_A macro.
---
For now I've gone with the temporary, and extra hacky, solution of
introducing a second LFSR_ATTR_ macro. This nesting of ATTR macros only
happens because of shrubs, and only ever goes 2 layers deep.
In the future maybe we should move away from implicit prefixing. They
have a few rough corners and may be a bit confusing for anyone new to
the code.
This commit is contained in:
@@ -636,12 +636,13 @@ enum lfsr_tag_type {
|
||||
// to change in the future
|
||||
LFSR_TAG_INTERNAL = 0x0800,
|
||||
LFSR_TAG_MOVE = 0x0800,
|
||||
LFSR_TAG_DEFER = 0x0801,
|
||||
|
||||
// some tag modifiers
|
||||
// in-device only
|
||||
LFSR_TAG_WIDE = 0x2000,
|
||||
LFSR_TAG_GROW = 0x4000,
|
||||
LFSR_TAG_RM = 0x8000,
|
||||
LFSR_TAG_GROW = 0x4000,
|
||||
LFSR_TAG_WIDE = 0x2000,
|
||||
|
||||
// lfsr_rbyd_appendattr specific flags
|
||||
LFSR_TAG_DIVERGED = 0x4000,
|
||||
@@ -654,10 +655,10 @@ enum lfsr_tag_type {
|
||||
#define LFSR_TAG_TAG(tag) (tag)
|
||||
|
||||
// some tag modifiers
|
||||
#define LFSR_TAG_WIDE(tag) (LFSR_TAG_WIDE | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_GROW(tag) (LFSR_TAG_GROW | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_RM(tag) (LFSR_TAG_RM | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_DEFERRED(tag) (LFSR_TAG_DEFERRED | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_RM(tag) (LFSR_TAG_RM | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_GROW(tag) (LFSR_TAG_GROW | LFSR_TAG_##tag)
|
||||
#define LFSR_TAG_WIDE(tag) (LFSR_TAG_WIDE | LFSR_TAG_##tag)
|
||||
|
||||
// some other tag encodings with their own subfields
|
||||
#define LFSR_TAG_ALT(d, c, key) \
|
||||
@@ -699,28 +700,32 @@ static inline lfsr_tag_t lfsr_tag_subkey(lfsr_tag_t tag) {
|
||||
return tag & 0x00ff;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_iswide(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_WIDE;
|
||||
static inline lfsr_tag_t lfsr_tag_deferredmode(lfsr_tag_t tag) {
|
||||
return tag & 0xe000;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_setwide(lfsr_tag_t tag) {
|
||||
return tag | LFSR_TAG_WIDE;
|
||||
static inline lfsr_tag_t lfsr_tag_deferredkey(lfsr_tag_t tag) {
|
||||
return tag & 0x1fff;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_clearwide(lfsr_tag_t tag) {
|
||||
return tag & ~LFSR_TAG_WIDE;
|
||||
static inline bool lfsr_tag_isalt(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_ALT;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_isgrow(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_GROW;
|
||||
static inline bool lfsr_tag_isdeferred(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_DEFERRED;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_setgrow(lfsr_tag_t tag) {
|
||||
return tag | LFSR_TAG_GROW;
|
||||
static inline bool lfsr_tag_istrunk(lfsr_tag_t tag) {
|
||||
return (tag & 0x6000) != 0x2000;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_cleargrow(lfsr_tag_t tag) {
|
||||
return tag & ~LFSR_TAG_GROW;
|
||||
static inline uint8_t lfsr_tag_filetype(lfsr_tag_t tag) {
|
||||
return tag - LFSR_TAG_REG;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_isinternal(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_INTERNAL;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_isrm(lfsr_tag_t tag) {
|
||||
@@ -735,20 +740,28 @@ static inline lfsr_tag_t lfsr_tag_clearrm(lfsr_tag_t tag) {
|
||||
return tag & ~LFSR_TAG_RM;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_isalt(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_ALT;
|
||||
static inline bool lfsr_tag_isgrow(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_GROW;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_istrunk(lfsr_tag_t tag) {
|
||||
return (tag & 0x6000) != 0x2000;
|
||||
static inline lfsr_tag_t lfsr_tag_setgrow(lfsr_tag_t tag) {
|
||||
return tag | LFSR_TAG_GROW;
|
||||
}
|
||||
|
||||
static inline uint8_t lfsr_tag_filetype(lfsr_tag_t tag) {
|
||||
return tag - LFSR_TAG_REG;
|
||||
static inline lfsr_tag_t lfsr_tag_cleargrow(lfsr_tag_t tag) {
|
||||
return tag & ~LFSR_TAG_GROW;
|
||||
}
|
||||
|
||||
static inline bool lfsr_tag_isinternal(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_INTERNAL;
|
||||
static inline bool lfsr_tag_iswide(lfsr_tag_t tag) {
|
||||
return tag & LFSR_TAG_WIDE;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_setwide(lfsr_tag_t tag) {
|
||||
return tag | LFSR_TAG_WIDE;
|
||||
}
|
||||
|
||||
static inline lfsr_tag_t lfsr_tag_clearwide(lfsr_tag_t tag) {
|
||||
return tag & ~LFSR_TAG_WIDE;
|
||||
}
|
||||
|
||||
// lfsr_rbyd_appendattr diverged specific flags
|
||||
@@ -1057,6 +1070,13 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
|
||||
#define LFSR_DATA_GRM(_grm) \
|
||||
((lfsr_data_t){.u.b.buffer=(const void*)(lfsr_grm_t*){_grm}})
|
||||
|
||||
// writing to an unrelated trunk in the rbyd
|
||||
#define LFSR_DATA_DEFER(_rbyd, ...) \
|
||||
((lfsr_data_t){.u.b.buffer=(const void*)&(const lfsr_defer_t){ \
|
||||
.rbyd=_rbyd, \
|
||||
.attrs=(const lfsr_attr_t[]){__VA_ARGS__}, \
|
||||
.attr_count=sizeof((const lfsr_attr_t[]){__VA_ARGS__}) \
|
||||
/ sizeof(lfsr_attr_t)}})
|
||||
|
||||
static inline bool lfsr_data_ondisk(const lfsr_data_t *data) {
|
||||
return data->u.size & 0x80000000;
|
||||
@@ -1309,6 +1329,14 @@ typedef struct lfsr_attr {
|
||||
_delta, \
|
||||
LFSR_DATA_##_data})
|
||||
|
||||
// TODO do we really need two?
|
||||
#define LFSR_ATTR_(_rid, _type, _delta, _data) \
|
||||
((const lfsr_attr_t){ \
|
||||
_rid, \
|
||||
LFSR_TAG_##_type, \
|
||||
_delta, \
|
||||
LFSR_DATA_##_data})
|
||||
|
||||
#define LFSR_ATTR_NOOP LFSR_ATTR(-1, RM, 0, NULL)
|
||||
|
||||
// TODO make this const again eventually
|
||||
@@ -1696,6 +1724,57 @@ static int lfsr_data_readgrm(lfs_t *lfs, lfsr_data_t *data,
|
||||
}
|
||||
|
||||
|
||||
// deferred tree things
|
||||
typedef struct lfsr_defer {
|
||||
lfsr_rbyd_t *rbyd;
|
||||
const lfsr_attr_t *attrs;
|
||||
lfs_size_t attr_count;
|
||||
} lfsr_defer_t;
|
||||
|
||||
// trunk on-disk encoding
|
||||
|
||||
// 2 leb128s => 10 bytes (worst case)
|
||||
#define LFSR_TRUNK_DSIZE (5+5)
|
||||
|
||||
#define LFSR_DATA_FROMTRUNK(_lfs, _rbyd, _buffer) \
|
||||
lfsr_data_fromtrunk(_lfs, _rbyd, _buffer)
|
||||
|
||||
static lfsr_data_t lfsr_data_fromtrunk(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
uint8_t buffer[static LFSR_TRUNK_DSIZE]) {
|
||||
(void)lfs;
|
||||
lfs_ssize_t d = 0;
|
||||
|
||||
// just write the trunk and weight, the rest of the rbyd is contextual
|
||||
lfs_ssize_t d_ = lfs_toleb128(rbyd->trunk, &buffer[d], 5);
|
||||
LFS_ASSERT(d_ >= 0);
|
||||
d += d_;
|
||||
|
||||
d_ = lfs_toleb128(rbyd->weight, &buffer[d], 5);
|
||||
LFS_ASSERT(d_ >= 0);
|
||||
d += d_;
|
||||
|
||||
return LFSR_DATA_BUF(buffer, d);
|
||||
}
|
||||
|
||||
static int lfsr_data_readtrunk(lfs_t *lfs, lfsr_data_t *data,
|
||||
lfsr_rbyd_t *rbyd) {
|
||||
// note the rest of the rbyd may not actually be backed by memory, so
|
||||
// we need to be conservative here
|
||||
int err = lfsr_data_readleb128(lfs, data, (int32_t*)&rbyd->trunk);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = lfsr_data_readleb128(lfs, data, &rbyd->weight);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Internal operations predeclared here ///
|
||||
//#ifndef LFS_READONLY
|
||||
//static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
||||
@@ -1796,6 +1875,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
// temporary state until we validate a cksum
|
||||
lfs_size_t off = sizeof(uint32_t);
|
||||
lfs_size_t trunk_ = 0;
|
||||
lfs_size_t trunk__ = 0;
|
||||
bool wastrunk = false;
|
||||
lfsr_rid_t weight = 0;
|
||||
lfsr_rid_t weight_ = 0;
|
||||
@@ -1894,8 +1974,8 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
// start of trunk?
|
||||
if (!wastrunk) {
|
||||
wastrunk = true;
|
||||
// save trunk entry point
|
||||
trunk_ = off;
|
||||
// keep track of trunk's entry point
|
||||
trunk__ = off;
|
||||
// reset weight
|
||||
weight_ = 0;
|
||||
}
|
||||
@@ -1911,8 +1991,12 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
// end of trunk?
|
||||
if (!lfsr_tag_isalt(tag)) {
|
||||
wastrunk = false;
|
||||
// update current weight
|
||||
weight = weight_;
|
||||
// update most recent trunk and weight, unless we are a
|
||||
// deferred trunk
|
||||
if (!lfsr_tag_isdeferred(tag)) {
|
||||
trunk_ = trunk__;
|
||||
weight = weight_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1958,11 +2042,12 @@ static int lfsr_rbyd_lookupnext(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
lfsr_srid_t *rid_,
|
||||
lfsr_tag_t *tag_, lfsr_rid_t *weight_, lfsr_data_t *data_) {
|
||||
// these bits should be clear at this point
|
||||
LFS_ASSERT(lfsr_tag_mode(tag) == 0x0000);
|
||||
LFS_ASSERT(!lfsr_tag_isrm(tag));
|
||||
LFS_ASSERT(!lfsr_tag_isgrow(tag));
|
||||
|
||||
// make sure we never look up zero tags, the way we create
|
||||
// unreachable tags has a hole here
|
||||
tag = lfs_max16(tag, 0x1);
|
||||
tag = lfs_max16(lfsr_tag_key(tag), 0x1);
|
||||
|
||||
// keep track of bounds as we descend down the tree
|
||||
lfs_size_t branch = rbyd->trunk;
|
||||
@@ -2002,12 +2087,12 @@ static int lfsr_rbyd_lookupnext(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
// update the tag rid
|
||||
lfsr_srid_t rid__ = upper-1;
|
||||
lfsr_tag_t tag__ = alt;
|
||||
LFS_ASSERT(lfsr_tag_mode(tag__) == 0x0000);
|
||||
LFS_ASSERT(lfsr_tag_deferredmode(tag__) == 0x0000);
|
||||
|
||||
// not what we're looking for?
|
||||
if (!tag__
|
||||
if (!lfsr_tag_key(tag__)
|
||||
|| rid__ < rid
|
||||
|| (rid__ == rid && tag__ < tag)) {
|
||||
|| (rid__ == rid && lfsr_tag_key(tag__) < tag)) {
|
||||
return LFS_ERR_NOENT;
|
||||
}
|
||||
|
||||
@@ -2035,7 +2120,7 @@ static int lfsr_rbyd_lookup(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
lfsr_tag_t *tag_, lfsr_data_t *data_) {
|
||||
lfsr_srid_t rid_;
|
||||
lfsr_tag_t tag__;
|
||||
int err = lfsr_rbyd_lookupnext(lfs, rbyd, rid, lfsr_tag_clearwide(tag),
|
||||
int err = lfsr_rbyd_lookupnext(lfs, rbyd, rid, tag,
|
||||
&rid_, &tag__, NULL, data_);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -2258,7 +2343,7 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
// note both normal and rm wide-tags have the same bounds, really it's
|
||||
// the normal non-wide-tags that are an outlier here
|
||||
if (lfsr_tag_iswide(tag)) {
|
||||
tag_ = lfsr_tag_suptype(lfsr_tag_key(tag));
|
||||
tag_ = lfsr_tag_supkey(tag);
|
||||
other_tag_ = tag_ + 0x100;
|
||||
} else if (lfsr_tag_isrm(tag) || !lfsr_tag_key(tag)) {
|
||||
tag_ = lfsr_tag_key(tag);
|
||||
@@ -2592,10 +2677,8 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
|| (rid_ == rid-lfs_smax32(-delta, 0)
|
||||
&& ((delta > 0 && !lfsr_tag_isgrow(tag))
|
||||
|| (lfsr_tag_iswide(tag)
|
||||
? lfsr_tag_suptype(lfsr_tag_key(tag_))
|
||||
< lfsr_tag_suptype(lfsr_tag_key(tag))
|
||||
: lfsr_tag_key(tag_)
|
||||
< lfsr_tag_key(tag)))))) {
|
||||
? lfsr_tag_supkey(tag_) < lfsr_tag_supkey(tag)
|
||||
: lfsr_tag_key(tag_) < lfsr_tag_key(tag)))))) {
|
||||
if (lfsr_tag_isrm(tag) || !lfsr_tag_key(tag)) {
|
||||
// if removed, make our tag unreachable
|
||||
alt = LFSR_TAG_ALT(GT, B, 0);
|
||||
@@ -2618,10 +2701,8 @@ static int lfsr_rbyd_appendattr(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
|| (rid_ == rid
|
||||
&& ((delta > 0 && !lfsr_tag_isgrow(tag))
|
||||
|| (lfsr_tag_iswide(tag)
|
||||
? lfsr_tag_suptype(lfsr_tag_key(tag_))
|
||||
> lfsr_tag_suptype(lfsr_tag_key(tag))
|
||||
: lfsr_tag_key(tag_)
|
||||
> lfsr_tag_key(tag)))))) {
|
||||
? lfsr_tag_supkey(tag_) > lfsr_tag_supkey(tag)
|
||||
: lfsr_tag_key(tag_) > lfsr_tag_key(tag)))))) {
|
||||
if (lfsr_tag_isrm(tag) || !lfsr_tag_key(tag)) {
|
||||
// if removed, make our tag unreachable
|
||||
alt = LFSR_TAG_ALT(GT, B, 0);
|
||||
@@ -2668,7 +2749,7 @@ leaf:;
|
||||
// can't find trunks during fetch
|
||||
lfs_ssize_t d = lfsr_bd_progtag(lfs, rbyd->block, rbyd->eoff,
|
||||
// rm => null, otherwise strip off control bits
|
||||
(lfsr_tag_isrm(tag) ? LFSR_TAG_NULL : lfsr_tag_key(tag)),
|
||||
(lfsr_tag_isrm(tag) ? LFSR_TAG_NULL : lfsr_tag_deferredkey(tag)),
|
||||
upper_rid - lower_rid - 1 + delta,
|
||||
lfsr_data_size(&data),
|
||||
&rbyd->cksum);
|
||||
@@ -4955,7 +5036,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
// do nothing
|
||||
|
||||
// move tags copy over any tags associated with the source's rid
|
||||
} else if (lfsr_tag_suptype(attrs[i].tag) == LFSR_TAG_MOVE) {
|
||||
} else if (attrs[i].tag == LFSR_TAG_MOVE) {
|
||||
// weighted moves are not supported
|
||||
LFS_ASSERT(attrs[i].delta == 0);
|
||||
const lfsr_mdir_t *mdir__
|
||||
@@ -4984,6 +5065,29 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
}
|
||||
}
|
||||
|
||||
// defer tags append a set of attributes to an unrelated trunk
|
||||
// in our rbyd
|
||||
} else if (attrs[i].tag == LFSR_TAG_DEFER) {
|
||||
const lfsr_defer_t *defer
|
||||
= (const lfsr_defer_t*)attrs[i].data.u.b.buffer;
|
||||
// swap out our trunk/weight temporarily, note we're operating
|
||||
// on a copy so if this fails not _too_ many things will get
|
||||
// messed up
|
||||
//
|
||||
// it is important that these rbyds share eoff/cksum/etc
|
||||
lfs_sswap32(&mdir_.u.m.weight, &defer->rbyd->weight);
|
||||
lfs_swap32(&mdir_.u.m.trunk, &defer->rbyd->trunk);
|
||||
|
||||
// append any deferred attributes
|
||||
int err = lfsr_rbyd_appendattrs(lfs, &mdir_.u.r.rbyd, -1, -1,
|
||||
defer->attrs, defer->attr_count);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_sswap32(&mdir_.u.m.weight, &defer->rbyd->weight);
|
||||
lfs_swap32(&mdir_.u.m.trunk, &defer->rbyd->trunk);
|
||||
|
||||
// write out normal tags normally
|
||||
} else {
|
||||
LFS_ASSERT(!lfsr_tag_isinternal(attrs[i].tag));
|
||||
@@ -5533,8 +5637,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
}
|
||||
return err;
|
||||
}
|
||||
// TODO use suptype == CONFIG here?
|
||||
if (rid != -1 || tag >= LFSR_TAG_GSTATE) {
|
||||
if (rid != -1 || lfsr_tag_suptype(tag) != LFSR_TAG_CONFIG) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7526,6 +7629,20 @@ int lfsr_dir_rewind(lfs_t *lfs, lfsr_dir_t *dir) {
|
||||
|
||||
/// File operations ///
|
||||
|
||||
#define LFSR_FILE_INLINEDDATA 0x80000000
|
||||
|
||||
static bool lfsr_file_isinlineddata(const lfsr_file_t *file) {
|
||||
return file->inlined.u.weight & LFSR_FILE_INLINEDDATA;
|
||||
}
|
||||
|
||||
static bool lfsr_file_isinlinedtree(const lfsr_file_t *file) {
|
||||
return !(file->inlined.u.weight & LFSR_FILE_INLINEDDATA);
|
||||
}
|
||||
|
||||
static lfs_off_t lfsr_file_inlinedweight(const lfsr_file_t *file) {
|
||||
return file->inlined.u.weight & ~LFSR_FILE_INLINEDDATA;
|
||||
}
|
||||
|
||||
static bool lfsr_file_isreadable(uint32_t flags) {
|
||||
return (flags & LFS_O_RDONLY) == LFS_O_RDONLY;
|
||||
}
|
||||
@@ -7534,8 +7651,6 @@ static bool lfsr_file_iswriteable(uint32_t flags) {
|
||||
return (flags & LFS_O_WRONLY) == LFS_O_WRONLY;
|
||||
}
|
||||
|
||||
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
const char *path, uint32_t flags,
|
||||
const struct lfs_file_config *cfg) {
|
||||
@@ -7547,12 +7662,14 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
}
|
||||
}
|
||||
|
||||
// default inlined state
|
||||
// setup file state
|
||||
file->flags = flags;
|
||||
file->cfg = cfg;
|
||||
file->pos = 0;
|
||||
file->inlined_pos = 0;
|
||||
file->inlined = LFSR_DATA_NULL;
|
||||
// default inlined state
|
||||
file->inlined.u.d.block = 0;
|
||||
file->inlined.u.d.off = 0;
|
||||
file->inlined.u.d.size = LFSR_FILE_INLINEDDATA | 0;
|
||||
|
||||
// lookup our parent
|
||||
lfsr_tag_t tag;
|
||||
@@ -7607,7 +7724,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
// read the inline state
|
||||
err = lfsr_mdir_lookup(lfs, &file->m.mdir,
|
||||
file->m.mdir.mid, LFSR_TAG_INLINED,
|
||||
NULL, &file->inlined);
|
||||
NULL, &file->inlined.u.data);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
@@ -7616,7 +7733,7 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file,
|
||||
|
||||
// TODO common function for this?
|
||||
// figure out the total size
|
||||
file->size = lfsr_data_size(&file->inlined);
|
||||
file->size = lfsr_file_inlinedweight(file);
|
||||
|
||||
// allocate buffer if necessary
|
||||
if (file->cfg->buffer) {
|
||||
@@ -7643,6 +7760,9 @@ int lfsr_file_open(lfs_t *lfs, lfsr_file_t *file,
|
||||
return lfsr_file_opencfg(lfs, file, path, flags, &lfsr_file_defaults);
|
||||
}
|
||||
|
||||
// needed in lfsr_file_close
|
||||
int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file);
|
||||
|
||||
int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) {
|
||||
int err = lfsr_file_sync(lfs, file);
|
||||
|
||||
@@ -7657,12 +7777,12 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
|
||||
void *buffer, lfs_size_t size) {
|
||||
LFS_ASSERT(lfsr_file_isreadable(file->flags));
|
||||
// direct read function without state updates
|
||||
int lfsr_file_read_(lfs_t *lfs, const lfsr_file_t *file,
|
||||
lfs_off_t pos, void *buffer, lfs_size_t size) {
|
||||
LFS_ASSERT(pos <= 0x7fffffff);
|
||||
LFS_ASSERT(size <= 0x7fffffff);
|
||||
|
||||
lfs_off_t pos = file->pos;
|
||||
uint8_t *buffer_ = buffer;
|
||||
while (size > 0) {
|
||||
lfs_ssize_t d = size;
|
||||
@@ -7686,23 +7806,19 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
|
||||
}
|
||||
|
||||
// is the data inlined?
|
||||
if (pos < file->inlined_pos + lfsr_data_size(&file->inlined)) {
|
||||
if (pos >= file->inlined_pos) {
|
||||
lfsr_data_t inlined = file->inlined;
|
||||
lfsr_data_add(&inlined, pos - file->inlined_pos);
|
||||
d = lfsr_data_read(lfs, &inlined, buffer_, d);
|
||||
if (d < 0) {
|
||||
return d;
|
||||
}
|
||||
|
||||
pos += d;
|
||||
buffer_ += d;
|
||||
size -= d;
|
||||
continue;
|
||||
if (lfsr_file_isinlineddata(file)
|
||||
&& pos < lfsr_file_inlinedweight(file)) {
|
||||
lfsr_data_t data = file->inlined.u.data;
|
||||
lfsr_data_add(&data, pos);
|
||||
d = lfsr_data_read(lfs, &data, buffer_, d);
|
||||
if (d < 0) {
|
||||
return d;
|
||||
}
|
||||
|
||||
// inlined data takes priority
|
||||
d = lfs_min32(d, file->inlined_pos - pos);
|
||||
pos += d;
|
||||
buffer_ += d;
|
||||
size -= d;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO
|
||||
@@ -7710,9 +7826,82 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
|
||||
break;
|
||||
}
|
||||
|
||||
lfs_size_t read = pos - file->pos;
|
||||
file->pos = pos;
|
||||
return read;
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
|
||||
void *buffer, lfs_size_t size) {
|
||||
LFS_ASSERT(lfsr_file_isreadable(file->flags));
|
||||
|
||||
lfs_ssize_t d = lfs_min32(size, file->size - file->pos);
|
||||
int err = lfsr_file_read_(lfs, file, file->pos, buffer, d);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
file->pos += d;
|
||||
return d;
|
||||
}
|
||||
|
||||
static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
|
||||
while (file->buffer_size > 0) {
|
||||
// do we need an inlined tree?
|
||||
if (lfsr_file_isinlineddata(file)) {
|
||||
// TODO rm? we haven't updated file->size yet!
|
||||
// // we shouldn't reach this point if we still fit entirely
|
||||
// // in a simple inlined file
|
||||
// LFS_ASSERT(file->size > lfs_min32(
|
||||
// lfs->cfg->cache_size,
|
||||
// lfs->cfg->inline_size));
|
||||
|
||||
// TODO
|
||||
// do we carve out any data from the inlined data?
|
||||
|
||||
file->inlined.u.t.trunk = 0;
|
||||
file->inlined.u.t.weight = 0;
|
||||
|
||||
int err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(file->m.mdir.mid, DEFER, 0, DEFER(
|
||||
// TODO bit of a hack...
|
||||
(lfsr_rbyd_t*)&file->inlined,
|
||||
LFSR_ATTR_(file->buffer_pos,
|
||||
DEFERRED(INLINED), +file->buffer_size, BUF(
|
||||
file->buffer, file->buffer_size))))));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
file->buffer_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// // do we fit in our inlined tree?
|
||||
// //
|
||||
// // this is a complex question since we may be carving out leaves of the
|
||||
// // inlined tree, we need to find these leaves to know for sure
|
||||
// //
|
||||
// // TODO can we somehow fit an rbyd struct in the file_t so we don't
|
||||
// // need this copy?
|
||||
// lfsr_rbyd_t rbyd;
|
||||
// rbyd.block = file->m.mdir.block;
|
||||
// rbyd.trunk = file->inlined.u.t.trunk;
|
||||
// rbyd.weight = file->inlined.u.t.weight;
|
||||
//
|
||||
// lfsr_srid_t left_rid;
|
||||
// lfsr_tag_t left_tag;
|
||||
// lfsr_rid_t left_weight;
|
||||
// lfsr_data_t left_data;
|
||||
// int err = lfsr_rbyd_lookupnext(lfs, &rbyd, file->buffer_pos, 0,
|
||||
// &left_rid, &left_tag, &left_weight, &left_data);
|
||||
// if (err
|
||||
|
||||
// TODO
|
||||
break;
|
||||
//LFS_ASSERT(false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
|
||||
@@ -7725,9 +7914,15 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
|
||||
int err;
|
||||
while (size > 0) {
|
||||
// try to fill our write buffer
|
||||
if (pos >= file->buffer_pos
|
||||
&& pos <= file->buffer_pos + file->buffer_size
|
||||
&& pos < file->buffer_pos + lfs->cfg->cache_size) {
|
||||
if (file->buffer_size == 0
|
||||
|| (pos >= file->buffer_pos
|
||||
&& pos <= file->buffer_pos + file->buffer_size
|
||||
&& pos < file->buffer_pos + lfs->cfg->cache_size)) {
|
||||
// unused buffer? we can move this where we need it
|
||||
if (file->buffer_size == 0) {
|
||||
file->buffer_pos = pos;
|
||||
}
|
||||
|
||||
lfs_size_t d = lfs_min32(
|
||||
size,
|
||||
lfs->cfg->cache_size - (pos - file->buffer_pos));
|
||||
@@ -7743,15 +7938,11 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO
|
||||
LFS_ASSERT(false);
|
||||
|
||||
// // flush our write buffer to make it useable, first condition can
|
||||
// // no longer fail
|
||||
// err = lfsr_file_flush(lfs, file);
|
||||
// if (err) {
|
||||
// return err;
|
||||
// }
|
||||
// flush our buffer so the above can't fail
|
||||
err = lfsr_file_flushbuffer(lfs, file);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
lfs_size_t written = pos - file->pos;
|
||||
@@ -7787,19 +7978,72 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
}
|
||||
|
||||
if (file->flags & LFS_F_UNSYNCED) {
|
||||
// TODO
|
||||
// TODO what if buffer_size > inlined_size?
|
||||
LFS_ASSERT(file->buffer_pos == 0);
|
||||
// commit our file's metadata
|
||||
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(file->m.mdir.mid, WIDE(RM), 0, NULL),
|
||||
(file->buffer_size > 0
|
||||
? LFSR_ATTR(file->m.mdir.mid,
|
||||
WIDE(INLINED), 0, BUF(
|
||||
file->buffer, file->buffer_size))
|
||||
: LFSR_ATTR_NOOP)));
|
||||
if (err) {
|
||||
goto failed;
|
||||
// TODO should we also update file to be unbuffered after syncing
|
||||
// inlined data?
|
||||
|
||||
// handle simple inlined files specially
|
||||
if (file->size <= lfs_min32(
|
||||
lfs->cfg->cache_size,
|
||||
lfs->cfg->inline_size)) {
|
||||
// read any unread data
|
||||
//
|
||||
// note we don't update buffer_pos until after reading! this
|
||||
// prevents lfsr_file_read from reading from our buffer by mistake
|
||||
//
|
||||
// TODO note this risks really messing up the file buffer if we
|
||||
// error, is that ok?
|
||||
memmove(file->buffer + file->buffer_pos,
|
||||
file->buffer,
|
||||
file->buffer_size);
|
||||
err = lfsr_file_read_(lfs, file, 0, file->buffer, file->buffer_pos);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
err = lfsr_file_read_(lfs, file,
|
||||
file->buffer_pos + file->buffer_size,
|
||||
file->buffer + file->buffer_pos + file->buffer_size,
|
||||
file->size - (file->buffer_pos + file->buffer_size));
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
file->buffer_pos = 0;
|
||||
file->buffer_size = file->size;
|
||||
|
||||
// commit our file's metadata
|
||||
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
|
||||
(file->buffer_size > 0
|
||||
? LFSR_ATTR(file->m.mdir.mid,
|
||||
WIDE(INLINED), 0, BUF(
|
||||
file->buffer, file->buffer_size))
|
||||
: LFSR_ATTR(file->m.mdir.mid,
|
||||
WIDE(RM(STRUCT)), 0, NULL))));
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
} else {
|
||||
// first make sure to flush our buffer
|
||||
//
|
||||
// TODO can we avoid an extra commit here? this may be too complex
|
||||
// to be worth doing...
|
||||
err = lfsr_file_flushbuffer(lfs, file);
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
LFS_ASSERT(!lfsr_file_isinlineddata(file));
|
||||
|
||||
// now commit our file's metadata
|
||||
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
|
||||
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
|
||||
LFSR_ATTR(file->m.mdir.mid, WIDE(TRUNK), 0, FROMTRUNK(
|
||||
// TODO too much of a hack?
|
||||
lfs, (const lfsr_rbyd_t*)&file->inlined, trunk_buf))));
|
||||
if (err) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
file->flags &= ~LFS_F_UNSYNCED;
|
||||
|
||||
@@ -500,8 +500,25 @@ typedef struct lfsr_file {
|
||||
uint8_t *buffer;
|
||||
lfs_size_t buffer_size;
|
||||
|
||||
lfs_off_t inlined_pos;
|
||||
lfsr_data_t inlined;
|
||||
struct {
|
||||
union {
|
||||
// note sign bit indicates if data is a single inlined data, or an
|
||||
// inlined tree, this works because inlined data is always on disk,
|
||||
// so data.size always has sign=1
|
||||
lfs_soff_t weight;
|
||||
lfsr_data_t data;
|
||||
struct {
|
||||
lfs_ssize_t size;
|
||||
lfs_size_t off;
|
||||
lfs_block_t block;
|
||||
} d;
|
||||
struct {
|
||||
lfs_soff_t weight;
|
||||
lfs_size_t trunk;
|
||||
lfs_size_t overhead;
|
||||
} t;
|
||||
} u;
|
||||
} inlined;
|
||||
|
||||
const struct lfs_file_config *cfg;
|
||||
} lfsr_file_t;
|
||||
|
||||
@@ -426,6 +426,73 @@ code = '''
|
||||
'''
|
||||
|
||||
|
||||
# try a larger file? this should need to write an inlined tree
|
||||
[cases.test_ftree_sprout]
|
||||
defines.SIZE = '2*CACHE_SIZE'
|
||||
defines.REMOUNT = [false, true]
|
||||
reentrant = true
|
||||
code = '''
|
||||
// format once per test
|
||||
lfs_t lfs;
|
||||
int err = lfsr_mount(&lfs, CFG);
|
||||
if (err) {
|
||||
lfsr_format(&lfs, CFG) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// create a file
|
||||
lfsr_file_t file;
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
|
||||
uint8_t wbuf[8192];
|
||||
uint32_t prng = 42;
|
||||
for (lfs_size_t i = 0; i < SIZE; i++) {
|
||||
wbuf[i] = TEST_PRNG(&prng);
|
||||
}
|
||||
lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE;
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
// remount?
|
||||
if (REMOUNT) {
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
lfsr_mount(&lfs, CFG) => 0;
|
||||
}
|
||||
|
||||
// check our file with stat
|
||||
struct lfs_info info;
|
||||
lfsr_stat(&lfs, "hello", &info) => 0;
|
||||
assert(strcmp(info.name, "hello") == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
// and with dir read
|
||||
lfsr_dir_t dir;
|
||||
lfsr_dir_open(&lfs, &dir, "/") => 0;
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS_TYPE_DIR);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => 0;
|
||||
assert(strcmp(info.name, "hello") == 0);
|
||||
assert(info.type == LFS_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT;
|
||||
lfsr_dir_close(&lfs, &dir) => 0;
|
||||
|
||||
// try reading our file
|
||||
lfsr_file_open(&lfs, &file, "hello", LFS_O_RDONLY) => 0;
|
||||
// is size correct?
|
||||
lfsr_file_size(&lfs, &file) => SIZE;
|
||||
// try reading
|
||||
uint8_t rbuf[8192];
|
||||
lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => SIZE;
|
||||
assert(memcmp(rbuf, wbuf, SIZE) == 0);
|
||||
lfsr_file_close(&lfs, &file) => 0;
|
||||
|
||||
lfsr_unmount(&lfs) => 0;
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user