# Advanced mount tests after = ['test_mtree', 'test_traversal'] # test we can mount [cases.test_mount_simple] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' # test that various mount flags don't assert and are returned by # lfsr_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.CKPARITY = [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 = [ 'LFS_IFDEF_REVDBG(true, !REVDBG)', 'LFS_IFDEF_REVNOISE(true, !REVNOISE)', '!REVDBG || !REVNOISE', 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', 'LFS_IFDEF_CKPARITY(true, !CKPARITY)', 'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)', '!RDONLY || !MKCONSISTENT', '!RDONLY || !LOOKAHEAD', '!RDONLY || !COMPACT', ] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, ((RDONLY) ? LFS_M_RDONLY : LFS_M_RDWR) | ((FLUSH) ? LFS_M_FLUSH : 0) | ((SYNC) ? LFS_M_SYNC : 0) | ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_M_REVDBG, -1) : 0) | ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_M_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_M_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_M_CKFETCHES, -1) : 0) | ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_M_CKPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS_IFDEF_CKDATACKSUMS(LFS_M_CKDATACKSUMS, -1) : 0) | ((MKCONSISTENT) ? LFS_M_MKCONSISTENT : 0) | ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0) | ((COMPACT) ? LFS_M_COMPACT : 0) | ((CKMETA) ? LFS_M_CKMETA : 0) | ((CKDATA) ? LFS_M_CKDATA : 0), CFG) => 0; // lfsr_fs_stat only returns some flags struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( ((RDONLY) ? LFS_I_RDONLY : 0) | ((FLUSH) ? LFS_I_FLUSH : 0) | ((SYNC) ? LFS_I_SYNC : 0) | ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_M_REVDBG, -1) : 0) | ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_M_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_I_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_I_CKFETCHES, -1) : 0) | ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_I_CKPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS_IFDEF_CKDATACKSUMS(LFS_I_CKDATACKSUMS, -1) : 0) | ((!MKCONSISTENT) ? LFS_I_MKCONSISTENT : 0) | ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0) | ((!COMPACT) ? LFS_I_COMPACT : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0) | ((!CKDATA) ? LFS_I_CKDATA : 0))); lfsr_unmount(&lfs) => 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.CKPARITY = [false, true] defines.CKDATACKSUMS = [false, true] defines.CKMETA = [false, true] defines.CKDATA = [false, true] if = [ 'LFS_IFDEF_REVDBG(true, !REVDBG)', 'LFS_IFDEF_REVNOISE(true, !REVNOISE)', '!REVDBG || !REVNOISE', 'LFS_IFDEF_CKPROGS(true, !CKPROGS)', 'LFS_IFDEF_CKFETCHES(true, !CKFETCHES)', 'LFS_IFDEF_CKPARITY(true, !CKPARITY)', 'LFS_IFDEF_CKDATACKSUMS(true, !CKDATACKSUMS)', ] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR | ((REVDBG) ? LFS_IFDEF_REVDBG(LFS_F_REVDBG, -1) : 0) | ((REVNOISE) ? LFS_IFDEF_REVNOISE(LFS_F_REVNOISE, -1) : 0) | ((CKPROGS) ? LFS_IFDEF_CKPROGS(LFS_F_CKPROGS, -1) : 0) | ((CKFETCHES) ? LFS_IFDEF_CKFETCHES(LFS_F_CKFETCHES, -1) : 0) | ((CKPARITY) ? LFS_IFDEF_CKPARITY(LFS_F_CKPARITY, -1) : 0) | ((CKDATACKSUMS) ? LFS_IFDEF_CKDATACKSUMS(LFS_F_CKDATACKSUMS, -1) : 0) | ((CKMETA) ? LFS_F_CKMETA : 0) | ((CKDATA) ? LFS_F_CKDATA : 0), CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 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 = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // by default we need a lookahead scan lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( LFS_I_MKCONSISTENT | LFS_I_LOOKAHEAD | LFS_I_COMPACT | LFS_I_CKMETA | LFS_I_CKDATA)); lfsr_unmount(&lfs) => 0; // with LFS_M_LOOKAHEAD, mount performs a lookahead scan lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_LOOKAHEAD | ((CKMETA) ? LFS_M_CKMETA : 0) | ((CKDATA) ? LFS_M_CKDATA : 0), CFG) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( LFS_I_MKCONSISTENT | LFS_I_COMPACT // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0) | ((!CKDATA) ? LFS_I_CKDATA : 0))); lfsr_unmount(&lfs) => 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 = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; uint32_t prng = 42; // first lets create a compactable filesystem lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // write to our mdir until >gc_compact_thresh full lfsr_file_t file; lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; // hack, don't use the internals like this uint8_t wbuf[SIZE]; while ((file.b.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH) { lfsr_file_rewind(&lfs, &file) => 0; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &file) => 0; } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // by default mount does not compact lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( LFS_I_MKCONSISTENT | LFS_I_LOOKAHEAD | LFS_I_COMPACT | LFS_I_CKMETA | LFS_I_CKDATA)); lfsr_unmount(&lfs) => 0; // with LFS_M_COMPACT, mount compact any uncompacted blocks lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_COMPACT | ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0) | ((CKMETA) ? LFS_M_CKMETA : 0) | ((CKDATA) ? LFS_M_CKDATA : 0), CFG) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( LFS_I_MKCONSISTENT | ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0) | ((!CKDATA) ? LFS_I_CKDATA : 0))); // mdir should have been compacted lfsr_file_open(&lfs, &file, "jellyfish", LFS_O_RDONLY) => 0; assert((file.b.o.mdir.rbyd.eoff & 0x7fffffff) <= GC_COMPACT_THRESH); // check we can still read the file uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 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 = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; uint32_t prng = 42; // first lets create some orphans lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create two files lfsr_file_t file; lfsr_file_open(&lfs, &file, "cuttlefish", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf1[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf1[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf1, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "octopus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf2[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf2[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf2, SIZE) => SIZE; lfsr_file_close(&lfs, &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) // lfsr_file_t orphans[ORPHANS]; for (lfs_size_t i = 0; i < ORPHANS; i++) { char name[256]; sprintf(name, "jellyfish%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; } for (lfs_size_t i = 0; i < ORPHANS; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_unmount(&lfs) => 0; // by default we clean up orphans lazily lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( LFS_I_MKCONSISTENT | LFS_I_LOOKAHEAD | LFS_I_COMPACT | LFS_I_CKMETA | LFS_I_CKDATA)); lfsr_unmount(&lfs) => 0; // with LFS_M_MKCONSISTENT, mount cleans up orphans eagerly lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_MKCONSISTENT | ((LOOKAHEAD) ? LFS_M_LOOKAHEAD : 0) | ((COMPACT) ? LFS_M_COMPACT : 0) | ((CKMETA) ? LFS_M_CKMETA : 0) | ((CKDATA) ? LFS_M_CKDATA : 0), CFG) => 0; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.flags == ( ((!LOOKAHEAD) ? LFS_I_LOOKAHEAD : 0) | ((!COMPACT) ? LFS_I_COMPACT : 0) // note ckdata implies ckmeta | ((!CKMETA && !CKDATA) ? LFS_I_CKMETA : 0) | ((!CKDATA) ? LFS_I_CKDATA : 0))); // check we can still read the files lfsr_file_open(&lfs, &file, "cuttlefish", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf1, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf2, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 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 = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_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 != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto clobbered; } } k += 1; } } clobbered:; // mount with LFS_M_CKMETA, we should detect clobbered blocks lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKMETA, CFG) => LFS_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 = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_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 != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto clobbered; } } k += 1; } } clobbered:; // mount with LFS_M_CKDATA, we should detect clobbered blocks // // note LFS_M_CKDATA implies LFS_M_CKMETA lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATA, CFG) => LFS_ERR_CORRUPT; } done:; ''' ## incompatiblity tests ## # test that we fail if we find no magic [cases.test_mount_incompat_no_magic] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // delete the magic string // // note we're messing around with internals to do this! this // is not a user API lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR( LFSR_TAG_RM | LFSR_TAG_MAGIC, 0))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT; ''' # test that we fail if we find bad magic [cases.test_mount_incompat_bad_magic] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // tweak the magic string // // note we're messing around with internals to do this! this // is not a user API lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_BUF( LFSR_TAG_MAGIC, 0, "lottlefs", 8))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT; ''' # test that we fail to mount after a major version bump [cases.test_mount_incompat_major] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // bump the major version // // note we're messing around with internals to do this! this // is not a user API lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_BUF( LFSR_TAG_VERSION, 0, ((const uint8_t[2]){ LFS_DISK_VERSION_MAJOR+1, 0}), 2))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test that we fail to mount after a minor version bump [cases.test_mount_incompat_minor] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // bump the minor version // // note we're messing around with internals to do this! this // is not a user API lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_BUF( LFSR_TAG_VERSION, 0, ((const uint8_t[2]){ LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR+1}), 2))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test that we fail to mount incompatible rcompat flags [cases.test_mount_incompat_rcompat] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LE32( LFSR_TAG_RCOMPAT, 0, LFSR_RCOMPAT_COMPAT | LFSR_RCOMPAT_NONSTANDARD))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test that we fail to mount incompatible wcompat flags [cases.test_mount_incompat_wcompat] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LE32( LFSR_TAG_WCOMPAT, 0, LFSR_WCOMPAT_COMPAT | LFSR_WCOMPAT_NONSTANDARD))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; // but we _can_ mount readonly lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' # test that an incompatible ocompat flag is a noop [cases.test_mount_incompat_ocompat] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LE32( LFSR_TAG_OCOMPAT, 0, LFSR_OCOMPAT_COMPAT | LFSR_OCOMPAT_NONSTANDARD))) => 0; lfsr_unmount(&lfs) => 0; // mount should _not_ fail, ocompat should always be ignored lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' # test that we fail to mount rdonly images [cases.test_mount_incompat_rdonly] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LE32( LFSR_TAG_WCOMPAT, 0, LFSR_WCOMPAT_COMPAT | LFSR_WCOMPAT_RDONLY))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; // but we _can_ mount readonly lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' # test that we fail to mount wronly images [cases.test_mount_incompat_wronly] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LE32( LFSR_TAG_RCOMPAT, 0, LFSR_RCOMPAT_COMPAT | LFSR_RCOMPAT_WRONLY))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # these are just a bit harder to detect [cases.test_mount_incompat_rcompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t rcompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_RCOMPAT, 0, lfsr_data_fromle32(LFSR_RCOMPAT_COMPAT, rcompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' [cases.test_mount_incompat_wcompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t wcompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_WCOMPAT, 0, lfsr_data_fromle32(LFSR_WCOMPAT_COMPAT, wcompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; // but we _can_ mount readonly lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_ocompat_overflow] defines.OVERFLOW = 72 defines.FLAG = 'range(72)' in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t ocompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_OCOMPAT, 0, lfsr_data_fromle32(LFSR_OCOMPAT_COMPAT, ocompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should _not_ fail, ocompat should always be ignored lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' # but just appending zeros is _not_ an error [cases.test_mount_incompat_rcompat_padding] defines.OVERFLOW = 72 in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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)); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t rcompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_RCOMPAT, 0, lfsr_data_fromle32(LFSR_RCOMPAT_COMPAT, rcompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should _not_ fail, extra zeros should be ignored lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_wcompat_padding] defines.OVERFLOW = 72 in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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)); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t wcompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_WCOMPAT, 0, lfsr_data_fromle32(LFSR_WCOMPAT_COMPAT, wcompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should _not_ fail, extra zeros should be ignored lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_ocompat_padding] defines.OVERFLOW = 72 in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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)); lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t ocompat_buf[LFSR_LE32_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_OCOMPAT, 0, lfsr_data_fromle32(LFSR_OCOMPAT_COMPAT, ocompat_buf), LFSR_DATA_BUF(overflow, sizeof(overflow))))) => 0; lfsr_unmount(&lfs) => 0; // mount should _not_ fail, extra zeros should be ignored lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => 0; lfsr_unmount(&lfs) => 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 = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_GEOMETRY( LFSR_TAG_GEOMETRY, 0, (&(lfsr_geometry_t){ INC_BLOCK_SIZE, BLOCK_COUNT})))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_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 = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_GEOMETRY( LFSR_TAG_GEOMETRY, 0, (&(lfsr_geometry_t){ BLOCK_SIZE, INC_BLOCK_COUNT})))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test that we fail to mount after incompatible name limit [cases.test_mount_incompat_name_limit] defines.INC_NAME_LIMIT = ['LFS_NAME_MAX*2'] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_LEB128( LFSR_TAG_NAMELIMIT, 0, INC_NAME_LIMIT))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test that we fail to mount after incompatible file limit [cases.test_mount_incompat_file_limit] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint8_t file_limit_buf[LFSR_LEB128_DSIZE]; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_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 LFSR_DATA_BUF("\xff", 1), lfsr_data_fromleb128(LFS_FILE_MAX, file_limit_buf)))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test what happens if we find an unknown config [cases.test_mount_incompat_unknown_config] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create an unknown config lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_RATTRS( LFSR_RATTR_BUF( LFSR_TAG_CONFIG + 0x42, 0, "oh no!", strlen("oh no!")))) => 0; lfsr_unmount(&lfs) => 0; // mount should now fail lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_NOTSUP; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_NOTSUP; ''' # test what happens if we find an unknown file type [cases.test_mount_incompat_unknown_type] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // open/mkdir should error lfsr_file_open(&lfs, &file, "b", LFS_O_RDONLY) => LFS_ERR_NOTSUP; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_NOTSUP; lfsr_mkdir(&lfs, "b") => LFS_ERR_EXIST; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_rm] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // removing unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_remove(&lfs, "b") => 0; // check that things look reasonable lfsr_stat(&lfs, "b", &info) => LFS_ERR_NOENT; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_src] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "b", "c") => 0; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "c", &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_dst] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "c", "b") => 0; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_src_dst] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh hi!", strlen("oh hi!")) => strlen("oh hi!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; path = "c"; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "b", "c") => 0; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "c", &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_noop] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!"); lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "b", "b") => 0; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi c!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_notdir] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_mkdir(&lfs, "c") => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "c", "b") => LFS_ERR_NOTDIR; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_mount_incompat_unknown_type_mv_isdir] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // create some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "a", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!"); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "b", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "oh no!", strlen("oh no!")) => strlen("oh no!"); lfsr_file_close(&lfs, &file) => 0; lfsr_mkdir(&lfs, "c") => 0; lfsr_unmount(&lfs) => 0; // change a file's type to something unknown lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; const char *path = "b"; lfsr_mdir_t mdir; lfsr_did_t did; lfsr_mtree_pathlookup(&lfs, &path, &mdir, NULL, &did) => 0; lfsr_mdir_commit(&lfs, &mdir, LFSR_RATTRS( LFSR_RATTR_CAT( LFSR_TAG_MASK8 | (LFSR_TAG_NAME + 0x13), 0, lfsr_data_fromleb128(did, (uint8_t[LFSR_LEB128_DSIZE]){0}), LFSR_DATA_BUF(path, lfsr_path_namelen(path))))) => 0; lfsr_unmount(&lfs) => 0; // mount lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // our file should appear as an unknown type struct lfs_info info; lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // renaming unknown files should still work, if this would leak // resources the new type should set a wcompat flag lfsr_rename(&lfs, "b", "c") => LFS_ERR_ISDIR; // check that things look reasonable after renaming/removing lfsr_stat(&lfs, "b", &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "a") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("hi a!")); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "b") == 0); assert(info.type == LFS_TYPE_UNKNOWN); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "c") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 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 = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; // with some files lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_t file; lfsr_file_open(&lfs, &file, "r2d2", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; char wbuf[256]; strcpy(wbuf, "beep boop"); lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf); lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "c3po", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; strcpy(wbuf, "we seem to be made to suffer"); lfsr_file_write(&lfs, &file, wbuf, strlen(wbuf)) => strlen(wbuf); lfsr_file_close(&lfs, &file) => 0; // shift the filesystem out-of-phase uint8_t shift_buf[BLOCK_SIZE]; for (lfs_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 lfsr_mount(&lfs, LFS_M_RDWR, CFG) => LFS_ERR_CORRUPT; lfsr_mount(&lfs, LFS_M_RDONLY, CFG) => LFS_ERR_CORRUPT; '''