Files
littlefs/tests/test_attrs.toml
T
Christopher Haster f80db15c7e attrs: (Re)implemented file-attached custom attributes
Unlike lfsr_setattr/getattr/etc, file-attached custom attributes are
RAM-backed snapshots attached to, well, files, that can be committed
atomically along with the file's contents. Great for power-loss
resilience, but boy does it make a mess of an API.

This API was really where custom attributes needed some TLC.

The biggest change is how file-attached custom attributes interact with
file sync broadcasting.

A common complaint from users is that setting custom attributes did not
update attributes in open file handles. This behavior is _very_
inconsistent with other filesystems and created a lot of confusion.
Since we're nailing down littlefs's snapshot/broadcasting model as a
part of larger changes, it makes sense to also nail down how custom
attributes interact.

In the new model:

- Custom attributes are still in-RAM snapshots. Updates do not
  immediately take effect, even across write calls.

- On lfsr_file_sync or lfsr_file_close, custom attributes are written
  atomically to disk and broadcasted to all open file handles.

- lfsr_setattr/removeattr also take part in attribute broadcasting. When
  called, lfsr_setattr/removeattr updates the attribute on disk and
  broadcasts the attribute changes to all open file handles.

- Desynced files do _not_ recieve any attribute broadcasts in the same
  way they do not recieve any data broadcasts.

This should hopefully make littlefs behave much more consistently with
other filesystems, while still maintaining a well-defined snapshot and
power-loss properties.

---

The lfs_attr struct also gained several new fields:

  // Custom attribute structure, used to describe custom attributes
  // committed atomically during file writes.
  struct lfs_attr {
      // Type of attribute
      //
      // Note some of this range is reserved:
      // 0x00-0x7f - Free for custom attributes
      // 0x80-0xff - May be assigned a standard attribute
      uint8_t type;

      // Flags that control how attr is read/written/removed
      uint8_t flags;

      // Pointer the buffer where the attr will be read/written
      void *buffer;

      // Size of the attr buffer in bytes, this can be set to
      // LFS_ERR_NOATTR to remove the attr
      lfs_ssize_t buffer_size;

      // Optional pointer to a mutable attr size, updated on read/write,
      // set to LFS_ERR_NOATTR if attr does not exist
      //
      // Defaults to buffer_size if NULL
      lfs_ssize_t *size;
  };

Which are useful for several new features:

- lfs_attr now supports LFS_A_RDONLY/WRONLY/RDWR modes.

  One of the blockers for attribute broadcasting was in-ROM attributes,
  where broadcast updates would hard-fault. But now if you mark in-ROM
  attributes as WRONLY, and in-RAM attributes as RDWR, this problem goes
  away.

- When opened, lfs_attr now optionally writes the attribute size to the
  indirect size field.

  No more hacky zero padding and not knowing an attribute's size.

  Note this follows the same rules as lfsr_getattr, so it does truncate
  if the buffer is too small.

  The size field can also be set to NULL, in which case lfs_attr
  defaults to the buffer_size. This can be quite useful for pure
  ROM-backed attributes.

- Missing attributes are now represented with size=LFS_ERR_NOATTR.

  No more zero-sized vs missing attribute ambiguity.

  This also makes it possible to remove attributes via lfs_attr, by
  setting the size to LFS_ERR_NOATTR manually.

  This does lead to a bit of a quirk where buffer_size can be
  LFS_ERR_NOATTR, which is a bit weird but at least consistent.

- Changes to lfs_attrs will now always trigger file syncs by default.

  Previously, if you changed an attribute, you had to also change the
  file's contents for it to get written to disk. As pointed out by users
  this is both surprising and difficult to work around.

  Solving this is quite tricky since there's no real signalling
  mechanism between attribute buffers and littlefs. The best I could
  come up with is to read attributes from disk during lfsr_file_sync to
  see if anything changed.

  At the very least, the new flag LFS_A_LAZY restores the old behavior
  in case the extra reads in lfsr_file_sync are problematic.

  Though I suspect _most_ calls to lfsr_file_sync immediately follow
  intentional changes to a file. It would be interesting to know of
  examples where this is not the case...

These new fields do increase the size of lfs_attr, which is a downside,
but thanks to flags fitting in type's padding, this is only an increase
from 3 words (12 bytes) -> 4 words (16 bytes).

---

Other implementation notes:

- I did try to implement LFS_A_CREAT/EXCL in lfs_attr but this proved
  to be too messy and inconsistent, so I dropped the idea for now.

  The idea was to error with NOATTR/EXIST if the lfs_attr flag in
  incompatible with what's on disk, but this led to a lot of complexity
  for what is a pretty niche use case.

  It's also inconsistent with rdonly attrs, which do _not_ error with
  NOATTR during lfsr_file_opencfg, because that would be kind of
  annoying.

- Having both `struct lfs_attr` and `lfsr_attr_t` to represent different
  things in the codebase is both fragile and confusing. One of these
  needs to change, probably `lfsr_attr_t`.

  If only I could think of a good name...

  One of the nice side-effects of the now-dropped uattr/sattr split was
  avoiding this conflict.

- We still need more tests related to how custom attributes interact
  with other filesystem operations, but I wanted to get what is
  currently working committed, see the TODOs in test_attrs.toml.

All of the new bells and whistles unfortunately do add up.
lfsr_file_sync is also the root of our current stack hot-path, so the
additional attr also adds a bit of stack:

           code          stack
  before: 37116           2608
  after:  38104 (+2.7%)   2624 (+0.6%)

Still, having a consistent and flexible API is well worth it.

Though I do think at some point we should add a compile-time option to
opt-out of custom attributes (LFS_NO_ATTR?).
2024-08-23 01:10:16 -05:00

2857 lines
92 KiB
TOML

# Custom attribute tests
after = ['test_files', 'test_fsync', 'test_forphans']
## General setattr/getattr tests
# test some simple attr operations
[cases.test_attrs_setattr]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test truncated getattr calls still work
[cases.test_attrs_setattr_trunc]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
defines.BUFSIZE = [1, 4, 7]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
// mark rbuf so we can detect overflow
uint8_t rbuf[256];
rbuf[BUFSIZE] = '!';
// try reading truncated attrs
lfsr_getattr(&lfs, path, 'a', rbuf, BUFSIZE) => BUFSIZE;
assert(memcmp(rbuf, a, BUFSIZE) == 0);
assert(rbuf[BUFSIZE] == '!');
lfsr_getattr(&lfs, path, 'b', rbuf, BUFSIZE) => BUFSIZE;
assert(memcmp(rbuf, b, BUFSIZE) == 0);
assert(rbuf[BUFSIZE] == '!');
lfsr_getattr(&lfs, path, 'c', rbuf, BUFSIZE) => BUFSIZE;
assert(memcmp(rbuf, c, BUFSIZE) == 0);
assert(rbuf[BUFSIZE] == '!');
// try reading the full attrs
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test ENOATTR works
[cases.test_attrs_setattr_noattr]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'b') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'c') => LFS_ERR_NOATTR;
// try reading attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
// try setting attrs but without CREAT
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a), 0) => LFS_ERR_NOATTR;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b), 0) => LFS_ERR_NOATTR;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c), 0) => LFS_ERR_NOATTR;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// make sure setattr didn't quietly create attrs
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'b') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'c') => LFS_ERR_NOATTR;
// try reading attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
}
lfsr_unmount(&lfs) => 0;
'''
# test EEXIST works
[cases.test_attrs_setattr_excl]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try to create again with excl, this should fail
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
lfsr_setattr(&lfs, path, 'a', a_, strlen(a_),
LFS_A_CREAT | LFS_A_EXCL) => LFS_ERR_EXIST;
const char *b_ = "Three slash four cup butter or margarine.";
lfsr_setattr(&lfs, path, 'b', b_, strlen(b_),
LFS_A_CREAT | LFS_A_EXCL) => LFS_ERR_EXIST;
const char *c_ = "One and two third cups granulated sugar.";
lfsr_setattr(&lfs, path, 'c', c_, strlen(b_),
LFS_A_CREAT | LFS_A_EXCL) => LFS_ERR_EXIST;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that updating attrs works
[cases.test_attrs_setattr_update]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
# update based on this mask
defines.MASK = 'range(0x8)'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// rewrite some attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
if (MASK & 0x1) {
lfsr_setattr(&lfs, path, 'a', a_, strlen(a_), 0) => 0;
}
const char *b_ = "Three slash four cup butter or margarine.";
if (MASK & 0x2) {
lfsr_setattr(&lfs, path, 'b', b_, strlen(b_), 0) => 0;
}
const char *c_ = "One and two third cups granulated sugar.";
if (MASK & 0x4) {
lfsr_setattr(&lfs, path, 'c', c_, strlen(c_), 0) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
if (MASK & 0x1) {
lfsr_sizeattr(&lfs, path, 'a') => strlen(a_);
} else {
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
}
if (MASK & 0x2) {
lfsr_sizeattr(&lfs, path, 'b') => strlen(b_);
} else {
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
}
if (MASK & 0x4) {
lfsr_sizeattr(&lfs, path, 'c') => strlen(c_);
} else {
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
}
// try reading attrs
uint8_t rbuf[256];
if (MASK & 0x1) {
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a_);
assert(memcmp(rbuf, a_, strlen(a_)) == 0);
} else {
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
}
if (MASK & 0x2) {
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b_);
assert(memcmp(rbuf, b_, strlen(b_)) == 0);
} else {
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
}
if (MASK & 0x4) {
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c_);
assert(memcmp(rbuf, c_, strlen(c_)) == 0);
} else {
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
}
lfsr_unmount(&lfs) => 0;
'''
# test removing attrs
[cases.test_attrs_removeattr]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
# remove based on this mask
defines.MASK = 'range(0x8)'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// remove some attrs
if (MASK & 0x1) {
lfsr_removeattr(&lfs, path, 'a') => 0;
}
if (MASK & 0x2) {
lfsr_removeattr(&lfs, path, 'b') => 0;
}
if (MASK & 0x4) {
lfsr_removeattr(&lfs, path, 'c') => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
if (MASK & 0x1) {
lfsr_sizeattr(&lfs, path, 'a') => LFS_ERR_NOATTR;
} else {
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
}
if (MASK & 0x2) {
lfsr_sizeattr(&lfs, path, 'b') => LFS_ERR_NOATTR;
} else {
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
}
if (MASK & 0x4) {
lfsr_sizeattr(&lfs, path, 'c') => LFS_ERR_NOATTR;
} else {
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
}
// try reading the full attrs
uint8_t rbuf[256];
if (MASK & 0x1) {
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
} else {
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf))
=> strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
}
if (MASK & 0x2) {
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
} else {
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf))
=> strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
}
if (MASK & 0x4) {
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
} else {
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf))
=> strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
}
lfsr_unmount(&lfs) => 0;
'''
# test the full range of attrs
[cases.test_attrs_all]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
defines.SIZE = 4
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// try creating every attr, this tests encoding quirks
uint32_t prng = 42;
for (uint16_t a = 0; a < 0x100; a++) {
// create the attr
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try getting the attr size
lfsr_sizeattr(&lfs, path, a) => SIZE;
// try reading the attr
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
// remove the attr
lfsr_removeattr(&lfs, path, a) => 0;
}
lfsr_unmount(&lfs) => 0;
'''
# test creating a bunch of attrs
[cases.test_attrs_many]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
defines.M = 40
defines.SIZE = 4
defines.COMPACT = [false, true]
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// create M attrs
uint32_t prng = 42;
for (uint16_t a = 0; a < M; a++) {
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
}
// try compacting?
if (COMPACT) {
lfsr_fs_gc(&lfs, -1, LFS_GC_COMPACT) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
for (uint16_t a = 0; a < M; a++) {
lfsr_sizeattr(&lfs, path, a) => SIZE;
}
// try reading the attrs
prng = 42;
for (uint16_t a = 0; a < M; a++) {
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
}
lfsr_unmount(&lfs) => 0;
'''
# test creating a bunch of attrs on a bunch of files
[cases.test_attrs_many_many]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
defines.N = 64
defines.M = 4
defines.SIZE = 4
defines.COMPACT = [false, true]
defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create N files
uint32_t prng = 42;
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
// create a file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", i);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow"))
=> strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
sprintf(path, "armadillo%03x", i);
lfsr_mkdir(&lfs, path) => 0;
}
// create M attrs
for (uint16_t a = 0; a < M; a++) {
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
}
}
// try compacting?
if (COMPACT) {
lfsr_fs_gc(&lfs, -1, LFS_GC_COMPACT) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
prng = 42;
for (lfs_size_t x = 0; x < N; x++) {
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// try getting the attr sizes
for (uint16_t a = 0; a < M; a++) {
lfsr_sizeattr(&lfs, path, a) => SIZE;
}
// try reading the attrs
for (uint16_t a = 0; a < M; a++) {
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26);
}
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
}
}
}
lfsr_unmount(&lfs) => 0;
'''
# fuzz attrs
[cases.test_attrs_fuzz]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
# FILETYPE=2 => root
defines.FILETYPE = [0, 1, 2]
defines.M = 10
defines.SIZE = 4
defines.OPS = '4*M'
defines.SEED = 'range(20)'
fuzz = 'SEED'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else if (FILETYPE == 1) {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
// do nothing for root
} else {
path = "/";
}
// set up a simulation to compare against
uint32_t *sim_prngs = malloc(M*sizeof(uint32_t));
memset(sim_prngs, 0, M*sizeof(uint32_t));
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 2;
// create an attr?
if (op == 0) {
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// choose a prng
uint32_t wprng = TEST_PRNG(&prng);
// create the attr
uint32_t wprng_ = wprng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
if (sim_prngs[a]) {
lfsr_setattr(&lfs, path, a, wbuf, SIZE, 0) => 0;
} else {
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
}
// update our sim
sim_prngs[a] = wprng;
// remove an attr?
} else if (op == 1) {
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// remove the attr
if (sim_prngs[a]) {
lfsr_removeattr(&lfs, path, a) => 0;
} else {
lfsr_removeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
// update our sim
sim_prngs[a] = 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting each attr size
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[a]) {
lfsr_sizeattr(&lfs, path, a) => SIZE;
} else {
lfsr_sizeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
}
// try reading each attr
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[a]) {
uint32_t wprng_ = sim_prngs[a];
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
} else {
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
}
}
}
// clean up sim/lfs
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# fuzz attrs on multiple files
[cases.test_attrs_fuzz_fuzz]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
defines.N = 64
defines.M = 4
defines.SIZE = 4
defines.OPS = '4*N*M'
defines.SEED = 'range(20)'
fuzz = 'SEED'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create N files
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
// create a file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", i);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow"))
=> strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
sprintf(path, "armadillo%03x", i);
lfsr_mkdir(&lfs, path) => 0;
}
}
// set up a simulation to compare against
uint32_t *sim_prngs = malloc(N*M*sizeof(uint32_t));
memset(sim_prngs, 0, N*M*sizeof(uint32_t));
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 2;
// create an attr?
if (op == 0) {
// choose a file
lfs_size_t x = TEST_PRNG(&prng) % N;
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// choose a prng
uint32_t wprng = TEST_PRNG(&prng);
// create the attr
uint32_t wprng_ = wprng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
if (sim_prngs[x*M+a]) {
lfsr_setattr(&lfs, path, a, wbuf, SIZE, 0) => 0;
} else {
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
}
// update our sim
sim_prngs[x*M+a] = wprng;
// remove an attr?
} else if (op == 1) {
// choose a file
lfs_size_t x = TEST_PRNG(&prng) % N;
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// remove the attr
if (sim_prngs[x*M+a]) {
lfsr_removeattr(&lfs, path, a) => 0;
} else {
lfsr_removeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
// update our sim
sim_prngs[x*M+a] = 0;
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
for (lfs_size_t x = 0; x < N; x++) {
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// try getting each attr size
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[x*M+a]) {
lfsr_sizeattr(&lfs, path, a) => SIZE;
} else {
lfsr_sizeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
}
// try reading each attr
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[x*M+a]) {
uint32_t wprng_ = sim_prngs[x*M+a];
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
} else {
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
}
}
}
}
// clean up sim/lfs
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
# test that removing a file removes all attrs
[cases.test_attrs_rm]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// remove the file
lfsr_remove(&lfs, path) => 0;
// create the file again
// file?
if (FILETYPE == 0) {
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
// dir?
} else {
lfsr_mkdir(&lfs, path) => 0;
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'b') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'c') => LFS_ERR_NOATTR;
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
}
lfsr_unmount(&lfs) => 0;
'''
# test that moving onto a file removes all attrs
[cases.test_attrs_mv_dst]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// create a file?
if (FILETYPE == 0) {
path = "cat";
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
path = "armadillo";
lfsr_mkdir(&lfs, path) => 0;
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, path, 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, path, 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, path, 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// create another file and move it onto our file
// file?
if (FILETYPE == 0) {
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "beaver", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
// dir?
} else {
lfsr_mkdir(&lfs, "beaver") => 0;
}
lfsr_rename(&lfs, "beaver", path) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'b') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, path, 'c') => LFS_ERR_NOATTR;
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
}
lfsr_unmount(&lfs) => 0;
'''
# test that moves bring over all attrs
[cases.test_attrs_mv_src]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
defines.REPLACE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
const char *path;
// file?
if (FILETYPE == 0) {
path = "cat";
// dir?
} else {
path = "armadillo";
}
// if replacing create a file to replace
if (REPLACE) {
// create a file?
if (FILETYPE == 0) {
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path, LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow"))
=> strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
lfsr_mkdir(&lfs, path) => 0;
}
}
// create a file?
if (FILETYPE == 0) {
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "beaver",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "miao", strlen("miao"))
=> strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
lfsr_mkdir(&lfs, "beaver") => 0;
}
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "beaver", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "beaver", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "beaver", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// move file
lfsr_rename(&lfs, "beaver", path) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, path, 'a') => strlen(a);
lfsr_sizeattr(&lfs, path, 'b') => strlen(b);
lfsr_sizeattr(&lfs, path, 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, path, 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, path, 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# fuzz attrs mixed with file moves and removes
[cases.test_attrs_mvrm_fuzz_fuzz]
# type of file to attach attrs to
# FILETYPE=0 => regular file
# FILETYPE=1 => directory
defines.FILETYPE = [0, 1]
defines.N = 64
defines.M = 4
defines.SIZE = 4
defines.OPS = '4*N*M'
defines.SEED = 'range(20)'
fuzz = 'SEED'
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create N files
for (lfs_size_t i = 0; i < N; i++) {
char path[256];
// create a file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", i);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow"))
=> strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
sprintf(path, "armadillo%03x", i);
lfsr_mkdir(&lfs, path) => 0;
}
}
// set up a simulation to compare against
uint32_t *sim_prngs = malloc(N*M*sizeof(uint32_t));
memset(sim_prngs, 0, N*M*sizeof(uint32_t));
uint32_t prng = SEED;
for (lfs_size_t i = 0; i < OPS; i++) {
// choose which operation to do
uint8_t op = TEST_PRNG(&prng) % 4;
// create an attr?
if (op == 0) {
// choose a file
lfs_size_t x = TEST_PRNG(&prng) % N;
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// choose a prng
uint32_t wprng = TEST_PRNG(&prng);
// create the attr
uint32_t wprng_ = wprng;
uint8_t wbuf[SIZE];
for (lfs_size_t j = 0; j < SIZE; j++) {
wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
if (sim_prngs[x*M+a]) {
lfsr_setattr(&lfs, path, a, wbuf, SIZE, 0) => 0;
} else {
lfsr_setattr(&lfs, path, a, wbuf, SIZE,
LFS_A_CREAT | LFS_A_EXCL) => 0;
}
// update our sim
sim_prngs[x*M+a] = wprng;
// remove an attr?
} else if (op == 1) {
// choose a file
lfs_size_t x = TEST_PRNG(&prng) % N;
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// choose an attr
uint8_t a = TEST_PRNG(&prng) % M;
// remove the attr
if (sim_prngs[x*M+a]) {
lfsr_removeattr(&lfs, path, a) => 0;
} else {
lfsr_removeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
// update our sim
sim_prngs[x*M+a] = 0;
// remove a file?
} else if (op == 2) {
// choose a file
lfs_size_t x = TEST_PRNG(&prng) % N;
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// remove the file
lfsr_remove(&lfs, path) => 0;
// but recreate the file so we always have something to
// attach attrs to
// create a file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "miao", strlen("miao"))
=> strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
sprintf(path, "armadillo%03x", x);
lfsr_mkdir(&lfs, path) => 0;
}
// update our sim
memset(&sim_prngs[x*M], 0, M*sizeof(uint32_t));
// rename a file?
} else if (op == 3) {
// choose two files
lfs_size_t x = TEST_PRNG(&prng) % N;
lfs_size_t y = TEST_PRNG(&prng) % N;
char path[256];
char path_[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
sprintf(path_, "cat%03x", y);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
sprintf(path_, "armadillo%03x", y);
}
// rename the file
lfsr_rename(&lfs, path, path_) => 0;
if (x != y) {
// but recreate the file so we always have something to
// attach attrs to
// create a file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
lfsr_file_t file;
lfsr_file_open(&lfs, &file, path,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "nyan", strlen("nyan"))
=> strlen("nyan");
lfsr_file_close(&lfs, &file) => 0;
// create a dir?
} else {
sprintf(path, "armadillo%03x", x);
lfsr_mkdir(&lfs, path) => 0;
}
// update our sim
memcpy(&sim_prngs[y*M], &sim_prngs[x*M], M*sizeof(uint32_t));
memset(&sim_prngs[x*M], 0, M*sizeof(uint32_t));
}
}
}
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
for (lfs_size_t x = 0; x < N; x++) {
char path[256];
// file?
if (FILETYPE == 0) {
sprintf(path, "cat%03x", x);
// dir?
} else {
sprintf(path, "armadillo%03x", x);
}
// try getting each attr size
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[x*M+a]) {
lfsr_sizeattr(&lfs, path, a) => SIZE;
} else {
lfsr_sizeattr(&lfs, path, a) => LFS_ERR_NOATTR;
}
}
// try reading each attr
for (uint16_t a = 0; a < M; a++) {
if (sim_prngs[x*M+a]) {
uint32_t wprng_ = sim_prngs[x*M+a];
uint8_t wbuf[SIZE];
for (lfs_size_t i = 0; i < SIZE; i++) {
wbuf[i] = 'a' + (TEST_PRNG(&wprng_) % 26);
}
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf)) => SIZE;
assert(memcmp(rbuf, wbuf, SIZE) == 0);
} else {
uint8_t rbuf[256];
lfsr_getattr(&lfs, path, a, rbuf, sizeof(rbuf))
=> LFS_ERR_NOATTR;
}
}
}
}
// clean up sim/lfs
free(sim_prngs);
lfsr_unmount(&lfs) => 0;
'''
## Tests involving file-attached attrs
# test that file-attached attrs are read correctly
[cases.test_attrs_fattr_get]
defines.MODE = ['LFS_A_RDONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with these attrs
uint8_t a_buf[256];
lfs_ssize_t a_size = -1;
uint8_t b_buf[256];
lfs_ssize_t b_size = -1;
uint8_t c_buf[256];
lfs_ssize_t c_size = -1;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = sizeof(a_buf),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = sizeof(b_buf),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = sizeof(c_buf),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
// did we read the attrs correctly?
if (MUTSIZE) {
assert(a_size == strlen(a));
}
assert(memcmp(a_buf, a, strlen(a)) == 0);
if (MUTSIZE) {
assert(b_size == strlen(b));
}
assert(memcmp(b_buf, b, strlen(b)) == 0);
if (MUTSIZE) {
assert(c_size == strlen(c));
}
assert(memcmp(c_buf, c, strlen(c)) == 0);
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that truncate attrs will work
[cases.test_attrs_fattr_trunc]
defines.MODE = ['LFS_A_RDONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
defines.BUFSIZE = [1, 4, 7]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with these attrs, marking bufs so we can
// detect overflow
uint8_t a_buf[256];
a_buf[BUFSIZE] = '!';
lfs_ssize_t a_size = -1;
uint8_t b_buf[256];
b_buf[BUFSIZE] = '!';
lfs_ssize_t b_size = -1;
uint8_t c_buf[256];
c_buf[BUFSIZE] = '!';
lfs_ssize_t c_size = -1;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = BUFSIZE,
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = BUFSIZE,
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = BUFSIZE,
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
// did we read the attrs correctly? no overflow?
if (MUTSIZE) {
assert(a_size == BUFSIZE);
}
assert(memcmp(a_buf, a, BUFSIZE) == 0);
assert(a_buf[BUFSIZE] == '!');
if (MUTSIZE) {
assert(b_size == BUFSIZE);
}
assert(memcmp(b_buf, b, BUFSIZE) == 0);
assert(a_buf[BUFSIZE] == '!');
if (MUTSIZE) {
assert(c_size == BUFSIZE);
}
assert(memcmp(c_buf, c, BUFSIZE) == 0);
assert(a_buf[BUFSIZE] == '!');
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that missing attrs are read correctly
[cases.test_attrs_fattr_noattr]
defines.MODE = ['LFS_A_RDONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// try opening a file with missing attrs
uint8_t a_buf[256];
lfs_ssize_t a_size = -1;
uint8_t b_buf[256];
lfs_ssize_t b_size = -1;
uint8_t c_buf[256];
lfs_ssize_t c_size = -1;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = sizeof(a_buf),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = sizeof(b_buf),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = sizeof(c_buf),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
// did we read the attrs correctly?
if (MUTSIZE) {
assert(a_size == LFS_ERR_NOATTR);
}
if (MUTSIZE) {
assert(b_size == LFS_ERR_NOATTR);
}
if (MUTSIZE) {
assert(c_size == LFS_ERR_NOATTR);
}
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that file-attached attrs are written correctly
[cases.test_attrs_fattr_set]
defines.MODE = ['LFS_A_WRONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
const char *b = "One can prepared coconut pecan frosting.";
const char *c = "Three slash four cup vegetable oil.";
// try opening a file with attrs
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
// set the attrs
memcpy(a_buf, a, strlen(a));
memcpy(b_buf, b, strlen(b));
memcpy(c_buf, c, strlen(c));
a_size = strlen(a);
b_size = strlen(b);
c_size = strlen(c);
// write and close our file to write the attrs out to disk
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that we can update existing attrs
[cases.test_attrs_fattr_update]
defines.MODE = ['LFS_A_WRONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
const char *b_ = "Three slash four cup butter or margarine.";
const char *c_ = "One and two third cups granulated sugar.";
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a_),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b_),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c_),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
if (MODE == LFS_A_RDWR && MUTSIZE) {
// did we read the attrs correctly?
assert(a_size == strlen(a));
assert(memcmp(a_buf, a, strlen(a)) == 0);
assert(b_size == strlen(b));
assert(memcmp(b_buf, b, strlen(b)) == 0);
assert(c_size == strlen(c));
assert(memcmp(c_buf, c, strlen(c)) == 0);
}
// update the attrs with new values
memcpy(a_buf, a_, strlen(a_));
memcpy(b_buf, b_, strlen(b_));
memcpy(c_buf, c_, strlen(c_));
a_size = strlen(a_);
b_size = strlen(b_);
c_size = strlen(c_);
// write and close our file to write the attrs out to disk
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a_);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b_);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c_);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a_);
assert(memcmp(rbuf, a_, strlen(a_)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b_);
assert(memcmp(rbuf, b_, strlen(b_)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c_);
assert(memcmp(rbuf, c_, strlen(c_)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that we can remove attrs
[cases.test_attrs_fattr_remove]
defines.MODE = ['LFS_A_WRONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with attrs
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE)
? (lfs_ssize_t)sizeof(a_buf)
: LFS_ERR_NOATTR,
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE)
? (lfs_ssize_t)sizeof(b_buf)
: LFS_ERR_NOATTR,
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE)
? (lfs_ssize_t)sizeof(c_buf)
: LFS_ERR_NOATTR,
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
if (MODE == LFS_A_RDWR && MUTSIZE) {
// did we read the attrs correctly?
assert(a_size == strlen(a));
assert(memcmp(a_buf, a, strlen(a)) == 0);
assert(b_size == strlen(b));
assert(memcmp(b_buf, b, strlen(b)) == 0);
assert(c_size == strlen(c));
assert(memcmp(c_buf, c, strlen(c)) == 0);
}
// mark the attrs as removed
a_size = LFS_ERR_NOATTR;
b_size = LFS_ERR_NOATTR;
c_size = LFS_ERR_NOATTR;
// write and close our file to write the attrs out to disk
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, "cat", 'b') => LFS_ERR_NOATTR;
lfsr_sizeattr(&lfs, "cat", 'c') => LFS_ERR_NOATTR;
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => LFS_ERR_NOATTR;
}
lfsr_unmount(&lfs) => 0;
'''
# test that wronly attrs are not read from disk
[cases.test_attrs_fattr_wronly]
defines.MODE = ['LFS_A_WRONLY']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with new wronly attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
const char *b_ = "Three slash four cup butter or margarine.";
const char *c_ = "One and two third cups granulated sugar.";
uint8_t a_buf[256];
memcpy(a_buf, a_, strlen(a_));
lfs_ssize_t a_size = strlen(a_);
uint8_t b_buf[256];
memcpy(b_buf, b_, strlen(b_));
lfs_ssize_t b_size = strlen(b_);
uint8_t c_buf[256];
memcpy(c_buf, c_, strlen(c_));
lfs_ssize_t c_size = strlen(c_);
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a_),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b_),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c_),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", LFS_O_RDWR, &filecfg) => 0;
// open should have had no effect on our wronly attrs
assert(a_size == strlen(a_));
assert(memcmp(a_buf, a_, strlen(a_)) == 0);
assert(b_size == strlen(b_));
assert(memcmp(b_buf, b_, strlen(b_)) == 0);
assert(c_size == strlen(c_));
assert(memcmp(c_buf, c_, strlen(c_)) == 0);
// write and close our file to write the attrs out to disk
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a_);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b_);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c_);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a_);
assert(memcmp(rbuf, a_, strlen(a_)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b_);
assert(memcmp(rbuf, b_, strlen(b_)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c_);
assert(memcmp(rbuf, c_, strlen(c_)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that rdonly attrs have no effect on disk
[cases.test_attrs_fattr_rdonly]
defines.MODE = ['LFS_A_RDONLY']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with rdonly attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
const char *b_ = "Three slash four cup butter or margarine.";
const char *c_ = "One and two third cups granulated sugar.";
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a_),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b_),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c_),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", LFS_O_RDWR, &filecfg) => 0;
if (MUTSIZE) {
// did we read the attrs correctly?
assert(a_size == strlen(a));
assert(memcmp(a_buf, a, strlen(a)) == 0);
assert(b_size == strlen(b));
assert(memcmp(b_buf, b, strlen(b)) == 0);
assert(c_size == strlen(c));
assert(memcmp(c_buf, c, strlen(c)) == 0);
}
// update the attrs with new values
memcpy(a_buf, a_, strlen(a_));
memcpy(b_buf, b_, strlen(b_));
memcpy(c_buf, c_, strlen(c_));
a_size = strlen(a_);
b_size = strlen(b_);
c_size = strlen(c_);
// write and close our file, this should have no effect on attrs
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that attrs are written correctly even if there are no file changes
[cases.test_attrs_fattr_noop]
defines.MODE = ['LFS_A_WRONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
const char *b_ = "Three slash four cup butter or margarine.";
const char *c_ = "One and two third cups granulated sugar.";
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a_),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b_),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c_),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
if (MODE == LFS_A_RDWR && MUTSIZE) {
// did we read the attrs correctly?
assert(a_size == strlen(a));
assert(memcmp(a_buf, a, strlen(a)) == 0);
assert(b_size == strlen(b));
assert(memcmp(b_buf, b, strlen(b)) == 0);
assert(c_size == strlen(c));
assert(memcmp(c_buf, c, strlen(c)) == 0);
}
// update the attrs with new values
memcpy(a_buf, a_, strlen(a_));
memcpy(b_buf, b_, strlen(b_));
memcpy(c_buf, c_, strlen(c_));
a_size = strlen(a_);
b_size = strlen(b_);
c_size = strlen(c_);
// close our file without any data changes, this should still update
// our attrs!
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a_);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b_);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c_);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a_);
assert(memcmp(rbuf, a_, strlen(a_)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b_);
assert(memcmp(rbuf, b_, strlen(b_)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c_);
assert(memcmp(rbuf, c_, strlen(c_)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# test that lazy attrs aren't written _until_ there are file changes
[cases.test_attrs_fattr_lazy]
defines.MODE = ['LFS_A_WRONLY', 'LFS_A_RDWR']
defines.MUTSIZE = [false, true]
code = '''
lfs_t lfs;
lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
// create a file
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "cat", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfsr_file_write(&lfs, &file, "meow", strlen("meow")) => strlen("meow");
lfsr_file_close(&lfs, &file) => 0;
// create some attrs
const char *a = "One 18.25 ounce package chocolate cake mix.";
lfsr_setattr(&lfs, "cat", 'a', a, strlen(a),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *b = "One can prepared coconut pecan frosting.";
lfsr_setattr(&lfs, "cat", 'b', b, strlen(b),
LFS_A_CREAT | LFS_A_EXCL) => 0;
const char *c = "Three slash four cup vegetable oil.";
lfsr_setattr(&lfs, "cat", 'c', c, strlen(c),
LFS_A_CREAT | LFS_A_EXCL) => 0;
// try opening a file with attrs
const char *a_ = "Four large eggs. One cup semi-sweet chocolate chips.";
const char *b_ = "Three slash four cup butter or margarine.";
const char *c_ = "One and two third cups granulated sugar.";
uint8_t a_buf[256];
lfs_ssize_t a_size;
uint8_t b_buf[256];
lfs_ssize_t b_size;
uint8_t c_buf[256];
lfs_ssize_t c_size;
struct lfs_attr attrs[] = {
{
.type = 'a',
.flags = MODE | LFS_A_LAZY,
.buffer = a_buf,
.buffer_size = (MUTSIZE) ? sizeof(a_buf) : strlen(a_),
.size = (MUTSIZE) ? &a_size : NULL,
},
{
.type = 'b',
.flags = MODE | LFS_A_LAZY,
.buffer = b_buf,
.buffer_size = (MUTSIZE) ? sizeof(b_buf) : strlen(b_),
.size = (MUTSIZE) ? &b_size : NULL,
},
{
.type = 'c',
.flags = MODE | LFS_A_LAZY,
.buffer = c_buf,
.buffer_size = (MUTSIZE) ? sizeof(c_buf) : strlen(c_),
.size = (MUTSIZE) ? &c_size : NULL,
}
};
struct lfs_file_config filecfg = {
.attrs = attrs,
.attr_count = 3,
};
lfsr_file_opencfg(&lfs, &file, "cat", MODE, &filecfg) => 0;
if (MODE == LFS_A_RDWR && MUTSIZE) {
// did we read the attrs correctly?
assert(a_size == strlen(a));
assert(memcmp(a_buf, a, strlen(a)) == 0);
assert(b_size == strlen(b));
assert(memcmp(b_buf, b, strlen(b)) == 0);
assert(c_size == strlen(c));
assert(memcmp(c_buf, c, strlen(c)) == 0);
}
// update the attrs with new values
memcpy(a_buf, a_, strlen(a_));
memcpy(b_buf, b_, strlen(b_));
memcpy(c_buf, c_, strlen(c_));
a_size = strlen(a_);
b_size = strlen(b_);
c_size = strlen(c_);
// sync our file without any data changes, this should have no
// effect because our attrs are lazy
lfsr_file_sync(&lfs, &file) => 0;
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a);
assert(memcmp(rbuf, a, strlen(a)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b);
assert(memcmp(rbuf, b, strlen(b)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c);
assert(memcmp(rbuf, c, strlen(c)) == 0);
// now write some data and close our file, this should update
// the attrs now
lfsr_file_write(&lfs, &file, "miao", strlen("miao")) => strlen("miao");
lfsr_file_close(&lfs, &file) => 0;
for (int remount = 0; remount < 2; remount++) {
// remount?
if (remount) {
lfsr_unmount(&lfs) => 0;
lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0;
}
// try getting the attr sizes
lfsr_sizeattr(&lfs, "cat", 'a') => strlen(a_);
lfsr_sizeattr(&lfs, "cat", 'b') => strlen(b_);
lfsr_sizeattr(&lfs, "cat", 'c') => strlen(c_);
// try reading the attrs
uint8_t rbuf[256];
lfsr_getattr(&lfs, "cat", 'a', rbuf, sizeof(rbuf)) => strlen(a_);
assert(memcmp(rbuf, a_, strlen(a_)) == 0);
lfsr_getattr(&lfs, "cat", 'b', rbuf, sizeof(rbuf)) => strlen(b_);
assert(memcmp(rbuf, b_, strlen(b_)) == 0);
lfsr_getattr(&lfs, "cat", 'c', rbuf, sizeof(rbuf)) => strlen(c_);
assert(memcmp(rbuf, c_, strlen(c_)) == 0);
}
lfsr_unmount(&lfs) => 0;
'''
# TODO
#[cases.test_attrs_fattr_all]
#[cases.test_attrs_fattr_many]
#[cases.test_attrs_fattr_many_many]
#[cases.test_attrs_fattr_fuzz]
#[cases.test_attrs_fattr_fuzz_fuzz]
#[cases.test_attrs_fattr_broadcast]
#[cases.test_attrs_fattr_remove_broadcast]
#[cases.test_attrs_fattr_setattr_broadcast]
#[cases.test_attrs_fattr_removeattr_broadcast]
#[cases.test_attrs_fattr_wronly_broadcast]
#[cases.test_attrs_fattr_rdonly_broadcast]
#[cases.test_attrs_fattr_desync_broadcast]
#[cases.test_attrs_fattr_resync]
#[cases.test_attrs_fattr_zombie_resync] TODO do we test file zombie resync?
#[cases.test_attrs_fattr_pl] ?
#[cases.test_attrs_get_set]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "hello") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello");
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# uint8_t buffer[1024];
# memset(buffer, 0, sizeof(buffer));
# lfs_setattr(&lfs, "hello", 'A', "aaaa", 4) => 0;
# lfs_setattr(&lfs, "hello", 'B', "bbbbbb", 6) => 0;
# lfs_setattr(&lfs, "hello", 'C', "ccccc", 5) => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "bbbbbb", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "hello", 'B', "", 0) => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 0;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_removeattr(&lfs, "hello", 'B') => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => LFS_ERR_NOATTR;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "hello", 'B', "dddddd", 6) => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 6;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "dddddd", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "hello", 'B', "eee", 3) => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 3;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "eee\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "hello", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
# lfs_setattr(&lfs, "hello", 'B', "fffffffff", 9) => 0;
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 6) => 9;
# lfs_getattr(&lfs, "hello", 'C', buffer+10, 5) => 5;
#
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# memset(buffer, 0, sizeof(buffer));
# lfs_getattr(&lfs, "hello", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "hello", 'B', buffer+4, 9) => 9;
# lfs_getattr(&lfs, "hello", 'C', buffer+13, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "fffffffff", 9) => 0;
# memcmp(buffer+13, "ccccc", 5) => 0;
#
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello");
# memcmp(buffer, "hello", strlen("hello")) => 0;
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_attrs_get_set_root]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "hello") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello");
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# uint8_t buffer[1024];
# memset(buffer, 0, sizeof(buffer));
# lfs_setattr(&lfs, "/", 'A', "aaaa", 4) => 0;
# lfs_setattr(&lfs, "/", 'B', "bbbbbb", 6) => 0;
# lfs_setattr(&lfs, "/", 'C', "ccccc", 5) => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 6;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "bbbbbb", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "/", 'B', "", 0) => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 0;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_removeattr(&lfs, "/", 'B') => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => LFS_ERR_NOATTR;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "/", 'B', "dddddd", 6) => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 6;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "dddddd", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "/", 'B', "eee", 3) => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 3;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "eee\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# lfs_setattr(&lfs, "/", 'A', buffer, LFS_ATTR_MAX+1) => LFS_ERR_NOSPC;
# lfs_setattr(&lfs, "/", 'B', "fffffffff", 9) => 0;
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 6) => 9;
# lfs_getattr(&lfs, "/", 'C', buffer+10, 5) => 5;
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# memset(buffer, 0, sizeof(buffer));
# lfs_getattr(&lfs, "/", 'A', buffer, 4) => 4;
# lfs_getattr(&lfs, "/", 'B', buffer+4, 9) => 9;
# lfs_getattr(&lfs, "/", 'C', buffer+13, 5) => 5;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "fffffffff", 9) => 0;
# memcmp(buffer+13, "ccccc", 5) => 0;
#
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello");
# memcmp(buffer, "hello", strlen("hello")) => 0;
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_attrs_get_set_file]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "hello") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello");
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# uint8_t buffer[1024];
# memset(buffer, 0, sizeof(buffer));
# struct lfs_attr attrs1[] = {
# {'A', buffer, 4},
# {'B', buffer+4, 6},
# {'C', buffer+10, 5},
# };
# struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
#
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
# memcpy(buffer, "aaaa", 4);
# memcpy(buffer+4, "bbbbbb", 6);
# memcpy(buffer+10, "ccccc", 5);
# lfs_file_close(&lfs, &file) => 0;
# memset(buffer, 0, 15);
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "bbbbbb", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# attrs1[1].size = 0;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memset(buffer, 0, 15);
# attrs1[1].size = 6;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# attrs1[1].size = 6;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
# memcpy(buffer+4, "dddddd", 6);
# lfs_file_close(&lfs, &file) => 0;
# memset(buffer, 0, 15);
# attrs1[1].size = 6;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "dddddd", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# attrs1[1].size = 3;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
# memcpy(buffer+4, "eee", 3);
# lfs_file_close(&lfs, &file) => 0;
# memset(buffer, 0, 15);
# attrs1[1].size = 6;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "eee\0\0\0", 6) => 0;
# memcmp(buffer+10, "ccccc", 5) => 0;
#
# attrs1[0].size = LFS_ATTR_MAX+1;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1)
# => LFS_ERR_NOSPC;
#
# struct lfs_attr attrs2[] = {
# {'A', buffer, 4},
# {'B', buffer+4, 9},
# {'C', buffer+13, 5},
# };
# struct lfs_file_config cfg2 = {.attrs=attrs2, .attr_count=3};
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDWR, &cfg2) => 0;
# memcpy(buffer+4, "fffffffff", 9);
# lfs_file_close(&lfs, &file) => 0;
# attrs1[0].size = 4;
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg1) => 0;
# lfs_file_close(&lfs, &file) => 0;
#
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# memset(buffer, 0, sizeof(buffer));
# struct lfs_attr attrs3[] = {
# {'A', buffer, 4},
# {'B', buffer+4, 9},
# {'C', buffer+13, 5},
# };
# struct lfs_file_config cfg3 = {.attrs=attrs3, .attr_count=3};
#
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_RDONLY, &cfg3) => 0;
# lfs_file_close(&lfs, &file) => 0;
# memcmp(buffer, "aaaa", 4) => 0;
# memcmp(buffer+4, "fffffffff", 9) => 0;
# memcmp(buffer+13, "ccccc", 5) => 0;
#
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_RDONLY) => 0;
# lfs_file_read(&lfs, &file, buffer, sizeof(buffer)) => strlen("hello");
# memcmp(buffer, "hello", strlen("hello")) => 0;
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#'''
#
#[cases.test_attrs_deferred_file]
#code = '''
# lfs_t lfs;
# lfs_format(&lfs, cfg) => 0;
# lfs_mount(&lfs, cfg) => 0;
# lfs_mkdir(&lfs, "hello") => 0;
# lfs_file_t file;
# lfs_file_open(&lfs, &file, "hello/hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
# lfs_file_write(&lfs, &file, "hello", strlen("hello")) => strlen("hello");
# lfs_file_close(&lfs, &file);
# lfs_unmount(&lfs) => 0;
#
# lfs_mount(&lfs, cfg) => 0;
# lfs_setattr(&lfs, "hello/hello", 'B', "fffffffff", 9) => 0;
# lfs_setattr(&lfs, "hello/hello", 'C', "ccccc", 5) => 0;
#
# uint8_t buffer[1024];
# memset(buffer, 0, sizeof(buffer));
# struct lfs_attr attrs1[] = {
# {'B', "gggg", 4},
# {'C', "", 0},
# {'D', "hhhh", 4},
# };
# struct lfs_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
#
# lfs_file_opencfg(&lfs, &file, "hello/hello", LFS_O_WRONLY, &cfg1) => 0;
#
# lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 9;
# lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 5;
# lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => LFS_ERR_NOATTR;
# memcmp(buffer, "fffffffff", 9) => 0;
# memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0;
# memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0;
#
# lfs_file_sync(&lfs, &file) => 0;
# lfs_getattr(&lfs, "hello/hello", 'B', buffer, 9) => 4;
# lfs_getattr(&lfs, "hello/hello", 'C', buffer+9, 9) => 0;
# lfs_getattr(&lfs, "hello/hello", 'D', buffer+18, 9) => 4;
# memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0;
# memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0;
# memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0;
#
# lfs_file_close(&lfs, &file) => 0;
# lfs_unmount(&lfs) => 0;
#'''