# Advanced mount tests after = ['test_mtree', 'test_trvs'] # test we can mount [cases.test_mount_simple] code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that various mount flags don't assert and are returned by # lfs3_fs_stat [cases.test_mount_flags] defines.RDONLY = [false, true] defines.FLUSH = [false, true] defines.SYNC = [false, true] defines.REVDBG = [false, true] defines.REVNOISE = [false, true] defines.CKPROGS = [false, true] defines.CKFETCHES = [false, true] defines.CKMETAPARITY = [false, true] defines.CKDATACKSUMS = [false, true] defines.MKCONSISTENT = [false, true] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] if = [ 'LFS3_IFDEF_REVDBG(true, !REVDBG)', 'LFS3_IFDEF_REVNOISE(true, !REVNOISE)', '!REVDBG || !REVNOISE', 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', 'LFS3_IFDEF_CKFETCHES(true, !CKFETCHES)', 'LFS3_IFDEF_CKMETAPARITY(true, !CKMETAPARITY)', 'LFS3_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)', '!RDONLY || !MKCONSISTENT', '!RDONLY || !LOOKAHEAD', '!RDONLY || !COMPACT', ] code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; lfs3_mount(&lfs3, ((RDONLY) ? LFS3_M_RDONLY : LFS3_M_RDWR) | ((FLUSH) ? LFS3_M_FLUSH : 0) | ((SYNC) ? LFS3_M_SYNC : 0) | ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_M_REVDBG, -1) : 0) | ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_M_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_M_CKFETCHES, -1) : 0) | ((CKMETAPARITY) ? LFS3_IFDEF_CKMETAPARITY(LFS3_M_CKMETAPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS3_IFDEF_CKDATACKSUMS(LFS3_M_CKDATACKSUMS, -1) : 0) | ((MKCONSISTENT) ? LFS3_M_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0) | ((COMPACT) ? LFS3_M_COMPACT : 0) | ((CKMETA) ? LFS3_M_CKMETA : 0) | ((CKDATA) ? LFS3_M_CKDATA : 0), CFG) => 0; // lfs3_fs_stat only returns some flags struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( ((RDONLY) ? LFS3_I_RDONLY : 0) | ((FLUSH) ? LFS3_I_FLUSH : 0) | ((SYNC) ? LFS3_I_SYNC : 0) | ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_M_REVDBG, -1) : 0) | ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_M_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_I_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_I_CKFETCHES, -1) : 0) | ((CKMETAPARITY) ? LFS3_IFDEF_CKMETAPARITY(LFS3_I_CKMETAPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS3_IFDEF_CKDATACKSUMS(LFS3_I_CKDATACKSUMS, -1) : 0) | ((!MKCONSISTENT) ? LFS3_I_MKCONSISTENT : 0) | ((!LOOKAHEAD) ? LFS3_I_LOOKAHEAD : 0) | ((!COMPACT) ? LFS3_I_COMPACT : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0) | ((!CKDATA) ? LFS3_I_CKDATA : 0) | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); lfs3_unmount(&lfs3) => 0; ''' # test that various format flags don't, uh, assert or anything # # these end up passed to mount internally [cases.test_mount_format_flags] defines.REVDBG = [false, true] defines.REVNOISE = [false, true] defines.CKPROGS = [false, true] defines.CKFETCHES = [false, true] defines.CKMETAPARITY = [false, true] defines.CKDATACKSUMS = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] if = [ 'LFS3_IFDEF_REVDBG(true, !REVDBG)', 'LFS3_IFDEF_REVNOISE(true, !REVNOISE)', '!REVDBG || !REVNOISE', 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', 'LFS3_IFDEF_CKFETCHES(true, !CKFETCHES)', 'LFS3_IFDEF_CKMETAPARITY(true, !CKMETAPARITY)', 'LFS3_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)', ] code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((REVDBG) ? LFS3_IFDEF_REVDBG(LFS3_F_REVDBG, -1) : 0) | ((REVNOISE) ? LFS3_IFDEF_REVNOISE(LFS3_F_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS3_IFDEF_CKFETCHES(LFS3_F_CKFETCHES, -1) : 0) | ((CKMETAPARITY) ? LFS3_IFDEF_CKMETAPARITY(LFS3_F_CKMETAPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS3_IFDEF_CKDATACKSUMS(LFS3_F_CKDATACKSUMS, -1) : 0) | ((CKMETA) ? LFS3_F_CKMETA : 0) | ((CKDATA) ? LFS3_F_CKDATA : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that on-mount traversals do what they say they do [cases.test_mount_t_lookahead] defines.CKMETA = [false, true] defines.CKDATA = [false, true] code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // by default we need a lookahead scan lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( LFS3_I_MKCONSISTENT | LFS3_I_LOOKAHEAD | LFS3_I_COMPACT | LFS3_I_CKMETA | LFS3_I_CKDATA | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); lfs3_unmount(&lfs3) => 0; // with LFS3_M_LOOKAHEAD, mount performs a lookahead scan lfs3_mount(&lfs3, LFS3_M_RDWR | LFS3_M_LOOKAHEAD | ((CKMETA) ? LFS3_M_CKMETA : 0) | ((CKDATA) ? LFS3_M_CKDATA : 0), CFG) => 0; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( LFS3_I_MKCONSISTENT | LFS3_I_COMPACT // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0) | ((!CKDATA) ? LFS3_I_CKDATA : 0) | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_t_compact] defines.LOOKAHEAD = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.SIZE = [ 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] # set compact thresh to minimum defines.GC_COMPACT_THRESH = 'BLOCK_SIZE/2' code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; uint32_t prng = 42; // first lets create a compactable filesystem lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // write to our mdir until >gc_compact_thresh full lfs3_file_t file; lfs3_file_open(&lfs3, &file, "jellyfish", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; // hack, don't use the internals like this uint8_t wbuf[SIZE]; while ((file.b.h.mdir.r.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { lfs3_file_rewind(&lfs3, &file) => 0; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &file) => 0; } lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // by default mount does not compact lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( LFS3_I_MKCONSISTENT | LFS3_I_LOOKAHEAD | LFS3_I_COMPACT | LFS3_I_CKMETA | LFS3_I_CKDATA | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); lfs3_unmount(&lfs3) => 0; // with LFS3_M_COMPACT, mount compact any uncompacted blocks lfs3_mount(&lfs3, LFS3_M_RDWR | LFS3_M_COMPACT | ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0) | ((CKMETA) ? LFS3_M_CKMETA : 0) | ((CKDATA) ? LFS3_M_CKDATA : 0), CFG) => 0; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( LFS3_I_MKCONSISTENT | ((!LOOKAHEAD) ? LFS3_I_LOOKAHEAD : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0) | ((!CKDATA) ? LFS3_I_CKDATA : 0) | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); // mdir should have been compacted lfs3_file_open(&lfs3, &file, "jellyfish", LFS3_O_RDONLY) => 0; assert((file.b.h.mdir.r.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); // check we can still read the file uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_t_mkconsistent] defines.LOOKAHEAD = [false, true] defines.COMPACT = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] defines.SIZE = 'FILE_CACHE_SIZE/2' # <=2 => grm-able # >2 => requires orphans defines.ORPHANS = [0, 1, 2, 3, 100] code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; uint32_t prng = 42; // first lets create some orphans lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // create two files lfs3_file_t file; lfs3_file_open(&lfs3, &file, "cuttlefish", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf1[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, wbuf1, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "octopus", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf2[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, wbuf2, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfs3_file_t orphans[ORPHANS]; for (lfs3_size_t i = 0; i < ORPHANS; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfs3_file_open(&lfs3, &orphans[i], name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL | LFS3_O_DESYNC) => 0; } for (lfs3_size_t i = 0; i < ORPHANS; i++) { lfs3_file_close(&lfs3, &orphans[i]) => 0; } lfs3_unmount(&lfs3) => 0; // by default we clean up orphans lazily lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( LFS3_I_MKCONSISTENT | LFS3_I_LOOKAHEAD | LFS3_I_COMPACT | LFS3_I_CKMETA | LFS3_I_CKDATA | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); lfs3_unmount(&lfs3) => 0; // with LFS3_M_MKCONSISTENT, mount cleans up orphans eagerly lfs3_mount(&lfs3, LFS3_M_RDWR | LFS3_M_MKCONSISTENT | ((LOOKAHEAD) ? LFS3_M_LOOKAHEAD : 0) | ((COMPACT) ? LFS3_M_COMPACT : 0) | ((CKMETA) ? LFS3_M_CKMETA : 0) | ((CKDATA) ? LFS3_M_CKDATA : 0), CFG) => 0; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.flags == ( ((!LOOKAHEAD) ? LFS3_I_LOOKAHEAD : 0) | ((!COMPACT) ? LFS3_I_COMPACT : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS3_I_CKMETA : 0) | ((!CKDATA) ? LFS3_I_CKDATA : 0) | LFS3_IFDEF_BMAP(LFS3_I_BMAPCACHE, 0))); // check we can still read the files lfs3_file_open(&lfs3, &file, "cuttlefish", LFS3_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf1, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "octopus", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf2, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; ''' # test we can detect at least fully clobbered blocks # # these are tested more thoroughly in test_ck [cases.test_mount_t_ckmeta] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs3_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; } // traverse to find blocks lfs3_trv_t trv; lfs3_trv_open(&lfs3, &trv, LFS3_T_RDONLY) => 0; lfs3_block_t k = 0; for (lfs3_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs3_tinfo tinfo; int err = lfs3_trv_read(&lfs3, &trv, &tinfo); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { lfs3_trv_close(&lfs3, &trv) => 0; lfs3_unmount(&lfs3) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS3_BTYPE_MDIR || tinfo.btype == LFS3_BTYPE_BTREE) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS3_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS3_BTYPE_MDIR) ? 2 : 1; lfs3_trv_close(&lfs3, &trv) => 0; lfs3_unmount(&lfs3) => 0; goto clobbered; } } k += 1; } } clobbered:; // mount with LFS3_M_CKMETA, we should detect clobbered blocks lfs3_mount(&lfs3, LFS3_M_RDWR | LFS3_M_CKMETA, CFG) => LFS3_ERR_CORRUPT; } done:; ''' [cases.test_mount_t_ckdata] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs3_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; } // traverse to find blocks lfs3_trv_t trv; lfs3_trv_open(&lfs3, &trv, LFS3_T_RDONLY) => 0; lfs3_block_t k = 0; for (lfs3_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs3_tinfo tinfo; int err = lfs3_trv_read(&lfs3, &trv, &tinfo); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { lfs3_trv_close(&lfs3, &trv) => 0; lfs3_unmount(&lfs3) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS3_BTYPE_MDIR || tinfo.btype == LFS3_BTYPE_BTREE || tinfo.btype == LFS3_BTYPE_DATA) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS3_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS3_BTYPE_MDIR) ? 2 : 1; lfs3_trv_close(&lfs3, &trv) => 0; lfs3_unmount(&lfs3) => 0; goto clobbered; } } k += 1; } } clobbered:; // mount with LFS3_M_CKDATA, we should detect clobbered blocks // // note LFS3_M_CKDATA implies LFS3_M_CKMETA lfs3_mount(&lfs3, LFS3_M_RDWR | LFS3_M_CKDATA, CFG) => LFS3_ERR_CORRUPT; } done:; ''' ## incompatiblity tests ## # test that we fail if we find no magic [cases.test_mount_incompat_no_magic] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // delete the magic string // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR( LFS3_TAG_RM | LFS3_TAG_MAGIC, 0))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT; ''' # test that we fail if we find bad magic [cases.test_mount_incompat_bad_magic] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // tweak the magic string // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_MAGIC, 0, "lottlefs", 8))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT; ''' # test that we fail to mount after a major version bump [cases.test_mount_incompat_major] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // bump the major version // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_VERSION, 0, ((const uint8_t[2]){ LFS3_DISK_VERSION_MAJOR+1, 0}), 2))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount after a minor version bump [cases.test_mount_incompat_minor] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // bump the minor version // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_VERSION, 0, ((const uint8_t[2]){ LFS3_DISK_VERSION_MAJOR, LFS3_DISK_VERSION_MINOR+1}), 2))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount incompatible rcompat flags [cases.test_mount_incompat_rcompat] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set the nonstandard rcompat flag, this will always be incompatible // with standard littlefs // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LE32( LFS3_TAG_RCOMPAT, 0, LFS3_RCOMPAT_COMPAT | LFS3_RCOMPAT_NONSTANDARD))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount incompatible wcompat flags [cases.test_mount_incompat_wcompat] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set the nonstandard rcompat flag, this will always be incompatible // with standard littlefs // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LE32( LFS3_TAG_WCOMPAT, 0, LFS3_WCOMPAT_COMPAT | LFS3_WCOMPAT_NONSTANDARD))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; // but we _can_ mount readonly lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that an incompatible ocompat flag is a noop [cases.test_mount_incompat_ocompat] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set the nonstandard ocompat flag, this will always be incompatible // with standard littlefs // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LE32( LFS3_TAG_OCOMPAT, 0, LFS3_OCOMPAT_COMPAT | LFS3_OCOMPAT_NONSTANDARD))) => 0; lfs3_unmount(&lfs3) => 0; // mount should _not_ fail, ocompat should always be ignored lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that we fail to mount rdonly images [cases.test_mount_incompat_rdonly] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set the rdonly flag, this prevents writing from a littlefs image // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LE32( LFS3_TAG_WCOMPAT, 0, LFS3_WCOMPAT_COMPAT | LFS3_WCOMPAT_RDONLY))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; // but we _can_ mount readonly lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that we fail to mount wronly images [cases.test_mount_incompat_wronly] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set the wronly flag, this prevents reading from a littlefs image // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LE32( LFS3_TAG_RCOMPAT, 0, LFS3_RCOMPAT_COMPAT | LFS3_RCOMPAT_WRONLY))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # these are just a bit harder to detect [cases.test_mount_incompat_rcompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set a really far rcompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); overflow[FLAG / 8] |= 1 << (FLAG % 8); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t rcompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_RCOMPAT, 0, lfs3_data_fromle32(LFS3_RCOMPAT_COMPAT, rcompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' [cases.test_mount_incompat_wcompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; uint8_t flags[9] = {0}; flags[FLAG / 8] |= 1 << (FLAG % 8); // set a really far wcompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); overflow[FLAG / 8] |= 1 << (FLAG % 8); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t wcompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_WCOMPAT, 0, lfs3_data_fromle32(LFS3_WCOMPAT_COMPAT, wcompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; // but we _can_ mount readonly lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_ocompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set a really far ocompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); overflow[FLAG / 8] |= 1 << (FLAG % 8); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t ocompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_OCOMPAT, 0, lfs3_data_fromle32(LFS3_OCOMPAT_COMPAT, ocompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should _not_ fail, ocompat should always be ignored lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # but just appending zeros is _not_ an error [cases.test_mount_incompat_rcompat_padding] defines.OVERFLOW = 72 in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set a really far rcompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t rcompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_RCOMPAT, 0, lfs3_data_fromle32(LFS3_RCOMPAT_COMPAT, rcompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should _not_ fail, extra zeros should be ignored lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_wcompat_padding] defines.OVERFLOW = 72 in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; uint8_t flags[9] = {0}; flags[FLAG / 8] |= 1 << (FLAG % 8); // set a really far wcompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t wcompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_WCOMPAT, 0, lfs3_data_fromle32(LFS3_WCOMPAT_COMPAT, wcompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should _not_ fail, extra zeros should be ignored lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_ocompat_padding] defines.OVERFLOW = 72 in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set a really far ocompat flag // // note we're messing around with internals to do this! this // is not a user API uint8_t overflow[OVERFLOW / 8]; memset(overflow, 0, sizeof(overflow)); lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t ocompat_buf[LFS3_LE32_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_OCOMPAT, 0, lfs3_data_fromle32(LFS3_OCOMPAT_COMPAT, ocompat_buf), LFS3_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfs3_unmount(&lfs3) => 0; // mount should _not_ fail, extra zeros should be ignored lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => 0; lfs3_unmount(&lfs3) => 0; ''' # test that we fail to mount incompatible block sizes [cases.test_mount_incompat_block_size] defines.INC_BLOCK_SIZE = ['BLOCK_SIZE/2', 'BLOCK_SIZE*2'] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set an incompatible block size // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_GEOMETRY( LFS3_TAG_GEOMETRY, 0, (&(lfs3_geometry_t){ INC_BLOCK_SIZE, BLOCK_COUNT})))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount after incompatible block counts [cases.test_mount_incompat_block_count] defines.INC_BLOCK_COUNT = ['BLOCK_COUNT*2'] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set an incompatible block count // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_GEOMETRY( LFS3_TAG_GEOMETRY, 0, (&(lfs3_geometry_t){ BLOCK_SIZE, INC_BLOCK_COUNT})))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount after incompatible name limit [cases.test_mount_incompat_name_limit] defines.INC_NAME_LIMIT = ['LFS3_NAME_MAX*2'] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set an incompatible block size // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_LEB128( LFS3_TAG_NAMELIMIT, 0, INC_NAME_LIMIT))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test that we fail to mount after incompatible file limit [cases.test_mount_incompat_file_limit] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // set an incompatible file limit // // note we're messing around with internals to do this! this // is not a user API lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; uint8_t file_limit_buf[LFS3_LEB128_DSIZE]; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_FILELIMIT, 0, // it's a bit difficult to test this since file limit // is usually our integer limit, but we can force a // larger value by inserting an extra byte into our // leb128 encoding LFS3_DATA_BUF("\xff", 1), lfs3_data_fromleb128(LFS3_FILE_MAX, file_limit_buf)))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test what happens if we find an unknown config [cases.test_mount_incompat_unknown_config] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create an unknown config lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_mdir_commit(&lfs3, &lfs3.mroot, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_CONFIG + 0x42, 0, "oh no!", strlen("oh no!")))) => 0; lfs3_unmount(&lfs3) => 0; // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_NOTSUP; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_NOTSUP; ''' # test what happens if we find an unknown file type [cases.test_mount_incompat_unknown_type] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // open/mkdir should error lfs3_file_open(&lfs3, &file, "b", LFS3_O_RDONLY) => LFS3_ERR_NOTSUP; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT) => LFS3_ERR_NOTSUP; lfs3_mkdir(&lfs3, "b") => LFS3_ERR_EXIST; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_rm] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // removing unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_remove(&lfs3, "b") => 0; // check that things look reasonable lfs3_stat(&lfs3, "b", &info) => LFS3_ERR_NOENT; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_src] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "b", "c") => 0; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "c", &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_dst] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "c", "b") => 0; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_src_dst] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh hi!", strlen("oh hi!")) => strlen("oh hi!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; path = "c"; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "b", "c") => 0; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "c", &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_noop] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "b", "b") => 0; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi c!")); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_notdir] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_mkdir(&lfs3, "c") => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "c", "b") => LFS3_ERR_NOTDIR; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_isdir] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // create some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "a", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "b", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; lfs3_file_write(&lfs3, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfs3_file_close(&lfs3, &file) => 0; lfs3_mkdir(&lfs3, "c") => 0; lfs3_unmount(&lfs3) => 0; // change a file's type to something unknown lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; const char *path = "b"; lfs3_mdir_t mdir; lfs3_did_t did; lfs3_mtree_pathlookup(&lfs3, &path, &mdir, &did) => LFS3_TAG_REG; lfs3_mdir_commit(&lfs3, &mdir, LFS3_RATTRS( LFS3_RATTR_CAT( LFS3_TAG_MASK8 | (LFS3_TAG_NAME + 0x13), 0, lfs3_data_fromleb128(did, (uint8_t[LFS3_LEB128_DSIZE]){0}), LFS3_DATA_BUF(path, lfs3_path_namelen(path))))) => 0; lfs3_unmount(&lfs3) => 0; // mount lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs3_info info; lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfs3_rename(&lfs3, "b", "c") => LFS3_ERR_ISDIR; // check that things look reasonable after renaming/removing lfs3_stat(&lfs3, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_open(&lfs3, &dir, "/") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == strlen("hi a!")); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS3_TYPE_UNKNOWN); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; lfs3_unmount(&lfs3) => 0; ''' # Ok, here's an interesting one, test that we fail if we're # "out-of-phase", i.e. the mrootanchor has been shifted by a small # number of blocks. # # This can happen if we find the wrong mrootanchor (after, say, a magic # scan), and risks filesystem corruption. To prevent this, we include 2 # phase bits in cksum tags to detect up to a 3 block shift (the maximum # number of redund mrootanchors) # [cases.test_mount_incompat_out_of_phase] defines.PHASE = [1, 2, 3, 4] in = 'lfs3.c' code = ''' // create a superblock lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; // with some files lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; lfs3_file_t file; lfs3_file_open(&lfs3, &file, "r2d2", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; char wbuf[256]; strcpy(wbuf, "beep boop"); lfs3_file_write(&lfs3, &file, wbuf, strlen(wbuf)) => strlen(wbuf); lfs3_file_close(&lfs3, &file) => 0; lfs3_file_open(&lfs3, &file, "c3po", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; strcpy(wbuf, "we seem to be made to suffer"); lfs3_file_write(&lfs3, &file, wbuf, strlen(wbuf)) => strlen(wbuf); lfs3_file_close(&lfs3, &file) => 0; // shift the filesystem out-of-phase uint8_t shift_buf[BLOCK_SIZE]; for (lfs3_size_t i = 0; i < 4; i++) { CFG->read(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 0; CFG->erase(CFG, 4-1-i + PHASE) => 0; CFG->prog(CFG, 4-1-i + PHASE, 0, shift_buf, BLOCK_SIZE) => 0; memset(shift_buf, 0, BLOCK_SIZE); strcpy((char*)shift_buf, "these aren't the files you're looking for ;)"); CFG->erase(CFG, 4-1-i) => 0; CFG->prog(CFG, 4-1-i, 0, shift_buf, BLOCK_SIZE) => 0; } // mount should now fail lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => LFS3_ERR_CORRUPT; lfs3_mount(&lfs3, LFS3_M_RDONLY, CFG) => LFS3_ERR_CORRUPT; '''