# Bad-block related tests after = [ 'test_dirs', 'test_files', 'test_fwrite', 'test_stickynotes', 'test_trvs', 'test_gc', 'test_mount', 'test_ck', 'test_compat', ] ## Single-block badblock tests # # first test with every possible single badblock # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_every_btree_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] # maximize lookahead buffer to avoid alloc scans defines.LOOKAHEAD_SIZE = '(BLOCK_COUNT+8-1) / 8' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' in = 'lfs3.c' code = ''' // test all possible bad blocks for (lfs3_size_t i = 0; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test creating a btree lfs3_t lfs3; lfs3_init(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create free lookahead memset(lfs3.lookahead.buffer, 0, CFG->lookahead_size); lfs3.lookahead.window = 2; lfs3.lookahead.off = 0; lfs3.lookahead.size = lfs3_min(8*CFG->lookahead_size, CFG->block_count-2); lfs3_alloc_ckpoint(&lfs3); // create a btree lfs3_btree_t btree; lfs3_btree_init(&btree); // set up a simulation to compare against char *sim = malloc(N); lfs3_size_t sim_size = 0; memset(sim, 0, N); uint32_t prng = SEED; for (lfs3_size_t i = 0; i < N; i++) { // choose a pseudo-random bid lfs3_size_t bid = TEST_PRNG(&prng) % (sim_size+1); // add to btree lfs3_btree_commit(&lfs3, &btree, bid, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_DATA, +1, &(uint8_t){'a'+(i % 26)}, 1))) => 0; // add to sim memmove(&sim[bid+1], &sim[bid], sim_size-bid); sim[bid] = 'a'+(i % 26); sim_size += 1; } // check that btree matches sim printf("expd: ["); bool first = true; for (lfs3_size_t i = 0; i < sim_size; i++) { if (!first) { printf(", "); } first = false; printf("%c", sim[i]); } printf("]\n"); printf("btree: w%d 0x%x.%x\n", btree.r.weight, btree.r.blocks[0], btree.r.trunk); assert(btree.r.weight == sim_size); uint8_t buffer[4]; lfs3_bid_t bid_; lfs3_stag_t tag_; lfs3_size_t weight_; lfs3_data_t data_; for (lfs3_size_t i = 0; i < sim_size; i++) { tag_ = lfs3_btree_lookupnext(&lfs3, &btree, i, &bid_, &weight_, &data_); lfs3_data_read(&lfs3, &data_, buffer, 4) => 1; assert(bid_ == i); assert(tag_ == LFS3_TAG_DATA); assert(weight_ == 1); assert(memcmp(buffer, &sim[i], 1) == 0); } // and no extra elements lfs3_btree_lookupnext(&lfs3, &btree, sim_size, &bid_, &weight_, &data_) => LFS3_ERR_NOENT; // clean up sim free(sim); lfs3_deinit(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with dirs [cases.test_badblocks_every_spam_dir_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test creating directories lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // make this many directories for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); int err = lfs3_mkdir(&lfs3, name); assert(!err || (TEST_PLS && err == LFS3_ERR_EXIST)); } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // check that our mkdir worked for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_open(&lfs3, &dir, name) => 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) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; } } lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with fuzz dirs [cases.test_badblocks_every_spam_dir_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] defines.OPS = '2*N' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test fuzz with dirs lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose a pseudo-random op, either mkdir, remove, or rename uint8_t op = TEST_PRNG(&prng) % 3; if (op == 0 || sim_size == 0) { // choose a pseudo-random number, truncate to 3 hexadecimals lfs3_size_t x = TEST_PRNG(&prng) % N; // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // do nothing } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); sim_size += 1; sim[j] = x; } break; } } // create a directory here char name[256]; sprintf(name, "dir%03x", x); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_EXIST); } else if (op == 1) { // choose a pseudo-random entry to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; // remove this directory char name[256]; sprintf(name, "dir%03x", x); lfs3_remove(&lfs3, name) => 0; } else { // choose a pseudo-random entry to rename, and a pseudo-random // number to rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // already seen and not a noop? if (k < sim_size && sim[k] == y && x != y) { // just delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); sim[k] = y; } break; } } // rename this directory char old_name[256]; sprintf(old_name, "dir%03x", x); char new_name[256]; sprintf(new_name, "dir%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // test that our directories match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; char name2[256]; sprintf(name2, "dir%03x", sim[j]); assert(strcmp(info.name, name2) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; } // clean up sim/lfs3 free(sim); lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with files [cases.test_badblocks_every_spam_file_many] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' 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', '4*BLOCK_SIZE', ] if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 32', ] code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test creating files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create this many files uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "amethyst%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; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our writes worked prng = 42; for (lfs3_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "amethyst%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); // try reading the file, note we reset prng above uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_t file; uint8_t rbuf[SIZE]; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with fuzz files [cases.test_badblocks_every_spam_file_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test fuzz with files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 3; // creating a new file? if (op == 0 || sim_size == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // associate each file with a prng that generates its contents uint32_t wprng = TEST_PRNG(&prng); // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // new prng sim_prngs[j] = wprng; } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); memmove(&sim_prngs[j+1], &sim_prngs[j], (sim_size-j)*sizeof(uint32_t)); sim_size += 1; sim[j] = x; sim_prngs[j] = wprng; } break; } } // create a file here char name[256]; sprintf(name, "amethyst%03x", x); uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0; lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; // deleting a file? } else if (op == 1) { // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; // delete this file char name[256]; sprintf(name, "amethyst%03x", x); lfs3_remove(&lfs3, name) => 0; // renaming a file? } else { // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng sim_prngs[k] = wprng; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); sim[k] = y; sim_prngs[k] = wprng; } break; } } // rename this file char old_name[256]; sprintf(old_name, "amethyst%03x", x); char new_name[256]; sprintf(new_name, "amethyst%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our files match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // check the file contents for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } // clean up sim/lfs3 free(sim); free(sim_prngs); lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with more complex file writes [cases.test_badblocks_every_spam_fwrite_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.OPS = 20 defines.SIZE = [ 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # chunk is more an upper limit here defines.CHUNK = 64 # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', 'CHUNK <= SIZE', # this just saves testing time 'SIZE <= 4*1024*FRAGMENT_SIZE', ] code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test with complex file writes lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create a file lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; // simulate our file in ram uint8_t sim[SIZE]; lfs3_off_t size; uint32_t prng = SEED; if (INIT == 0) { memset(sim, 0, SIZE); size = 0; } else if (INIT == 1) { for (lfs3_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, sim, SIZE) => SIZE; size = SIZE; } else { memset(sim, 0, SIZE); lfs3_file_truncate(&lfs3, &file, SIZE) => 0; size = SIZE; } // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } for (lfs3_size_t i = 0; i < OPS; i++) { // choose a random location lfs3_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs3_size_t chunk = lfs3_min( TEST_PRNG(&prng) % CHUNK, SIZE - off); // update sim for (lfs3_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } if (chunk != 0) { size = lfs3_max(size, off+chunk); } // update file lfs3_file_seek(&lfs3, &file, off, LFS3_SEEK_SET) => off; lfs3_file_write(&lfs3, &file, &sim[off], chunk) => chunk; // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } } lfs3_file_close(&lfs3, &file) => 0; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check our file with stat struct lfs3_info info; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); // and with dir read 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, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // try reading our file lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; // is size correct? lfs3_file_size(&lfs3, &file) => size; // try reading uint8_t rbuf[2*SIZE]; memset(rbuf, 0xaa, 2*SIZE); lfs3_file_read(&lfs3, &file, rbuf, 2*SIZE) => size; // does our file match our simulation? assert(memcmp(rbuf, sim, size) == 0); lfs3_file_close(&lfs3, &file) => 0; } lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with uncreats, zombies, etc [cases.test_badblocks_every_spam_uz_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test with uncreats, zombies, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 5; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = false; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; bool sticky = sim_files[j]->sticky; bool zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky sim_prngs[k] = wprng; sim_isstickys[k] = sticky; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' # badblocks with uncreats, zombies, dirs, etc [cases.test_badblocks_every_spam_uzd_fuzz] defines.BADBLOCK = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test all possible bad blocks for (lfs3_size_t i = 2; i < ((BADBLOCK == -1) ? BLOCK_COUNT : 1); i++) { lfs3_size_t badblock = (BADBLOCK == -1) ? i : BADBLOCK; printf("--- badblock: 0x%x ---\n", badblock); // mark our badblock as bad lfs3_emubd_markbad(CFG, badblock) => 0; // test with uncreats, zombies, dirs, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); bool *sim_isdirs = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 8; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = true; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { if (sim_isdirs[j]) { goto nonsense; } exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = false; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; lfs3_size_t sticky = sim_files[j]->sticky; lfs3_size_t zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); memmove(&sim_isdirs[k], &sim_isdirs[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; bool dir = sim_isdirs[j]; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // type mismatch? if (sim_isdirs[k] != dir) { goto nonsense; } } break; } } // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky/dir sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } // toss a directory into the mix } else if (op == 5) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { goto nonsense; } break; } } // make the directory char name[256]; sprintf(name, "batman%03x", x); lfs3_mkdir(&lfs3, name) => 0; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = 0; sim_isdirs[k] = true; break; } } // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim_isdirs[j]) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => LFS3_ERR_ISDIR; } else { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); free(sim_isdirs); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; // reset badblock lfs3_emubd_markgood(CFG, badblock) => 0; } ''' ## Badblock regions # # Test with a region of badblocks, this chould cause cascading failures, # which can be tricky # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_region_btree_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] # maximize lookahead buffer to avoid alloc scans defines.LOOKAHEAD_SIZE = '(BLOCK_COUNT+8-1) / 8' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' in = 'lfs3.c' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { lfs3_emubd_markbad(CFG, i) => 0; } else { lfs3_emubd_markbad(CFG, i + BLOCK_COUNT/2) => 0; } } // test creating a btree lfs3_t lfs3; lfs3_init(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create free lookahead memset(lfs3.lookahead.buffer, 0, CFG->lookahead_size); lfs3.lookahead.window = 2; lfs3.lookahead.off = 0; lfs3.lookahead.size = lfs3_min(8*CFG->lookahead_size, CFG->block_count-2); lfs3_alloc_ckpoint(&lfs3); // create a btree lfs3_btree_t btree; lfs3_btree_init(&btree); // set up a simulation to compare against char *sim = malloc(N); lfs3_size_t sim_size = 0; memset(sim, 0, N); uint32_t prng = SEED; for (lfs3_size_t i = 0; i < N; i++) { // choose a pseudo-random bid lfs3_size_t bid = TEST_PRNG(&prng) % (sim_size+1); // add to btree lfs3_btree_commit(&lfs3, &btree, bid, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_DATA, +1, &(uint8_t){'a'+(i % 26)}, 1))) => 0; // add to sim memmove(&sim[bid+1], &sim[bid], sim_size-bid); sim[bid] = 'a'+(i % 26); sim_size += 1; } // check that btree matches sim printf("expd: ["); bool first = true; for (lfs3_size_t i = 0; i < sim_size; i++) { if (!first) { printf(", "); } first = false; printf("%c", sim[i]); } printf("]\n"); printf("btree: w%d 0x%x.%x\n", btree.r.weight, btree.r.blocks[0], btree.r.trunk); assert(btree.r.weight == sim_size); uint8_t buffer[4]; lfs3_bid_t bid_; lfs3_stag_t tag_; lfs3_size_t weight_; lfs3_data_t data_; for (lfs3_size_t i = 0; i < sim_size; i++) { tag_ = lfs3_btree_lookupnext(&lfs3, &btree, i, &bid_, &weight_, &data_); lfs3_data_read(&lfs3, &data_, buffer, 4) => 1; assert(bid_ == i); assert(tag_ == LFS3_TAG_DATA); assert(weight_ == 1); assert(memcmp(buffer, &sim[i], 1) == 0); } // and no extra elements lfs3_btree_lookupnext(&lfs3, &btree, sim_size, &bid_, &weight_, &data_) => LFS3_ERR_NOENT; // clean up sim free(sim); lfs3_deinit(&lfs3) => 0; ''' # badblocks with dirs [cases.test_badblocks_region_spam_dir_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test creating directories lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // make this many directories for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); int err = lfs3_mkdir(&lfs3, name); assert(!err || (TEST_PLS && err == LFS3_ERR_EXIST)); } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // check that our mkdir worked for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_open(&lfs3, &dir, name) => 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) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; } } lfs3_unmount(&lfs3) => 0; ''' # badblocks with fuzz dirs [cases.test_badblocks_region_spam_dir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] defines.OPS = '2*N' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test fuzz with dirs lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose a pseudo-random op, either mkdir, remove, or rename uint8_t op = TEST_PRNG(&prng) % 3; if (op == 0 || sim_size == 0) { // choose a pseudo-random number, truncate to 3 hexadecimals lfs3_size_t x = TEST_PRNG(&prng) % N; // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // do nothing } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); sim_size += 1; sim[j] = x; } break; } } // create a directory here char name[256]; sprintf(name, "dir%03x", x); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_EXIST); } else if (op == 1) { // choose a pseudo-random entry to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; // remove this directory char name[256]; sprintf(name, "dir%03x", x); lfs3_remove(&lfs3, name) => 0; } else { // choose a pseudo-random entry to rename, and a pseudo-random // number to rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // already seen and not a noop? if (k < sim_size && sim[k] == y && x != y) { // just delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); sim[k] = y; } break; } } // rename this directory char old_name[256]; sprintf(old_name, "dir%03x", x); char new_name[256]; sprintf(new_name, "dir%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // test that our directories match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; char name2[256]; sprintf(name2, "dir%03x", sim[j]); assert(strcmp(info.name, name2) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; } // clean up sim/lfs3 free(sim); lfs3_unmount(&lfs3) => 0; ''' # badblocks with files [cases.test_badblocks_region_spam_file_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] 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', '4*BLOCK_SIZE', ] if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 32', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test creating files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create this many files uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "amethyst%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; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our writes worked prng = 42; for (lfs3_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "amethyst%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); // try reading the file, note we reset prng above uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_t file; uint8_t rbuf[SIZE]; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } lfs3_unmount(&lfs3) => 0; ''' # badblocks with fuzz files [cases.test_badblocks_region_spam_file_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test fuzz with files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 3; // creating a new file? if (op == 0 || sim_size == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // associate each file with a prng that generates its contents uint32_t wprng = TEST_PRNG(&prng); // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // new prng sim_prngs[j] = wprng; } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); memmove(&sim_prngs[j+1], &sim_prngs[j], (sim_size-j)*sizeof(uint32_t)); sim_size += 1; sim[j] = x; sim_prngs[j] = wprng; } break; } } // create a file here char name[256]; sprintf(name, "amethyst%03x", x); uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0; lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; // deleting a file? } else if (op == 1) { // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; // delete this file char name[256]; sprintf(name, "amethyst%03x", x); lfs3_remove(&lfs3, name) => 0; // renaming a file? } else { // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng sim_prngs[k] = wprng; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); sim[k] = y; sim_prngs[k] = wprng; } break; } } // rename this file char old_name[256]; sprintf(old_name, "amethyst%03x", x); char new_name[256]; sprintf(new_name, "amethyst%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our files match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // check the file contents for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } // clean up sim/lfs3 free(sim); free(sim_prngs); lfs3_unmount(&lfs3) => 0; ''' # badblocks with more complex file writes [cases.test_badblocks_region_spam_fwrite_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.OPS = 20 defines.SIZE = [ 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # chunk is more an upper limit here defines.CHUNK = [32, 8, 1] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', 'CHUNK <= SIZE', # this just saves testing time 'SIZE <= 4*1024*FRAGMENT_SIZE', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test with complex file writes lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create a file lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; // simulate our file in ram uint8_t sim[SIZE]; lfs3_off_t size; uint32_t prng = SEED; if (INIT == 0) { memset(sim, 0, SIZE); size = 0; } else if (INIT == 1) { for (lfs3_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, sim, SIZE) => SIZE; size = SIZE; } else { memset(sim, 0, SIZE); lfs3_file_truncate(&lfs3, &file, SIZE) => 0; size = SIZE; } // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } for (lfs3_size_t i = 0; i < OPS; i++) { // choose a random location lfs3_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs3_size_t chunk = lfs3_min( TEST_PRNG(&prng) % CHUNK, SIZE - off); // update sim for (lfs3_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } if (chunk != 0) { size = lfs3_max(size, off+chunk); } // update file lfs3_file_seek(&lfs3, &file, off, LFS3_SEEK_SET) => off; lfs3_file_write(&lfs3, &file, &sim[off], chunk) => chunk; // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } } lfs3_file_close(&lfs3, &file) => 0; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check our file with stat struct lfs3_info info; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); // and with dir read 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, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // try reading our file lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; // is size correct? lfs3_file_size(&lfs3, &file) => size; // try reading uint8_t rbuf[2*SIZE]; memset(rbuf, 0xaa, 2*SIZE); lfs3_file_read(&lfs3, &file, rbuf, 2*SIZE) => size; // does our file match our simulation? assert(memcmp(rbuf, sim, size) == 0); lfs3_file_close(&lfs3, &file) => 0; } lfs3_unmount(&lfs3) => 0; ''' # badblocks with uncreats, zombies, etc [cases.test_badblocks_region_spam_uz_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test with uncreats, zombies, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 5; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = false; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; bool sticky = sim_files[j]->sticky; bool zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky sim_prngs[k] = wprng; sim_isstickys[k] = sticky; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; ''' # badblocks with uncreats, zombies, dirs, etc [cases.test_badblocks_region_spam_uzd_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16' ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (i >= 2) { lfs3_emubd_markbad(CFG, i) => 0; } } else { if (i+BLOCK_COUNT/2 >= 2) { lfs3_emubd_markbad(CFG, i+BLOCK_COUNT/2) => 0; } } } // test with uncreats, zombies, dirs, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); bool *sim_isdirs = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 8; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = true; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { if (sim_isdirs[j]) { goto nonsense; } exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = false; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; lfs3_size_t sticky = sim_files[j]->sticky; lfs3_size_t zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); memmove(&sim_isdirs[k], &sim_isdirs[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; bool dir = sim_isdirs[j]; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // type mismatch? if (sim_isdirs[k] != dir) { goto nonsense; } } break; } } // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky/dir sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } // toss a directory into the mix } else if (op == 5) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { goto nonsense; } break; } } // make the directory char name[256]; sprintf(name, "batman%03x", x); lfs3_mkdir(&lfs3, name) => 0; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = 0; sim_isdirs[k] = true; break; } } // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim_isdirs[j]) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => LFS3_ERR_ISDIR; } else { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); free(sim_isdirs); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; ''' ## Alternating badblocks # # Test alternating badblocks, this can be difficult for pair allocations # B-tree's ridiculous branching factor is great for performance, but it makes # them a bit of a pain to test, here we test them explicitly [cases.test_badblocks_alternating_btree_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] # maximize lookahead buffer to avoid alloc scans defines.LOOKAHEAD_SIZE = '(BLOCK_COUNT+8-1) / 8' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' in = 'lfs3.c' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } else { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } // test creating a btree lfs3_t lfs3; lfs3_init(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create free lookahead memset(lfs3.lookahead.buffer, 0, CFG->lookahead_size); lfs3.lookahead.window = 2; lfs3.lookahead.off = 0; lfs3.lookahead.size = lfs3_min(8*CFG->lookahead_size, CFG->block_count-2); lfs3_alloc_ckpoint(&lfs3); // create a btree lfs3_btree_t btree; lfs3_btree_init(&btree); // set up a simulation to compare against char *sim = malloc(N); lfs3_size_t sim_size = 0; memset(sim, 0, N); uint32_t prng = SEED; for (lfs3_size_t i = 0; i < N; i++) { // choose a pseudo-random bid lfs3_size_t bid = TEST_PRNG(&prng) % (sim_size+1); // add to btree lfs3_btree_commit(&lfs3, &btree, bid, LFS3_RATTRS( LFS3_RATTR_BUF( LFS3_TAG_DATA, +1, &(uint8_t){'a'+(i % 26)}, 1))) => 0; // add to sim memmove(&sim[bid+1], &sim[bid], sim_size-bid); sim[bid] = 'a'+(i % 26); sim_size += 1; } // check that btree matches sim printf("expd: ["); bool first = true; for (lfs3_size_t i = 0; i < sim_size; i++) { if (!first) { printf(", "); } first = false; printf("%c", sim[i]); } printf("]\n"); printf("btree: w%d 0x%x.%x\n", btree.r.weight, btree.r.blocks[0], btree.r.trunk); assert(btree.r.weight == sim_size); uint8_t buffer[4]; lfs3_bid_t bid_; lfs3_stag_t tag_; lfs3_size_t weight_; lfs3_data_t data_; for (lfs3_size_t i = 0; i < sim_size; i++) { tag_ = lfs3_btree_lookupnext(&lfs3, &btree, i, &bid_, &weight_, &data_); lfs3_data_read(&lfs3, &data_, buffer, 4) => 1; assert(bid_ == i); assert(tag_ == LFS3_TAG_DATA); assert(weight_ == 1); assert(memcmp(buffer, &sim[i], 1) == 0); } // and no extra elements lfs3_btree_lookupnext(&lfs3, &btree, sim_size, &bid_, &weight_, &data_) => LFS3_ERR_NOENT; // clean up sim free(sim); lfs3_deinit(&lfs3) => 0; ''' # badblocks with dirs [cases.test_badblocks_alternating_spam_dir_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test creating directories lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // make this many directories for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); int err = lfs3_mkdir(&lfs3, name); assert(!err || (TEST_PLS && err == LFS3_ERR_EXIST)); } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // check that our mkdir worked for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "dir%03x", i); lfs3_dir_open(&lfs3, &dir, name) => 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) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; } } lfs3_unmount(&lfs3) => 0; ''' # badblocks with fuzz dirs [cases.test_badblocks_alternating_spam_dir_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] defines.OPS = '2*N' defines.SEED = 42 fuzz = 'SEED' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test fuzz with dirs lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose a pseudo-random op, either mkdir, remove, or rename uint8_t op = TEST_PRNG(&prng) % 3; if (op == 0 || sim_size == 0) { // choose a pseudo-random number, truncate to 3 hexadecimals lfs3_size_t x = TEST_PRNG(&prng) % N; // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // do nothing } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); sim_size += 1; sim[j] = x; } break; } } // create a directory here char name[256]; sprintf(name, "dir%03x", x); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_EXIST); } else if (op == 1) { // choose a pseudo-random entry to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; // remove this directory char name[256]; sprintf(name, "dir%03x", x); lfs3_remove(&lfs3, name) => 0; } else { // choose a pseudo-random entry to rename, and a pseudo-random // number to rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // already seen and not a noop? if (k < sim_size && sim[k] == y && x != y) { // just delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); sim[k] = y; } break; } } // rename this directory char old_name[256]; sprintf(old_name, "dir%03x", x); char new_name[256]; sprintf(new_name, "dir%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // grm should be zero here assert(lfs3.grm_p[0] == 0); // test that our directories match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; char name2[256]; sprintf(name2, "dir%03x", sim[j]); assert(strcmp(info.name, name2) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "dir%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 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; } // clean up sim/lfs3 free(sim); lfs3_unmount(&lfs3) => 0; ''' # badblocks with files [cases.test_badblocks_alternating_spam_file_many] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] 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', '4*BLOCK_SIZE', ] if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 32', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test creating files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create this many files uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "amethyst%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; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our writes worked prng = 42; for (lfs3_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "amethyst%03x", i); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); // try reading the file, note we reset prng above uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_t file; uint8_t rbuf[SIZE]; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } lfs3_unmount(&lfs3) => 0; ''' # badblocks with fuzz files [cases.test_badblocks_alternating_spam_file_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test fuzz with files lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); lfs3_size_t sim_size = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 3; // creating a new file? if (op == 0 || sim_size == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // associate each file with a prng that generates its contents uint32_t wprng = TEST_PRNG(&prng); // insert into our sim for (lfs3_size_t j = 0;; j++) { if (j >= sim_size || sim[j] >= x) { // already seen? if (j < sim_size && sim[j] == x) { // new prng sim_prngs[j] = wprng; } else { // insert memmove(&sim[j+1], &sim[j], (sim_size-j)*sizeof(lfs3_size_t)); memmove(&sim_prngs[j+1], &sim_prngs[j], (sim_size-j)*sizeof(uint32_t)); sim_size += 1; sim[j] = x; sim_prngs[j] = wprng; } break; } } // create a file here char name[256]; sprintf(name, "amethyst%03x", x); uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC) => 0; lfs3_file_write(&lfs3, &file, wbuf, SIZE) => SIZE; lfs3_file_close(&lfs3, &file) => 0; // deleting a file? } else if (op == 1) { // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; // delete this file char name[256]; sprintf(name, "amethyst%03x", x); lfs3_remove(&lfs3, name) => 0; // renaming a file? } else { // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng sim_prngs[k] = wprng; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); sim[k] = y; sim_prngs[k] = wprng; } break; } } // rename this file char old_name[256]; sprintf(old_name, "amethyst%03x", x); char new_name[256]; sprintf(new_name, "amethyst%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; } } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check that our files match our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // check the file contents for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "amethyst%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfs3_file_close(&lfs3, &file) => 0; } } // clean up sim/lfs3 free(sim); free(sim_prngs); lfs3_unmount(&lfs3) => 0; ''' # badblocks with more complex file writes [cases.test_badblocks_alternating_spam_fwrite_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.OPS = 20 defines.SIZE = [ 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # chunk is more an upper limit here defines.CHUNK = [32, 8, 1] # INIT=0 => no init # INIT=1 => fill with data # INIT=2 => truncate to size defines.INIT = [0, 1, 2] defines.SYNC = [false, true] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', 'CHUNK <= SIZE', # this just saves testing time 'SIZE <= 4*1024*FRAGMENT_SIZE', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test with complex file writes lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // create a file lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; // simulate our file in ram uint8_t sim[SIZE]; lfs3_off_t size; uint32_t prng = SEED; if (INIT == 0) { memset(sim, 0, SIZE); size = 0; } else if (INIT == 1) { for (lfs3_size_t i = 0; i < SIZE; i++) { sim[i] = 'a' + (TEST_PRNG(&prng) % 26); } lfs3_file_write(&lfs3, &file, sim, SIZE) => SIZE; size = SIZE; } else { memset(sim, 0, SIZE); lfs3_file_truncate(&lfs3, &file, SIZE) => 0; size = SIZE; } // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } for (lfs3_size_t i = 0; i < OPS; i++) { // choose a random location lfs3_off_t off = TEST_PRNG(&prng) % SIZE; // and a random size, up to the chunk size lfs3_size_t chunk = lfs3_min( (TEST_PRNG(&prng) % (CHUNK+1-1)) + 1, SIZE - off); // update sim for (lfs3_size_t j = 0; j < chunk; j++) { sim[off+j] = 'a' + (TEST_PRNG(&prng) % 26); } size = lfs3_max(size, off+chunk); // update file lfs3_file_seek(&lfs3, &file, off, LFS3_SEEK_SET) => off; lfs3_file_write(&lfs3, &file, &sim[off], chunk) => chunk; // sync? if (SYNC) { lfs3_file_sync(&lfs3, &file) => 0; } } lfs3_file_close(&lfs3, &file) => 0; for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; } // check our file with stat struct lfs3_info info; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); // and with dir read 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, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == size); lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; // try reading our file lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; // is size correct? lfs3_file_size(&lfs3, &file) => size; // try reading uint8_t rbuf[2*SIZE]; memset(rbuf, 0xaa, 2*SIZE); lfs3_file_read(&lfs3, &file, rbuf, 2*SIZE) => size; // does our file match our simulation? assert(memcmp(rbuf, sim, size) == 0); lfs3_file_close(&lfs3, &file) => 0; } lfs3_unmount(&lfs3) => 0; ''' # badblocks with uncreats, zombies, etc [cases.test_badblocks_alternating_spam_uz_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test with uncreats, zombies, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 5; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = false; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; bool sticky = sim_files[j]->sticky; bool zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky sim_prngs[k] = wprng; sim_isstickys[k] = sticky; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; ''' # badblocks with uncreats, zombies, dirs, etc [cases.test_badblocks_alternating_spam_uzd_fuzz] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' defines.MIRROR = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 42 fuzz = 'SEED' if = [ 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)', '(SIZE*N)/BLOCK_SIZE <= 16', ] code = ''' // test a large region of bad blocks for (lfs3_size_t i = 0; i < BLOCK_COUNT/2; i++) { // mark our badblock as bad if (!MIRROR) { if (2*i+0 >= 2) { lfs3_emubd_markbad(CFG, 2*i+0) => 0; } } else { if (2*i+1 >= 2) { lfs3_emubd_markbad(CFG, 2*i+1) => 0; } } } // test with uncreats, zombies, dirs, etc lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_F_CKPROGS, -1) : 0), CFG) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; // set up a simulation to compare against lfs3_size_t *sim = malloc(N*sizeof(lfs3_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isstickys = malloc(N*sizeof(bool)); bool *sim_isdirs = malloc(N*sizeof(bool)); lfs3_size_t sim_size = 0; typedef struct sim_file { lfs3_size_t x; bool sticky; bool zombie; uint32_t prng; lfs3_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs3_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs3_size_t i = 0; i < OPS; i++) { nonsense:; // choose which operation to do uint8_t op = TEST_PRNG(&prng) % 8; // open a new file? if (op == 0) { if (sim_file_count >= N) { goto nonsense; } // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; // already exists? bool exist = true; uint32_t wprng = 0; bool sticky = true; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { if (sim_isdirs[j]) { goto nonsense; } exist = true; wprng = sim_prngs[j]; sticky = sim_isstickys[j]; break; } } // choose a random seed if we don't exist if (!exist) { wprng = TEST_PRNG(&prng); sticky = true; } lfs3_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT) => 0; // write some initial data if we don't exist if (!exist || sticky) { uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // open in our sim sim_files[j]->x = x; sim_files[j]->sticky = sticky; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { // new prng sim_prngs[k] = wprng; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = false; } break; } } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // write to the file lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfs3_file_sync(&lfs3, &sim_files[j]->file) => 0; // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // update in our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // new prng sim_prngs[k] = wprng; // no longer sticky sim_isstickys[k] = false; break; } } // update related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { // new prng sim_files[k]->prng = wprng; // no longer sticky sim_files[k]->sticky = false; } } } // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs3_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs3_size_t x = sim_files[j]->x; lfs3_size_t sticky = sim_files[j]->sticky; lfs3_size_t zombie = sim_files[j]->zombie; // this doesn't really test anything, but if we don't close // files eventually everything will end up zombies // close the file without affected disk lfs3_file_desync(&lfs3, &sim_files[j]->file) => 0; lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; // clobber closed files to try to catch lingering references memset(&sim_files[j]->file, 0xcc, sizeof(lfs3_file_t)); // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // update our sim if (sticky && !zombie) { // orphaned? bool orphan = true; for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { orphan = false; } } // if we were never synced, delete from sim if (orphan) { for (lfs3_size_t k = 0;; k++) { if (sim[k] == x) { memmove(&sim[k], &sim[k+1], (sim_size-(k+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[k], &sim_prngs[k+1], (sim_size-(k+1))*sizeof(uint32_t)); memmove(&sim_isstickys[k], &sim_isstickys[k+1], (sim_size-(k+1))*sizeof(bool)); memmove(&sim_isdirs[k], &sim_isdirs[k+1], (sim_size-(k+1))*sizeof(bool)); sim_size -= 1; break; } } } } // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; // delete this file char name[256]; sprintf(name, "batman%03x", x); lfs3_remove(&lfs3, name) => 0; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // rename a file? } else if (op == 4) { if (sim_size == 0) { goto nonsense; } // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng) % sim_size; lfs3_size_t x = sim[j]; lfs3_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool sticky = sim_isstickys[j]; bool dir = sim_isdirs[j]; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // type mismatch? if (sim_isdirs[k] != dir) { goto nonsense; } } break; } } // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfs3_rename(&lfs3, old_name, new_name) => 0; // update our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= y) { // renaming and replacing if (k < sim_size && sim[k] == y && x != y) { // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); sim_size -= 1; if (k > j) { k -= 1; } // update the prng/sticky/dir sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); memmove(&sim_isstickys[j], &sim_isstickys[j+1], (sim_size-(j+1))*sizeof(bool)); memmove(&sim_isdirs[j], &sim_isdirs[j+1], (sim_size-(j+1))*sizeof(bool)); if (k > j) { k -= 1; } // then insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isstickys[k] = sticky; sim_isdirs[k] = dir; } break; } } // update any related sim files for (lfs3_size_t k = 0; k < sim_file_count; k++) { // move source files if (sim_files[k]->x == x) { sim_files[k]->x = y; // mark target files as zombied } else if (sim_files[k]->x == y) { sim_files[k]->zombie = true; } } // toss a directory into the mix } else if (op == 5) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng) % N; for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { goto nonsense; } break; } } // make the directory char name[256]; sprintf(name, "batman%03x", x); lfs3_mkdir(&lfs3, name) => 0; // insert into our sim for (lfs3_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs3_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isstickys[k+1], &sim_isstickys[k], (sim_size-k)*sizeof(bool)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = 0; sim_isdirs[k] = true; break; } } // mark any related sim files as zombied for (lfs3_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } } } // check that disk matches our simulation for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs3_info info; lfs3_stat(&lfs3, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "/") => 0; struct lfs3_info info; 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); for (lfs3_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_dir_read(&lfs3, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); } else if (sim_isstickys[j]) { assert(info.type == LFS3_TYPE_STICKYNOTE); assert(info.size == 0); } else { assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); } } lfs3_dir_read(&lfs3, &dir, &info) => LFS3_ERR_NOENT; lfs3_dir_close(&lfs3, &dir) => 0; for (lfs3_size_t j = 0; j < sim_size; j++) { if (sim_isdirs[j]) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => LFS3_ERR_ISDIR; } else { char name[256]; sprintf(name, "batman%03x", sim[j]); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; if (sim_isstickys[j]) { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => 0; } else { lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } lfs3_file_close(&lfs3, &file) => 0; } } // check that our file handles match our simulation for (lfs3_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfs3_file_rewind(&lfs3, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs3 free(sim); free(sim_prngs); free(sim_isstickys); free(sim_isdirs); for (lfs3_size_t j = 0; j < sim_file_count; j++) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfs3_unmount(&lfs3) => 0; ''' ## other corner cases # test formatting with 0 or 1 bad, this should just error [cases.test_badblocks_mrootanchor_format] defines.BADBLOCKS = [0x1, 0x2, 0x3] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] code = ''' if (BADBLOCKS & 0x1) { lfs3_emubd_markbad(CFG, 0) => 0; } if (BADBLOCKS & 0x2) { lfs3_emubd_markbad(CFG, 1) => 0; } lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => LFS3_ERR_CORRUPT; ''' # test blocks 0 or 1 going bad, this should just error [cases.test_badblocks_mrootanchor_wear] defines.BADBLOCKS = [0x1, 0x2] defines.BADBLOCK_BEHAVIOR = [ 'LFS3_EMUBD_BADBLOCK_PROGERROR', 'LFS3_EMUBD_BADBLOCK_ERASEERROR', 'LFS3_EMUBD_BADBLOCK_READERROR', 'LFS3_EMUBD_BADBLOCK_PROGNOOP', 'LFS3_EMUBD_BADBLOCK_ERASENOOP', ] # we need prog checking to detect read errors defines.CKPROGS = 'BADBLOCK_BEHAVIOR >= LFS3_EMUBD_BADBLOCK_READERROR' if = 'LFS3_IFDEF_CKPROGS(true, !CKPROGS)' code = ''' lfs3_t lfs3; lfs3_format(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; if (BADBLOCKS & 0x1) { lfs3_emubd_markbad(CFG, 0) => 0; } if (BADBLOCKS & 0x2) { lfs3_emubd_markbad(CFG, 1) => 0; } lfs3_mount(&lfs3, LFS3_M_RDWR | ((CKPROGS) ? LFS3_IFDEF_CKPROGS(LFS3_M_CKPROGS, -1) : 0), CFG) => 0; for (lfs3_size_t i = 0;; i++) { // this should eventually fail assert(i <= BLOCK_COUNT); int err = lfs3_mkdir(&lfs3, "hi"); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { break; } err = lfs3_remove(&lfs3, "hi"); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { break; } } lfs3_unmount(&lfs3) => 0; '''