diff --git a/lfs.c b/lfs.c index 536bad6..f582902 100644 --- a/lfs.c +++ b/lfs.c @@ -821,8 +821,12 @@ static int lfs_dir_traverse(lfs_t *lfs, return err; } } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { + const struct lfs_attr *a = buffer; for (unsigned i = 0; i < lfs_tag_size(tag); i++) { - const struct lfs_attr *a = buffer; + if (a[i].size > lfs->attr_max) { + return LFS_ERR_NOSPC; + } + int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); if (err) { @@ -1180,6 +1184,11 @@ static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, dir->tail[0] = lfs->root[0]; dir->tail[1] = lfs->root[1]; + // NULL path == root + if (!name) { + return tag; + } + while (true) { nextname: // skip slashes @@ -2020,6 +2029,7 @@ compact: #ifndef LFS_READONLY static int lfs_rawmkdir(lfs_t *lfs, const char *path) { // deorphan if we haven't yet, needed at most once after poweron + LFS_ASSERT(path); int err = lfs_fs_forceconsistency(lfs); if (err) { return err; @@ -2457,14 +2467,14 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, const struct lfs_file_config *cfg) { #ifndef LFS_READONLY // deorphan if we haven't yet, needed at most once after poweron - if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) { + if (flags & LFS_O_WRONLY) { int err = lfs_fs_forceconsistency(lfs); if (err) { return err; } } #else - LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY); + LFS_ASSERT(flags & LFS_O_RDONLY); #endif // setup simple file details @@ -2475,7 +2485,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, file->off = 0; file->cache.buffer = NULL; - // allocate entry for file if it doesn't exist + // find path lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { err = tag; @@ -2483,19 +2493,22 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, } // get id, add to list of mdirs to catch update changes - file->type = LFS_TYPE_REG; - lfs_mlist_append(lfs, (struct lfs_mlist *)file); + if (!(flags & LFS_O_SNAPSHOT)) { + file->type = LFS_TYPE_REG; + lfs_mlist_append(lfs, (struct lfs_mlist *)file); + } -#ifdef LFS_READONLY - if (tag == LFS_ERR_NOENT) { - err = LFS_ERR_NOENT; +#ifndef LFS_READONLY + if ((flags & LFS_O_CREAT) && (flags & LFS_O_SNAPSHOT) && + (tag == LFS_ERR_NOENT || lfs_tag_type3(tag) != LFS_TYPE_REG)) { + // special case for "temporary" files + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + } else if ((flags & LFS_O_EXCL) && tag != LFS_ERR_NOENT) { + err = LFS_ERR_EXIST; goto cleanup; -#else - if (tag == LFS_ERR_NOENT) { - if (!(flags & LFS_O_CREAT)) { - err = LFS_ERR_NOENT; - goto cleanup; - } + } else if ((flags & LFS_O_CREAT) && tag == LFS_ERR_NOENT) { + // allocate entry for file if it doesn't exist + LFS_ASSERT(path); // check that name fits lfs_size_t nlen = strlen(path); @@ -2512,60 +2525,53 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, file->cfg->attr_count), file->cfg->attrs})); if (err) { - err = LFS_ERR_NAMETOOLONG; goto cleanup; } tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); - } else if (flags & LFS_O_EXCL) { - err = LFS_ERR_EXIST; + } else /**/ +#endif /**/ + /*********/ + /**/ if (tag == LFS_ERR_NOENT) { + err = LFS_ERR_NOENT; goto cleanup; -#endif } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { err = LFS_ERR_ISDIR; goto cleanup; -#ifndef LFS_READONLY - } else if (flags & LFS_O_TRUNC) { - // truncate if requested - // always mark dirty in case we have custom attributes - tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); - file->flags |= LFS_F_DIRTY; -#endif } else { - // try to load what's on disk, if it's inlined we'll fix it later - tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); - if (tag < 0) { - err = tag; - goto cleanup; - } - lfs_ctz_fromle32(&file->ctz); - } - - // fetch attrs - for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { - // if opened for read / read-write operations - if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { - lfs_stag_t res = lfs_dir_get(lfs, &file->m, - LFS_MKTAG(0x7ff, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, - file->id, file->cfg->attrs[i].size), - file->cfg->attrs[i].buffer); - if (res < 0 && res != LFS_ERR_NOENT) { - err = res; - goto cleanup; - } - } - #ifndef LFS_READONLY - // if opened for write / read-write operations - if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) { - if (file->cfg->attrs[i].size > lfs->attr_max) { - err = LFS_ERR_NOSPC; + if (flags & LFS_O_TRUNC) { + // truncate if requested + // always mark dirty in case we have custom attributes + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + file->flags |= LFS_F_DIRTY; + } else /**/ +#endif /*********/ + /**/ { + // try to load what's on disk, if it's inlined we'll fix it later + tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); + if (tag < 0) { + err = tag; goto cleanup; } + lfs_ctz_fromle32(&file->ctz); + } + + // fetch attrs if opened for read / read-write operations + if (flags & LFS_O_RDONLY) { + for (lfs_size_t i = 0; i < file->cfg->attr_count; i++) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, + file->id, file->cfg->attrs[i].size), + file->cfg->attrs[i].buffer); + if (res < 0 && res != LFS_ERR_NOENT) { + err = res; + goto cleanup; + } + } } -#endif } // allocate buffer if needed @@ -2610,7 +2616,7 @@ static int lfs_file_rawopencfg(lfs_t *lfs, lfs_file_t *file, cleanup: // clean up lingering resources #ifndef LFS_READONLY - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; #endif lfs_file_rawclose(lfs, file); return err; @@ -2624,10 +2630,9 @@ static int lfs_file_rawopen(lfs_t *lfs, lfs_file_t *file, } static int lfs_file_rawclose(lfs_t *lfs, lfs_file_t *file) { -#ifndef LFS_READONLY - int err = lfs_file_rawsync(lfs, file); -#else int err = 0; +#ifndef LFS_READONLY + err = lfs_file_rawsync(lfs, file); #endif // remove from list of mdirs @@ -2809,17 +2814,22 @@ relocate: #ifndef LFS_READONLY static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { - if (file->flags & LFS_F_ERRED) { + if (file->flags & LFS_F_ZOMBIE) { // it's not safe to do anything if our file errored return 0; } int err = lfs_file_flush(lfs, file); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } + if (file->flags & LFS_O_SNAPSHOT) { + // we do flush snapshot files, but we don't commit, so stop here + return 0; + } + if ((file->flags & LFS_F_DIRTY) && !lfs_pair_isnull(file->m.pair)) { // update dir entry @@ -2848,7 +2858,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, file->cfg->attr_count), file->cfg->attrs})); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } @@ -2860,7 +2870,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { f->id == file->id && // only readable handles because wronly files // may reference attributes in ROM - (f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { + (f->flags & LFS_O_RDONLY)) { // sync disk structure f->ctz = file->ctz; // copying the cache is required for inline files @@ -2892,7 +2902,7 @@ static int lfs_file_rawsync(lfs_t *lfs, lfs_file_t *file) { static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size) { - LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY); + LFS_ASSERT(file->flags & LFS_O_RDONLY); uint8_t *data = buffer; lfs_size_t nsize = size; @@ -2966,7 +2976,7 @@ static lfs_ssize_t lfs_file_rawread(lfs_t *lfs, lfs_file_t *file, #ifndef LFS_READONLY static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size) { - LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + LFS_ASSERT(file->flags & LFS_O_WRONLY); const uint8_t *data = buffer; lfs_size_t nsize = size; @@ -3008,7 +3018,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, // inline file doesn't fit anymore int err = lfs_file_outline(lfs, file); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } } @@ -3024,7 +3034,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, file->ctz.head, file->ctz.size, file->pos-1, &file->block, &file->off); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } @@ -3038,7 +3048,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, file->block, file->pos, &file->block, &file->off); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } } else { @@ -3058,7 +3068,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, if (err == LFS_ERR_CORRUPT) { goto relocate; } - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } @@ -3066,7 +3076,7 @@ static lfs_ssize_t lfs_file_rawwrite(lfs_t *lfs, lfs_file_t *file, relocate: err = lfs_file_relocate(lfs, file); if (err) { - file->flags |= LFS_F_ERRED; + file->flags |= LFS_F_ZOMBIE; return err; } } @@ -3079,7 +3089,7 @@ relocate: lfs_alloc_ack(lfs); } - file->flags &= ~LFS_F_ERRED; + file->flags &= ~LFS_F_ZOMBIE; return size; } #endif @@ -3116,7 +3126,7 @@ static lfs_soff_t lfs_file_rawseek(lfs_t *lfs, lfs_file_t *file, #ifndef LFS_READONLY static int lfs_file_rawtruncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { - LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY); + LFS_ASSERT(file->flags & LFS_O_WRONLY); if (size > LFS_FILE_MAX) { return LFS_ERR_INVAL; @@ -3477,7 +3487,7 @@ static int lfs_commitattr(lfs_t *lfs, const char *path, f->id == id && // only readable handles because wronly files // may reference attributes in ROM - (f->flags & LFS_O_RDONLY) == LFS_O_RDONLY) { + (f->flags & LFS_O_RDONLY)) { // sync attrs for (lfs_size_t i = 0; i < f->cfg->attr_count; i++) { if (f->cfg->attrs[i].type == type) { @@ -3893,7 +3903,7 @@ int lfs_fs_rawtraverse(lfs_t *lfs, if (err) { return err; } - } else if (includeorphans && + } else if (includeorphans && lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { for (int i = 0; i < 2; i++) { err = cb(data, (&ctz.head)[i]); @@ -5148,7 +5158,8 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { return err; } LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawclose(lfs, file); @@ -5164,7 +5175,8 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { return err; } LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawsync(lfs, file); @@ -5182,7 +5194,8 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, } LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_ssize_t res = lfs_file_rawread(lfs, file, buffer, size); @@ -5200,7 +5213,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, } LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", (void*)lfs, (void*)file, buffer, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_ssize_t res = lfs_file_rawwrite(lfs, file, buffer, size); @@ -5218,7 +5232,8 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, } LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", (void*)lfs, (void*)file, off, whence); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawseek(lfs, file, off, whence); @@ -5235,7 +5250,8 @@ int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { } LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", (void*)lfs, (void*)file, size); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); err = lfs_file_rawtruncate(lfs, file, size); @@ -5251,7 +5267,8 @@ lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { return err; } LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawtell(lfs, file); @@ -5280,7 +5297,8 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { return err; } LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); - LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); + LFS_ASSERT((file->flags & LFS_O_SNAPSHOT) || + lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file)); lfs_soff_t res = lfs_file_rawsize(lfs, file); diff --git a/lfs.h b/lfs.h index 4fa3c97..8a8fa41 100644 --- a/lfs.h +++ b/lfs.h @@ -123,26 +123,30 @@ enum lfs_type { // File open flags enum lfs_open_flags { // open flags - LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_RDONLY = 1, // Open a file as read only #ifndef LFS_READONLY - LFS_O_WRONLY = 2, // Open a file as write only - LFS_O_RDWR = 3, // Open a file as read and write - LFS_O_CREAT = 0x0100, // Create a file if it does not exist - LFS_O_EXCL = 0x0200, // Fail if a file already exists - LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size - LFS_O_APPEND = 0x0800, // Move to end of file on every write + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write +#endif + +#ifndef LFS_READONLY + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + LFS_O_SNAPSHOT = 0x1000, // Open a temporary snapshot, ignore changes #endif // internally used flags #ifndef LFS_READONLY - LFS_F_DIRTY = 0x010000, // File does not match storage - LFS_F_WRITING = 0x020000, // File has been written since last flush + LFS_F_DIRTY = 0x010000, // File does not match storage #endif - LFS_F_READING = 0x040000, // File has been read since last flush + LFS_F_READING = 0x020000, // File has been read since last flush #ifndef LFS_READONLY - LFS_F_ERRED = 0x080000, // An error occurred during write + LFS_F_WRITING = 0x040000, // File has been written since last flush + LFS_F_ZOMBIE = 0x080000, // An error occurred during write #endif - LFS_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry }; // File seek flags diff --git a/tests/test_attrs.toml b/tests/test_attrs.toml index 1dc828b..dad4060 100644 --- a/tests/test_attrs.toml +++ b/tests/test_attrs.toml @@ -219,8 +219,8 @@ code = ''' assert(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; + lfs_file_opencfg(&lfs, &file, "hello/hello2", + LFS_O_WRONLY | LFS_O_CREAT, &cfg1) => LFS_ERR_NOSPC; struct lfs_attr attrs2[] = { {'A', buffer, 4}, diff --git a/tests/test_interspersed.toml b/tests/test_interspersed.toml index 2e042fb..9d35dfb 100644 --- a/tests/test_interspersed.toml +++ b/tests/test_interspersed.toml @@ -504,3 +504,433 @@ code = ''' } lfs_unmount(&lfs) => 0; ''' + +[[case]] # simple snapshot for reading +define.SIZE = [10, 100, 1000, 10000] +code = ''' + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + const char nums[] = "0123456789"; + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + const struct lfs_file_config filecfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', "abcd", 4}, + }, + }; + lfs_file_opencfg(&lfs, &file, "open_me", + LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // open reader/writer/snapshot + lfs_mount(&lfs, &cfg) => 0; + lfs_file_t reader; + const struct lfs_file_config readercfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_t writer; + const struct lfs_file_config writercfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_t snapshot; + const struct lfs_file_config snapshotcfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + lfs_file_opencfg(&lfs, &writer, "open_me", + LFS_O_WRONLY, &writercfg) => 0; + lfs_file_opencfg(&lfs, &snapshot, "open_me", + LFS_O_RDONLY | LFS_O_SNAPSHOT, &snapshotcfg) => 0; + + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE/2; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE/2; j++) { + lfs_file_read(&lfs, &snapshot, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + // write file + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &writer, &nums[j % 10], 1) => 1; + } + memcpy(writercfg.attrs[0].buffer, "0123", 4); + lfs_file_sync(&lfs, &writer) => 0; + + // reader should change + assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0); + for (int j = SIZE/2; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + // snapshot should remain unchanged + assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = SIZE/2; j < SIZE; j++) { + lfs_file_read(&lfs, &snapshot, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + lfs_file_close(&lfs, &reader) => 0; + lfs_file_close(&lfs, &writer) => 0; + lfs_file_close(&lfs, &snapshot) => 0; + + // disk should change + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "0123", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # simple snapshot for writing +define.SIZE = [10, 100, 1000, 10000] +code = ''' + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + const char nums[] = "0123456789"; + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + const struct lfs_file_config filecfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', "abcd", 4}, + }, + }; + lfs_file_opencfg(&lfs, &file, "open_me", + LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // open reader/snapshot + lfs_mount(&lfs, &cfg) => 0; + lfs_file_t reader; + const struct lfs_file_config readercfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_t snapshot; + const struct lfs_file_config snapshotcfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + lfs_file_opencfg(&lfs, &snapshot, "open_me", + LFS_O_RDWR | LFS_O_SNAPSHOT, &snapshotcfg) => 0; + + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE/2; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + assert(memcmp(snapshotcfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &snapshot, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + // modify snapshot + lfs_file_rewind(&lfs, &snapshot) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &snapshot, &nums[j % 10], 1) => 1; + } + memcpy(snapshotcfg.attrs[0].buffer, "0123", 4); + + lfs_file_rewind(&lfs, &snapshot) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &snapshot, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + lfs_file_sync(&lfs, &snapshot) => 0; + + // reader should not change + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = SIZE/2; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + // snapshot should changed + assert(memcmp(snapshotcfg.attrs[0].buffer, "0123", 4) == 0); + lfs_file_rewind(&lfs, &snapshot) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &snapshot, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + lfs_file_close(&lfs, &reader) => 0; + lfs_file_close(&lfs, &snapshot) => 0; + + // disk should not change + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # temporary files +define.SIZE = [10, 100, 1000, 10000] +define.TMP_PATH = 'range(4)' +code = ''' + const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; + const char nums[] = "0123456789"; + const char caps[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + const struct lfs_file_config filecfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', "abcd", 4}, + }, + }; + lfs_file_opencfg(&lfs, &file, "open_me", + LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1; + } + lfs_file_close(&lfs, &file) => 0; + lfs_file_opencfg(&lfs, &file, "dont_open_me", + LFS_O_CREAT | LFS_O_WRONLY, &filecfg) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &file, &alphas[j % 26], 1) => 1; + } + lfs_file_close(&lfs, &file) => 0; + lfs_unmount(&lfs) => 0; + + // open reader/writer/temp + lfs_mount(&lfs, &cfg) => 0; + lfs_file_t reader; + const struct lfs_file_config readercfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_t writer; + const struct lfs_file_config writercfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_t tmp; + const struct lfs_file_config tmpcfg = { + .attr_count = 1, + .attrs = (struct lfs_attr[]){ + {'A', &(uint8_t[4]){0}, 4} + }, + }; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + lfs_file_opencfg(&lfs, &writer, "open_me", + LFS_O_WRONLY, &writercfg) => 0; + const char *tmp_paths[] = {NULL, "/", "/tmp", "/open_me.tmp"}; + lfs_file_opencfg(&lfs, &tmp, tmp_paths[TMP_PATH], + LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT, &tmpcfg) => 0; + + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE/3; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + assert(memcmp(tmpcfg.attrs[0].buffer, "\0\0\0\0", 4) == 0); + assert(lfs_file_size(&lfs, &tmp) == 0); + + // write to tmp + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &tmp, &nums[j % 10], 1) => 1; + } + memcpy(tmpcfg.attrs[0].buffer, "0123", 4); + + lfs_file_rewind(&lfs, &tmp) => 0; + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &tmp, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + lfs_file_sync(&lfs, &tmp) => 0; + + // reader should not change + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = SIZE/3; j < 2*SIZE/3; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + + // tmp should change + assert(memcmp(tmpcfg.attrs[0].buffer, "0123", 4) == 0); + lfs_file_rewind(&lfs, &tmp) => 0; + for (int j = 0; j < SIZE/2; j++) { + lfs_file_read(&lfs, &tmp, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + // write to file + for (int j = 0; j < SIZE; j++) { + lfs_file_write(&lfs, &writer, &caps[j % 26], 1) => 1; + } + memcpy(writercfg.attrs[0].buffer, "ABCD", 4); + lfs_file_sync(&lfs, &writer) => 0; + + // reader should change + assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0); + for (int j = 2*SIZE/3; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == caps[j % 26]); + } + + // tmp should not change + assert(memcmp(tmpcfg.attrs[0].buffer, "0123", 4) == 0); + for (int j = SIZE/2; j < SIZE; j++) { + lfs_file_read(&lfs, &tmp, buffer, 1) => 1; + assert(buffer[0] == nums[j % 10]); + } + + lfs_file_close(&lfs, &reader) => 0; + lfs_file_close(&lfs, &writer) => 0; + lfs_file_close(&lfs, &tmp) => 0; + + // tmp should not appear on disk + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "dont_open_me") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "open_me") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == caps[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_file_opencfg(&lfs, &reader, "dont_open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; + + lfs_mount(&lfs, &cfg) => 0; + lfs_dir_open(&lfs, &dir, "/") => 0; + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, ".") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_DIR); + assert(strcmp(info.name, "..") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "dont_open_me") == 0); + lfs_dir_read(&lfs, &dir, &info) => 1; + assert(info.type == LFS_TYPE_REG); + assert(strcmp(info.name, "open_me") == 0); + lfs_dir_read(&lfs, &dir, &info) => 0; + lfs_dir_close(&lfs, &dir) => 0; + lfs_file_opencfg(&lfs, &reader, "open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "ABCD", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == caps[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_file_opencfg(&lfs, &reader, "dont_open_me", + LFS_O_RDONLY, &readercfg) => 0; + assert(memcmp(readercfg.attrs[0].buffer, "abcd", 4) == 0); + for (int j = 0; j < SIZE; j++) { + lfs_file_read(&lfs, &reader, buffer, 1) => 1; + assert(buffer[0] == alphas[j % 26]); + } + lfs_file_close(&lfs, &reader) => 0; + lfs_unmount(&lfs) => 0; +''' + +[[case]] # test snapshot open errors +code = ''' + lfs_format(&lfs, &cfg) => 0; + lfs_mount(&lfs, &cfg) => 0; + lfs_file_open(&lfs, &file, NULL, + LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "/", + LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_ISDIR; + lfs_file_open(&lfs, &file, "/tmp", + LFS_O_RDWR | LFS_O_SNAPSHOT) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "/tmp/", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT) => LFS_ERR_NOENT; + lfs_file_open(&lfs, &file, "/tmp/tmp", + LFS_O_RDWR | LFS_O_CREAT | LFS_O_SNAPSHOT) => LFS_ERR_NOENT; + lfs_unmount(&lfs) => 0; +'''