# Test variable block counts and grow related things after = [ 'test_dirs', 'test_files', 'test_fwrite', 'test_stickynotes', 'test_alloc', 'test_mount', ] # test we can mount a filesystem with fewer blocks [cases.test_grow_mount_smaller] defines.SMALLER_BLOCK_COUNT = [ 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '2', ] defines.BIGGER_BLOCK_COUNT = [ 'BLOCK_COUNT', 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', ] if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' code = ''' // create a smaller fs struct lfs3_cfg cfg = *CFG; cfg.block_count = SMALLER_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // do some work lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf[1024] = "Hello World!"; lfs3_size_t wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; 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 == wsize); uint8_t rbuf[1024]; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ////// // try to mount with a bigger block count cfg = *CFG; cfg.block_count = BIGGER_BLOCK_COUNT; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); // do some work lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_TRUNC) => 0; strcpy((char*)wbuf, "Chris was here!"); wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; // stays after a mount? lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ''' # test we _can't_ mount a filesystem with more blocks [cases.test_grow_mount_bigger] defines.SMALLER_BLOCK_COUNT = [ 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '2', ] defines.BIGGER_BLOCK_COUNT = [ 'BLOCK_COUNT', 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', ] if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' code = ''' // create a bigger fs struct lfs3_cfg cfg = *CFG; cfg.block_count = BIGGER_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // do some work lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf[1024] = "Hello World!"; lfs3_size_t wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; 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 == wsize); uint8_t rbuf[1024]; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ////// // try to mount with a smaller block count cfg = *CFG; cfg.block_count = SMALLER_BLOCK_COUNT; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => LFS3_ERR_NOTSUP; ''' # test we can grow a filesystem [cases.test_grow_grow] defines.SMALLER_BLOCK_COUNT = [ 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '2', ] defines.BIGGER_BLOCK_COUNT = [ 'BLOCK_COUNT', 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', ] if = 'BIGGER_BLOCK_COUNT > SMALLER_BLOCK_COUNT' code = ''' // create a smaller fs struct lfs3_cfg cfg = *CFG; cfg.block_count = SMALLER_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // do some work lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf[1024] = "Hello World!"; lfs3_size_t wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; 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 == wsize); uint8_t rbuf[1024]; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ////// // try to grow our filesystem cfg = *CFG; cfg.block_count = BIGGER_BLOCK_COUNT; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; lfs3_fs_grow(&lfs3, BIGGER_BLOCK_COUNT) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); // do some work lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_TRUNC) => 0; strcpy((char*)wbuf, "Chris was here!"); wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; // stays after a mount? lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == BIGGER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ''' # growing to the same size should do nothing [cases.test_grow_noop] defines.SMALLER_BLOCK_COUNT = [ 'BLOCK_COUNT', 'BLOCK_COUNT-1', 'BLOCK_COUNT/2', 'BLOCK_COUNT/4', '2', ] code = ''' // create a smaller fs struct lfs3_cfg cfg = *CFG; cfg.block_count = SMALLER_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // do some work lfs3_file_t file; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL) => 0; uint8_t wbuf[1024] = "Hello World!"; lfs3_size_t wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; 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 == wsize); uint8_t rbuf[1024]; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ////// // try to grow to same size cfg = *CFG; cfg.block_count = SMALLER_BLOCK_COUNT; lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; lfs3_fs_grow(&lfs3, SMALLER_BLOCK_COUNT) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); // do some work lfs3_file_open(&lfs3, &file, "hello", LFS3_O_WRONLY | LFS3_O_TRUNC) => 0; strcpy((char*)wbuf, "Chris was here!"); wsize = strlen((char*)wbuf); lfs3_file_write(&lfs3, &file, wbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; // stays after a mount? lfs3_mount(&lfs3, LFS3_M_RDWR, &cfg) => 0; // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == SMALLER_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // file still exists? lfs3_stat(&lfs3, "hello", &info) => 0; assert(strcmp(info.name, "hello") == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == wsize); lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; lfs3_file_read(&lfs3, &file, rbuf, wsize) => wsize; lfs3_file_close(&lfs3, &file) => 0; assert(memcmp(rbuf, wbuf, wsize) == 0); lfs3_unmount(&lfs3) => 0; ''' # These tests try various fuzz tests while incrementally growing the # filesystem. When encountering LFS3_ERR_NOSPC, the filesystem is grown by # one block. Hopefully this will catch most grow-related bugs. # [cases.test_grow_incr_spam_dir_many] defines.INIT_BLOCK_COUNT = 2 defines.REMOUNT = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // make this many directories for (lfs3_size_t i = 0; i < N; i++) { again:; char name[256]; sprintf(name, "dir%03x", i); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // remount? if (REMOUNT) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, 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; ''' [cases.test_grow_incr_spam_dir_fuzz] defines.INIT_BLOCK_COUNT = 2 defines.REMOUNT = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256] defines.OPS = 1024 defines.SEED = 'range(10)' fuzz = 'SEED' code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // 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++) { again:; uint32_t prng_ = prng; // 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; // create a directory here char name[256]; sprintf(name, "dir%03x", x); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; } } } 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]; // remove this directory char name[256]; sprintf(name, "dir%03x", x); int err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs3_size_t)); sim_size -= 1; } 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; // rename this directory char old_name[256]; sprintf(old_name, "dir%03x", x); char new_name[256]; sprintf(new_name, "dir%03x", y); int err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } 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; } } } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; int err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // remount? if (REMOUNT) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, 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; ''' [cases.test_grow_incr_spam_file_many] defines.INIT_BLOCK_COUNT = 2 defines.REMOUNT = [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 = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); uint32_t prng = 42; for (lfs3_size_t i = 0; i < N; i++) { again:; uint32_t prng_ = prng; // create this many files 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; int err = lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_EXCL); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // remount? if (REMOUNT) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, 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; ''' [cases.test_grow_incr_spam_file_fuzz] defines.INIT_BLOCK_COUNT = 2 defines.REMOUNT = [false, true] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = 1024 defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(10)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // 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++) { again:; uint32_t prng_ = prng; // 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_); // create a file here char name[256]; sprintf(name, "amethyst%03x", x); uint8_t wbuf[SIZE]; uint32_t wprng_ = wprng; for (lfs3_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng_) % 26); } lfs3_file_t file; int err = lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; } } // 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 this file char name[256]; sprintf(name, "amethyst%03x", x); int err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; // 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]; // rename this file char old_name[256]; sprintf(old_name, "amethyst%03x", x); char new_name[256]; sprintf(new_name, "amethyst%03x", y); int err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; } } } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; int err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // remount? if (REMOUNT) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, 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; ''' [cases.test_grow_incr_spam_uz_fuzz] defines.INIT_BLOCK_COUNT = 2 defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = 1024 defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(10)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // 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++) { again:; uint32_t prng_ = prng; 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); int err = lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { free(sim_files[j]); goto grow; } // 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_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE); LFS3_ASSERT(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; free(sim_files[j]); goto grow; } } // 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_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { goto grow; } int err = lfs3_file_sync(&lfs3, &sim_files[j]->file); assert(err == 0 || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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); int err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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); int err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; } } } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; int err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } // 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; ''' [cases.test_grow_incr_spam_uzd_fuzz] defines.INIT_BLOCK_COUNT = 2 defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = 1024 defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(10)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_t lfs3; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); // 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++) { again:; uint32_t prng_ = prng; 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); int err = lfs3_file_open(&lfs3, &sim_files[j]->file, name, LFS3_O_RDWR | LFS3_O_CREAT); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { free(sim_files[j]); goto grow; } // 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_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { free(sim_files[j]); lfs3_file_close(&lfs3, &sim_files[j]->file) => 0; goto grow; } } // 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_ssize_t d = lfs3_file_write(&lfs3, &sim_files[j]->file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { goto grow; } int err = lfs3_file_sync(&lfs3, &sim_files[j]->file); assert(err == 0 || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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); int err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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); int err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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); int err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // 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; } } } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; int err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } // 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; ''' # A general purpose powerloss fuzz test # # # Under powerloss, we can't really keep track of a sim reliably/ # efficiently, instead just do random operations, store a counter in a # special file so we know how much progress has been made, and hope for # the best. Most likely an internal assert will trigger if anything goes # wrong. # [cases.test_grow_incr_spam_f_pl_fuzz] defines.INIT_BLOCK_COUNT = 2 defines.N = [1, 2, 4, 8, 16, 32, 64] defines.OPS = 256 defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(10)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' reentrant = true code = ''' // format once per test lfs3_t lfs3; int err = lfs3_mount(&lfs3, LFS3_M_RDWR, CFG); if (err) { // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); } // keep some test state on disk to survive powerloss typedef struct fuzz_state { lfs3_size_t i; uint32_t prng; } fuzz_state_t; fuzz_state_t state = {.i = 0, .prng = SEED}; lfs3_file_t state_file; err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_RDONLY); assert(!err || err == LFS3_ERR_NOENT); if (!err) { lfs3_file_read(&lfs3, &state_file, &state, sizeof(state)) => sizeof(state); lfs3_file_close(&lfs3, &state_file) => 0; } uint32_t prng = state.prng; for (lfs3_size_t i = state.i; i < OPS; i++) { again:; uint32_t prng_ = prng; // choose which operation to do uint8_t op = TEST_PRNG(&prng_) % 3; // keep test files in a separate directory err = lfs3_mkdir(&lfs3, "test"); assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // how many files do we have? lfs3_size_t count = 0; lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "test") => 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); while (true) { err = lfs3_dir_read(&lfs3, &dir, &info); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info.name) == strlen("amethyst...")); assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); count++; } lfs3_dir_close(&lfs3, &dir) => 0; // creating a new file? if (op == 0 || count == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng_) % N; uint32_t wprng = TEST_PRNG(&prng_); // create a file here char name[256]; sprintf(name, "test/amethyst%03x", x); uint8_t wbuf[SIZE]; uint8_t ck = 0; for (lfs3_size_t j = 0; j < SIZE-1; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); ck = (ck + (wbuf[j] - 'a')) % 26; } // make the sum equal to 'a' mod 26 if (SIZE > 0) { wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); } lfs3_file_t file; err = lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // deleting a file? } else if (op == 1) { // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng_) % count; // find the file lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // delete this file char name[256]; assert(strlen(info.name) == strlen("amethyst...")); sprintf(name, "test/%s", info.name); err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // renaming a file? } else { // choose a random file to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng_) % count; lfs3_size_t y = TEST_PRNG(&prng_) % N; // find the file lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // rename this file char old_name[256]; assert(strlen(info.name) == strlen("amethyst...")); sprintf(old_name, "test/%s", info.name); char new_name[256]; sprintf(new_name, "test/amethyst%03x", y); err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } } // update our state file state.i = i; state.prng = prng_; err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &state_file, &state, sizeof(state)); assert(d == sizeof(state) || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &state_file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &state_file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // check that things look more-or-less ok lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "test") => 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); while (true) { int err = lfs3_dir_read(&lfs3, &dir, &info); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info.name) == strlen("amethyst...")); assert(memcmp(info.name, "amethyst", strlen("amethyst")) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); // at least try to read the files char name[256]; sprintf(name, "test/%s", info.name); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; // all data should be lowercase ascii for (lfs3_size_t j = 0; j < SIZE; j++) { assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); } // sum should be equal to 'a' mod 26 uint8_t ck = 0; for (lfs3_size_t j = 0; j < SIZE; j++) { ck = (ck + (rbuf[j] - 'a')) % 26; } assert(ck == 0); lfs3_file_close(&lfs3, &file) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; } lfs3_unmount(&lfs3) => 0; ''' # A general purpose powerloss fuzz test, with directories! # # Under powerloss, we can't really keep track of a sim reliably/ # efficiently, instead just do random operations, store a counter in a # special file so we know how much progress has been made, and hope for # the best. Most likely an internal assert will trigger if anything goes # wrong. # [cases.test_grow_incr_spam_fd_pl_fuzz] defines.INIT_BLOCK_COUNT = 2 # note dirs x files grows O(n^2) defines.N = [1, 2, 4, 8] defines.M = 'N' defines.OPS = 256 defines.SIZE = [ '0', 'FILE_CACHE_SIZE/2', '2*FILE_CACHE_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(10)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' reentrant = true code = ''' // format once per test lfs3_t lfs3; int err = lfs3_mount(&lfs3, LFS3_M_RDWR, CFG); if (err) { // start with a small number of blocks struct lfs3_cfg cfg = *CFG; cfg.block_count = INIT_BLOCK_COUNT; lfs3_format(&lfs3, LFS3_F_RDWR, &cfg) => 0; // mount with maximum block count lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; // fsstat up to date? struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == INIT_BLOCK_COUNT); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); } // keep some test state on disk to survive powerloss typedef struct fuzz_state { lfs3_size_t i; uint32_t prng; } fuzz_state_t; fuzz_state_t state = {.i = 0, .prng = SEED}; lfs3_file_t state_file; err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_RDONLY); assert(!err || err == LFS3_ERR_NOENT); if (!err) { lfs3_file_read(&lfs3, &state_file, &state, sizeof(state)) => sizeof(state); lfs3_file_close(&lfs3, &state_file) => 0; } uint32_t prng = state.prng; for (lfs3_size_t i = state.i; i < OPS; i++) { again:; uint32_t prng_ = prng; // choose which operation to do uint8_t op = TEST_PRNG(&prng_) % 6; // keep test files in a separate directory err = lfs3_mkdir(&lfs3, "test"); assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // how many dirs do we have? lfs3_size_t dir_count = 0; lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "test") => 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); while (true) { err = lfs3_dir_read(&lfs3, &dir, &info); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info.name) == strlen("quartz...")); assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); dir_count++; } lfs3_dir_close(&lfs3, &dir) => 0; // dir op? if (op < 3 || dir_count == 0) { // creating a new dir? if (op == 0 || dir_count == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng_) % N; // create a dir here char name[256]; sprintf(name, "test/quartz%03x", x); err = lfs3_mkdir(&lfs3, name); assert(!err || err == LFS3_ERR_EXIST || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // deleting a dir? } else if (op == 1) { // choose a random dir to delete lfs3_size_t j = TEST_PRNG(&prng_) % dir_count; // find the dir lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // try to delete this dir, ignore non-empty dirs! char name[256]; assert(strlen(info.name) == strlen("quartz...")); sprintf(name, "test/%s", info.name); err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOTEMPTY || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // renaming a dir? } else { // choose a random dir to rename, and a random number to // rename to lfs3_size_t j = TEST_PRNG(&prng_) % dir_count; lfs3_size_t y = TEST_PRNG(&prng_) % N; // find the dir lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // rename this dir, ignore conflicts! char old_name[256]; assert(strlen(info.name) == strlen("quartz...")); sprintf(old_name, "test/%s", info.name); char new_name[256]; sprintf(new_name, "test/quartz%03x", y); err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOTEMPTY || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } } // file op? } else { // choose a pseudo-random dir lfs3_size_t dir_i = TEST_PRNG(&prng_) % dir_count; // find the dir lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= dir_i; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; char dir_path[256]; sprintf(dir_path, "test/%s", info.name); // how many files do we have? lfs3_size_t count = 0; lfs3_dir_open(&lfs3, &dir, dir_path) => 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); while (true) { err = lfs3_dir_read(&lfs3, &dir, &info); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info.name) == strlen("amethyst...")); assert(memcmp( info.name, "amethyst", strlen("amethyst")) == 0); assert(info.type == LFS3_TYPE_REG); assert(info.size == SIZE); count++; } lfs3_dir_close(&lfs3, &dir) => 0; // creating a new file? if (op == 3 || count == 0) { // choose a pseudo-random number lfs3_size_t x = TEST_PRNG(&prng_) % M; uint32_t wprng = TEST_PRNG(&prng_); // create a file here char name[256]; sprintf(name, "%s/amethyst%03x", dir_path, x); uint8_t wbuf[SIZE]; uint8_t ck = 0; for (lfs3_size_t j = 0; j < SIZE-1; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); ck = (ck + (wbuf[j] - 'a')) % 26; } // make the sum equal to 'a' mod 26 if (SIZE > 0) { wbuf[SIZE-1] = 'a' + ((26 - ck) % 26); } lfs3_file_t file; err = lfs3_file_open(&lfs3, &file, name, LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &file, wbuf, SIZE); assert(d == SIZE || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // deleting a file? } else if (op == 4) { // choose a random file to delete lfs3_size_t j = TEST_PRNG(&prng_) % count; // find the file lfs3_dir_open(&lfs3, &dir, dir_path) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // delete this file char name[256]; assert(strlen(info.name) == strlen("amethyst...")); sprintf(name, "%s/%s", dir_path, info.name); err = lfs3_remove(&lfs3, name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } // renaming a file? } else { // choose a random file to rename lfs3_size_t j = TEST_PRNG(&prng_) % count; // find the file lfs3_dir_open(&lfs3, &dir, dir_path) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; lfs3_dir_read(&lfs3, &dir, &info) => 0; for (lfs3_size_t k = 0; k <= j; k++) { lfs3_dir_read(&lfs3, &dir, &info) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // choose a random dir to rename to lfs3_size_t dir_j = TEST_PRNG(&prng_) % dir_count; // find the dir struct lfs3_info info_; lfs3_dir_open(&lfs3, &dir, "test") => 0; lfs3_dir_read(&lfs3, &dir, &info_) => 0; lfs3_dir_read(&lfs3, &dir, &info_) => 0; for (lfs3_size_t k = 0; k <= dir_j; k++) { lfs3_dir_read(&lfs3, &dir, &info_) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; // choose a random file to rename to lfs3_size_t y = TEST_PRNG(&prng_) % M; // rename this file char old_name[256]; assert(strlen(info.name) == strlen("amethyst...")); sprintf(old_name, "%s/%s", dir_path, info.name); char new_name[256]; sprintf(new_name, "test/%s/amethyst%03x", info_.name, y); err = lfs3_rename(&lfs3, old_name, new_name); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } } } // update our state file state.i = i; state.prng = prng_; err = lfs3_file_open(&lfs3, &state_file, "state", LFS3_O_WRONLY | LFS3_O_CREAT | LFS3_O_TRUNC); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } lfs3_ssize_t d = lfs3_file_write(&lfs3, &state_file, &state, sizeof(state)); assert(d == sizeof(state) || d == LFS3_ERR_NOSPC); if (d == LFS3_ERR_NOSPC) { lfs3_file_close(&lfs3, &state_file) => 0; goto grow; } err = lfs3_file_close(&lfs3, &state_file); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { goto grow; } prng = prng_; continue; grow:; // try growing the filesystem struct lfs3_fsinfo fsinfo; lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_count >= INIT_BLOCK_COUNT); assert(fsinfo.block_count <= BLOCK_COUNT); lfs3_ssize_t used = lfs3_fs_usage(&lfs3); assert(used >= 0); // we may need to grow multiple blocks before the system gets unstuck lfs3_size_t block_count_ = fsinfo.block_count; while (true) { assert(block_count_ < BLOCK_COUNT); block_count_ += 1; err = lfs3_fs_grow(&lfs3, block_count_); assert(!err || err == LFS3_ERR_NOSPC); if (err == LFS3_ERR_NOSPC) { continue; } break; } printf("grew %d/%d -> %d/%d\n", used, fsinfo.block_count, used, block_count_); // fsstat up to date? lfs3_fs_stat(&lfs3, &fsinfo) => 0; assert(fsinfo.block_size == BLOCK_SIZE); assert(fsinfo.block_count == block_count_); assert(fsinfo.name_limit == LFS3_NAME_MAX); assert(fsinfo.file_limit == LFS3_FILE_MAX); goto again; } for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfs3_unmount(&lfs3) => 0; lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; } // check that things look more-or-less ok lfs3_dir_t dir; lfs3_dir_open(&lfs3, &dir, "test") => 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); while (true) { int err = lfs3_dir_read(&lfs3, &dir, &info); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info.name) == strlen("quartz...")); assert(memcmp(info.name, "quartz", strlen("quartz")) == 0); assert(info.type == LFS3_TYPE_DIR); assert(info.size == 0); // check that our dirs look more-or-less ok char name[256]; sprintf(name, "test/%s", info.name); lfs3_dir_t dir_; lfs3_dir_open(&lfs3, &dir_, name) => 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); while (true) { err = lfs3_dir_read(&lfs3, &dir_, &info_); assert(!err || err == LFS3_ERR_NOENT); if (err == LFS3_ERR_NOENT) { break; } assert(strlen(info_.name) == strlen("amethyst...")); assert(memcmp( info_.name, "amethyst", strlen("amethyst")) == 0); assert(info_.type == LFS3_TYPE_REG); assert(info_.size == SIZE); // at least try to read the files sprintf(name, "test/%s/%s", info.name, info_.name); lfs3_file_t file; lfs3_file_open(&lfs3, &file, name, LFS3_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfs3_file_read(&lfs3, &file, rbuf, SIZE) => SIZE; // all data should be lowercase ascii for (lfs3_size_t j = 0; j < SIZE; j++) { assert(rbuf[j] >= 'a' && rbuf[j] <= 'z'); } // sum should be equal to 'a' mod 26 uint8_t ck = 0; for (lfs3_size_t j = 0; j < SIZE; j++) { ck = (ck + (rbuf[j] - 'a')) % 26; } assert(ck == 0); lfs3_file_close(&lfs3, &file) => 0; } lfs3_dir_close(&lfs3, &dir_) => 0; } lfs3_dir_close(&lfs3, &dir) => 0; } lfs3_unmount(&lfs3) => 0; '''