diff --git a/lfs.c b/lfs.c index 3fb8e31b..d79aaf52 100644 --- a/lfs.c +++ b/lfs.c @@ -1419,6 +1419,7 @@ enum lfsr_tag { LFSR_TAG_SHRUBCOMMIT = 0x0801, LFSR_TAG_SHRUBTRUNK = 0x0802, LFSR_TAG_MOVE = 0x0803, + LFSR_TAG_UATTRS = 0x0804, // some in-device only tag modifiers LFSR_TAG_RM = 0x8000, @@ -2394,6 +2395,11 @@ typedef struct lfsr_data_name { #define LFSR_ATTR_ATTRS(_tag, _weight, _attrs, _attr_count) \ LFSR_ATTR_(_tag, _weight, (const lfsr_attr_t*){_attrs}, _attr_count) +// chain a list of user attributes, these require a bit of last-minute +// reencoding during commits +#define LFSR_ATTR_UATTRS(_tag, _weight, _attrs, _attr_count) \ + LFSR_ATTR_(_tag, _weight, (const struct lfs_attr*){_attrs}, _attr_count) + // a move of all attrs from an mdir entry #define LFSR_ATTR_MOVE(_tag, _weight, _mdir) \ LFSR_ATTR_(_tag, _weight, (const lfsr_mdir_t*){_mdir}, 0) @@ -2419,6 +2425,41 @@ typedef struct lfsr_shrubcommit lfsr_shrubcommit_t; LFSR_ATTR_(_tag, _weight, (const lfsr_shrub_t*){_shrub}, 0) +// operations on custom attribute lists +// +// a slightly different struct because it's user facing + +static inline lfs_ssize_t lfsr_uattr_size(const struct lfs_attr *attr) { + // we default to the buffer_size if a mutable size is not provided + if (attr->size) { + return *attr->size; + } else { + return attr->buffer_size; + } +} + +static inline bool lfsr_uattr_isnoattr(const struct lfs_attr *attr) { + return lfsr_uattr_size(attr) == LFS_ERR_NOATTR; +} + +static lfs_scmp_t lfsr_uattr_cmp(lfs_t *lfs, const struct lfs_attr *attr, + const lfsr_data_t *data) { + // note data=NULL => NOATTR + if (!data) { + return (lfsr_uattr_isnoattr(attr)) ? LFS_CMP_EQ : LFS_CMP_GT; + } else { + if (lfsr_uattr_isnoattr(attr)) { + return LFS_CMP_LT; + } else { + return lfsr_data_cmp(lfs, *data, + attr->buffer, + lfsr_uattr_size(attr)); + } + } +} + + + //struct lfsr_attr_from { // const lfsr_rbyd_t *rbyd; // const struct lfsr_attr *attrs; @@ -7117,6 +7158,11 @@ static inline bool lfsr_o_iszombie(uint32_t flags) { return flags & LFS_O_ZOMBIE; } +// custom attr flags +static inline bool lfsr_a_islazy(uint32_t flags) { + return flags & LFS_A_LAZY; +} + // traversal flags static inline bool lfsr_t_ismtreeonly(uint32_t flags) { return flags & LFS_T_MTREEONLY; @@ -8135,6 +8181,57 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir, } } + // custom attributes need to be reencoded into our tag format + } else if (lfsr_tag_key(attrs[i].tag) == LFSR_TAG_UATTRS) { + const struct lfs_attr *attrs_ = attrs[i].cat; + lfs_size_t attr_count_ = attrs[i].count; + + for (lfs_size_t j = 0; j < attr_count_; j++) { + // skip readonly attrs and lazy attrs + if (lfsr_o_isrdonly(attrs_[j].flags)) { + continue; + } + + // first lets check if the attr changed, we don't want + // to append attrs unless we have to + lfsr_data_t data; + int err = lfsr_mdir_lookup(lfs, mdir, + LFSR_TAG_ATTR(attrs_[j].type), + &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + // does disk match our attr? + lfs_scmp_t cmp = lfsr_uattr_cmp(lfs, &attrs_[j], + (err != LFS_ERR_NOENT) ? &data : NULL); + if (cmp < 0) { + return cmp; + } + + if (cmp == LFS_CMP_EQ) { + continue; + } + + // append the custom attr + err = lfsr_rbyd_appendattr(lfs, &mdir->rbyd, + rid - lfs_smax(start_rid, 0), + // removing or updating? + (lfsr_uattr_isnoattr(&attrs_[j])) + ? LFSR_ATTR( + LFSR_TAG_RM + | LFSR_TAG_ATTR(attrs_[j].type), 0, + LFSR_DATA_NULL()) + : LFSR_ATTR( + LFSR_TAG_ATTR(attrs_[j].type), 0, + LFSR_DATA_BUF( + attrs_[j].buffer, + lfsr_uattr_size(&attrs_[j])))); + if (err) { + return err; + } + } + // write out normal tags normally } else { LFS_ASSERT(!lfsr_tag_isinternal(attrs[i].tag)); @@ -11149,10 +11246,39 @@ int lfsr_setattr(lfs_t *lfs, const char *path, uint8_t type, // commit our attr lfs_alloc_ckpoint(lfs); - return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( + err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( LFSR_ATTR( LFSR_TAG_ATTR(type), 0, LFSR_DATA_BUF(buffer, size)))); + if (err) { + return err; + } + + // update any opened files tracking custom attrs + for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) { + if (!(lfsr_o_type(o->flags) == LFS_TYPE_REG + && o->mdir.mid == mdir.mid + && !lfsr_o_isdesync(o->flags))) { + continue; + } + + lfsr_file_t *file = (lfsr_file_t*)o; + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + if (!(file->cfg->attrs[i].type == type + && !lfsr_o_iswronly(file->cfg->attrs[i].flags))) { + continue; + } + + lfs_size_t d = lfs_min(size, file->cfg->attrs[i].buffer_size); + memcpy(file->cfg->attrs[i].buffer, buffer, d); + if (file->cfg->attrs[i].size) { + *file->cfg->attrs[i].size = d; + } + } + } + + + return 0; } int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) { @@ -11172,10 +11298,36 @@ int lfsr_removeattr(lfs_t *lfs, const char *path, uint8_t type) { // commit our removal lfs_alloc_ckpoint(lfs); - return lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( + err = lfsr_mdir_commit(lfs, &mdir, LFSR_ATTRS( LFSR_ATTR( LFSR_TAG_RM | LFSR_TAG_ATTR(type), 0, LFSR_DATA_NULL()))); + if (err) { + return err; + } + + // update any opened files tracking custom attrs + for (lfsr_omdir_t *o = lfs->omdirs; o; o = o->next) { + if (!(lfsr_o_type(o->flags) == LFS_TYPE_REG + && o->mdir.mid == mdir.mid + && !lfsr_o_isdesync(o->flags))) { + continue; + } + + lfsr_file_t *file = (lfsr_file_t*)o; + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + if (!(file->cfg->attrs[i].type == type + && !lfsr_o_iswronly(file->cfg->attrs[i].flags))) { + continue; + } + + if (file->cfg->attrs[i].size) { + *file->cfg->attrs[i].size = LFS_ERR_NOATTR; + } + } + } + + return 0; } @@ -11292,6 +11444,43 @@ static int lfsr_file_fetch(lfs_t *lfs, lfsr_file_t *file) { file->buffer.size = size; } + // try to fetch any custom attributes + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + // skip writeonly attrs + if (lfsr_o_iswronly(file->cfg->attrs[i].flags)) { + continue; + } + + // lookup the attr + lfsr_data_t data; + int err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, + LFSR_TAG_ATTR(file->cfg->attrs[i].type), + &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + // read the attr, if it exists + if (err == LFS_ERR_NOENT + // awkward case here if buffer_size is LFS_ERR_NOATTR + || file->cfg->attrs[i].buffer_size == LFS_ERR_NOATTR) { + if (file->cfg->attrs[i].size) { + *file->cfg->attrs[i].size = LFS_ERR_NOATTR; + } + } else { + lfs_ssize_t d = lfsr_data_read(lfs, &data, + file->cfg->attrs[i].buffer, + file->cfg->attrs[i].buffer_size); + if (d < 0) { + return d; + } + + if (file->cfg->attrs[i].size) { + *file->cfg->attrs[i].size = d; + } + } + } + return 0; } @@ -11324,6 +11513,13 @@ int lfsr_file_opencfg(lfs_t *lfs, lfsr_file_t *file, LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_iscreat(flags)); LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_isexcl(flags)); LFS_ASSERT(!lfsr_o_isrdonly(flags) || !lfsr_o_istrunc(flags)); + for (lfs_size_t i = 0; i < cfg->attr_count; i++) { + // these flags require a writable attr + LFS_ASSERT(!lfsr_o_isrdonly(cfg->attrs[i].flags) + || !lfsr_o_iscreat(cfg->attrs[i].flags)); + LFS_ASSERT(!lfsr_o_isrdonly(cfg->attrs[i].flags) + || !lfsr_o_isexcl(cfg->attrs[i].flags)); + } if (!lfsr_o_isrdonly(flags)) { // prepare our filesystem for writing @@ -12810,30 +13006,34 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { LFS_ASSERT(!lfsr_o_isorphan(file->o.o.flags) || lfsr_o_isunsync(file->o.o.flags)); - // don't write to disk if already in-sync - if (lfsr_o_isunsync(file->o.o.flags)) { - // commit any changes to our file's metadata - lfsr_attr_t attrs[2]; - lfs_size_t attr_count = 0; - lfsr_data_t name_data; - uint8_t buf[LFSR_BTREE_DSIZE]; + // build a commit of any pending file metadata + lfsr_attr_t attrs[3]; + lfs_size_t attr_count = 0; + lfsr_data_t name_data; + uint8_t buf[LFSR_BTREE_DSIZE]; - // not created yet? need to convert orphan to normal file - if (lfsr_o_isorphan(file->o.o.flags)) { - err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, LFSR_TAG_ORPHAN, - &name_data); - if (err) { - // we must have an orphan at this point - LFS_ASSERT(err != LFS_ERR_NOENT); - goto failed; - } - - attrs[attr_count++] = LFSR_ATTR_CAT_( - LFSR_TAG_SUB | LFSR_TAG_REG, 0, - &name_data, 1); + // not created yet? need to convert orphan to normal file + if (lfsr_o_isorphan(file->o.o.flags)) { + err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, LFSR_TAG_ORPHAN, + &name_data); + if (err) { + // orphan flag but no orphan tag? + LFS_ASSERT(err != LFS_ERR_NOENT); + goto failed; } - // commit the file state + attrs[attr_count++] = LFSR_ATTR_CAT_( + LFSR_TAG_SUB | LFSR_TAG_REG, 0, + &name_data, 1); + } + + // pending file changes? + if (lfsr_o_isunsync(file->o.o.flags)) { + // make sure data is on-disk before committing metadata + err = lfsr_bd_sync(lfs); + if (err) { + goto failed; + } // null? no attr? if (lfsr_o_isunflush(file->o.o.flags) && file->buffer.size == 0) { @@ -12858,13 +13058,54 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { } else { LFS_UNREACHABLE(); } + } - // make sure data is on-disk before committing metadata - err = lfsr_bd_sync(lfs); - if (err) { - goto failed; + // pending custom attributes? + // + // this gets real messy, since users can change custom attributes + // whenever they want without informing littlefs, the best we can do + // is read from disk to manually check if any attributes changed + bool uattrs = lfsr_o_isunsync(file->o.o.flags); + if (!uattrs) { + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + // skip readonly attrs and lazy attrs + if (lfsr_o_isrdonly(file->cfg->attrs[i].flags) + || lfsr_a_islazy(file->cfg->attrs[i].flags)) { + continue; + } + + // lookup the attr + lfsr_data_t data; + err = lfsr_mdir_lookup(lfs, &file->o.o.mdir, + LFSR_TAG_ATTR(file->cfg->attrs[i].type), + &data); + if (err && err != LFS_ERR_NOENT) { + goto failed; + } + + // does disk match our attr? + lfs_scmp_t cmp = lfsr_uattr_cmp(lfs, &file->cfg->attrs[i], + (err != LFS_ERR_NOENT) ? &data : NULL); + if (cmp < 0) { + err = cmp; + goto failed; + } + + if (cmp != LFS_CMP_EQ) { + uattrs = true; + break; + } } + } + if (uattrs) { + // need to append custom attributes + attrs[attr_count++] = LFSR_ATTR_UATTRS( + LFSR_TAG_UATTRS, 0, + file->cfg->attrs, file->cfg->attr_count); + } + // pending metadata? looks like we need to write to disk + if (attr_count > 0) { // checkpoint the allocator lfs_alloc_ckpoint(lfs); @@ -12907,6 +13148,38 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) { file->buffer.buffer, file->buffer.size); file_->buffer.size = file->buffer.size; + + // update any custom attrs + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + if (lfsr_o_isrdonly(file->cfg->attrs[i].flags)) { + continue; + } + + for (lfs_size_t j = 0; j < file_->cfg->attr_count; j++) { + if (!(file_->cfg->attrs[j].type + == file->cfg->attrs[i].type + && !lfsr_o_iswronly( + file_->cfg->attrs[j].flags))) { + continue; + } + + if (lfsr_uattr_isnoattr(&file->cfg->attrs[i])) { + if (file_->cfg->attrs[j].size) { + *file_->cfg->attrs[j].size = LFS_ERR_NOATTR; + } + } else { + lfs_size_t d = lfs_min( + lfsr_uattr_size(&file->cfg->attrs[i]), + file_->cfg->attrs[j].buffer_size); + memcpy(file_->cfg->attrs[j].buffer, + file->cfg->attrs[i].buffer, + d); + if (file_->cfg->attrs[j].size) { + *file_->cfg->attrs[j].size = d; + } + } + } + } } // clobber entangled traversals diff --git a/lfs.h b/lfs.h index 9b43feaa..7f3dbe97 100644 --- a/lfs.h +++ b/lfs.h @@ -152,8 +152,12 @@ enum lfs_type { #define LFS_SEEK_END 2 // Seek relative to the end of the file // Custom attribute flags +#define LFS_A_RDONLY 0 // Open an attr as read only +#define LFS_A_WRONLY 1 // Open an attr as write only +#define LFS_A_RDWR 2 // Open an attr as read and write #define LFS_A_CREAT 0x04 // Create an attr if it does not exist #define LFS_A_EXCL 0x08 // Fail if an attr already exists +#define LFS_A_LAZY 0x10 // Only write attr if file changed // Filesystem format flags #define LFS_F_RDWR 0 // Format the filesystem as read and write @@ -481,6 +485,33 @@ struct lfs_tinfo { // lfs_size_t size; //}; +// 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; +}; + // Optional configuration provided during lfs_file_opencfg struct lfs_file_config { // Optional statically allocated file buffer. Must be buffer_size. @@ -507,6 +538,15 @@ struct lfs_file_config { // // // Number of custom attributes in the list // lfs_size_t attr_count; + + // Optional list of custom attributes attached to the file. If readable, + // these attributes will be kept up to date with the attributes on-disk. + // If writeable, these attributes will be written to disk atomically on + // every file sync or close. + struct lfs_attr *attrs; + + // Number of custom attributes in the list + lfs_size_t attr_count; }; diff --git a/tests/test_attrs.toml b/tests/test_attrs.toml index cca7ebb4..2dbdfa5d 100644 --- a/tests/test_attrs.toml +++ b/tests/test_attrs.toml @@ -2,6 +2,8 @@ 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 @@ -68,13 +70,14 @@ code = ''' lfsr_unmount(&lfs) => 0; ''' -# test some simple attr operations +# 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; @@ -124,30 +127,17 @@ code = ''' // mark rbuf so we can detect overflow uint8_t rbuf[256]; - rbuf[1] = '!'; + rbuf[BUFSIZE] = '!'; // try reading truncated attrs - lfsr_getattr(&lfs, path, 'a', rbuf, 1) => 1; - assert(memcmp(rbuf, a, 1) == 0); - assert(rbuf[1] == '!'); - lfsr_getattr(&lfs, path, 'b', rbuf, 1) => 1; - assert(memcmp(rbuf, b, 1) == 0); - assert(rbuf[1] == '!'); - lfsr_getattr(&lfs, path, 'c', rbuf, 1) => 1; - assert(memcmp(rbuf, c, 1) == 0); - assert(rbuf[1] == '!'); - - // mark rbuf so we can detect overflow - rbuf[4] = '!'; - // try reading truncated attrs - lfsr_getattr(&lfs, path, 'a', rbuf, 4) => 4; - assert(memcmp(rbuf, a, 4) == 0); - assert(rbuf[4] == '!'); - lfsr_getattr(&lfs, path, 'b', rbuf, 4) => 4; - assert(memcmp(rbuf, b, 4) == 0); - assert(rbuf[4] == '!'); - lfsr_getattr(&lfs, path, 'c', rbuf, 4) => 4; - assert(memcmp(rbuf, c, 4) == 0); - assert(rbuf[4] == '!'); + 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); @@ -1515,21 +1505,1027 @@ code = ''' ''' +## 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] ?