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:
Christopher Haster
2023-09-18 23:27:53 -05:00
parent 6daa503ee2
commit c3533ab816
3 changed files with 431 additions and 103 deletions
+345 -101
View File
@@ -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;
+19 -2
View File
@@ -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;
+67
View File
@@ -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;
'''