# Test orphaned files and their various use cases after = ['test_fwrite', 'test_fsync'] # Some specific tests [cases.test_forphans_uncreat] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // check that the file doesn't _really_ exist lfs_t lfs_; lfsr_mount(&lfs_, LFS_M_RDWR, CFG) => 0; // via stat struct lfs_info info; lfsr_stat(&lfs_, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs_, &dir, "/") => 0; lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs_, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs_, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; lfsr_unmount(&lfs_) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // check that the file still doesn't _really_ exist lfsr_mount(&lfs_, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs_, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs_, &dir, "/") => 0; lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs_, &dir) => 0; // via open lfsr_file_open(&lfs_, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; lfsr_unmount(&lfs_) => 0; } if (SYNC) { // sync the file lfsr_file_sync(&lfs, &file) => 0; // now it should show up lfsr_mount(&lfs_, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs_, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs_, &dir, "/") => 0; lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs_, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs_, &dir) => 0; // via open lfsr_file_open(&lfs_, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs_, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs_, &file_) => 0; lfsr_unmount(&lfs_) => 0; } // close the file lfsr_file_close(&lfs, &file) => 0; // now it should show up lfsr_mount(&lfs_, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs_, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs_, &dir, "/") => 0; lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs_, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs_, &dir) => 0; // via open lfsr_file_open(&lfs_, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs_, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs_, &file_) => 0; lfsr_unmount(&lfs_) => 0; lfsr_unmount(&lfs) => 0; // should still be there lfsr_mount(&lfs_, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs_, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs_, &dir, "/") => 0; lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs_, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs_, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs_, &dir) => 0; // via open lfsr_file_open(&lfs_, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs_, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs_, &file_) => 0; lfsr_unmount(&lfs_) => 0; ''' [cases.test_forphans_uncreat_pl] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' reentrant = true code = ''' // format once per test lfs_t lfs; int err = lfsr_mount(&lfs, LFS_M_RDWR, CFG); if (err) { lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } // create a file // // note the excl flag lfsr_file_t file; err = lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); assert(!err || err == LFS_ERR_EXIST); if (err != LFS_ERR_EXIST) { // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // close the file lfsr_file_close(&lfs, &file) => 0; } // we should be able to read the file now lfsr_file_open(&lfs, &file, "batman", LFS_O_RDONLY) => 0; uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file) => 0; // and after remounting lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_many_pl] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', ] defines.CHUNK = 8 defines.N = 128 reentrant = true code = ''' // format once per test lfs_t lfs; int err = lfsr_mount(&lfs, LFS_M_RDWR, CFG); if (err) { lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } // create N files for (lfs_size_t j = 0; j < N; j++) { // note the excl flag char name[256]; sprintf(name, "batman%03x", j); lfsr_file_t file; err = lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); assert(!err || err == LFS_ERR_EXIST); if (err != LFS_ERR_EXIST) { // write to the file uint32_t prng = 42 + j; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // close the file lfsr_file_close(&lfs, &file) => 0; } } // we should be able to read the files now for (lfs_size_t j = 0; j < N; j++) { char name[256]; sprintf(name, "batman%03x", j); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; uint32_t prng = 42 + j; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file) => 0; } // and after remounting lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; for (lfs_size_t j = 0; j < N; j++) { char name[256]; sprintf(name, "batman%03x", j); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; uint32_t prng = 42 + j; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_sync_wr] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } // but we should still recieve sync broadcasts on sync/close lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } if (SYNC) { // sync the file lfsr_file_sync(&lfs, &file) => 0; // now it should show up // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file__) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close the file lfsr_file_close(&lfs, &file) => 0; // now it should show up // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file__) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file__) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_sync_rw] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // open a second reference lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_WRONLY | LFS_O_CREAT) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // write to the second file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } if (SYNC) { // sync the second file lfsr_file_sync(&lfs, &file__) => 0; // now it should show up // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close the second file lfsr_file_close(&lfs, &file__) => 0; // now it should show up // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_desync_wdwr] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a desync file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; // open a second reference lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // and a third for checking sync broadcasts lfsr_file_t file___; lfsr_file_open(&lfs, &file___, "batman", LFS_O_RDWR | LFS_O_CREAT) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // write to the first file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } if (SYNC) { // sync the second file lfsr_file_sync(&lfs, &file__) => 0; // now it should show up // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close the second file lfsr_file_close(&lfs, &file__) => 0; // now it should show up // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // now sync the first file, this should overwrite what is written lfsr_file_sync(&lfs, &file) => 0; // now it should show up // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // and close, this shouldn't change anything // // note we must sync to clear the desync flag lfsr_file_close(&lfs, &file) => 0; // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file___) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_open] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op defines.CLOSE = [0, 1] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op defines.REMOUNT = [0, 1] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an uncreat lfsr_file_t uncreat; lfsr_file_open(&lfs, &uncreat, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &uncreat, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the uncreat lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; // our uncreat should have been overwritten lfsr_file_rewind(&lfs, &uncreat) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &uncreat, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &uncreat) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &uncreat) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_excl] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op defines.CLOSE = [0, 1] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op defines.REMOUNT = [0, 1] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an uncreat lfsr_file_t uncreat; lfsr_file_open(&lfs, &uncreat, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &uncreat, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // attempt to create a new file over the uncreat // // counterintuitively, we _do_ error on an attempt to create an excl // file when there is an uncreat, even though the file doesn't exist // yet // // otherwise it's easy to create the same file twice with excl, // which isn't very useful and confusing for users // lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => LFS_ERR_EXIST; // we should still be able to read our uncreat lfsr_file_rewind(&lfs, &uncreat) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &uncreat, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &uncreat) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the excl file had no effect if (CLOSE == 1) { // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("WoOoOoOoOoO")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("WoOoOoOoOoO")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); lfsr_file_close(&lfs, &file_) => 0; } else { // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; } if (CLOSE == 0) { lfsr_file_close(&lfs, &uncreat) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_desync_open] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op defines.CLOSE = [0, 1] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op defines.REMOUNT = [0, 1] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a desynced uncreat lfsr_file_t uncreat; lfsr_file_open(&lfs, &uncreat, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &uncreat, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the uncreat lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; // we should still be able to read our uncreat lfsr_file_rewind(&lfs, &uncreat) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &uncreat, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &uncreat) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &uncreat) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_uncreat_desync_excl] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op defines.CLOSE = [0, 1] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op defines.REMOUNT = [0, 1] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a desynced uncreat lfsr_file_t uncreat; lfsr_file_open(&lfs, &uncreat, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &uncreat, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the uncreat // // while it's still possible for the desynced uncreat to create the // file by explicitly calling lfsr_file_sync, for the most part we // treat desynced files like zombies and allow excl creates // // this makes lfsr_file_sync/resync roughly the same as opening the // file after the excl create succeeds, and if you're using desynced // files you should probably be aware of littlefs's snapshot model // anyways // lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; // we should still be able to read our uncreat lfsr_file_rewind(&lfs, &uncreat) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &uncreat, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &uncreat) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &uncreat) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a desync file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } // close lfsr_file_close(&lfs, &file) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // because the file was desynced, it should still not exist // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // even after a remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // because the file was desynced, it should still not exist // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_wdwr] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' # SYNC=0x1 => sync before orphaning # SYNC=0x2 => sync after orphaning defines.SYNC = [0, 1, 2, 3] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a desync file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; // open a second reference lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // and a third for checking sync broadcasts lfsr_file_t file___; lfsr_file_open(&lfs, &file___, "batman", LFS_O_RDWR | LFS_O_CREAT) => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // write to the first file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; } if (SYNC & 0x1) { // sync the second file lfsr_file_sync(&lfs, &file__) => 0; // now it should show up // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close the first file, this should do nothing but discard the // file contents lfsr_file_close(&lfs, &file) => 0; if (SYNC & 0x2) { // sync the second file lfsr_file_sync(&lfs, &file__) => 0; // now it should show up // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close the second file lfsr_file_close(&lfs, &file__) => 0; // now it should show up // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // recieved sync broadcast? lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file___) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_unrelated] # different number of orphan require different methods of cleanup defines.N = 'range(6)' defines.M = 'range(6)' defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create N orphans lfsr_file_t orphans[N]; for (lfs_size_t o = 0; o < N; o++) { char name[256]; sprintf(name, "aatman%03x", o); lfsr_file_open(&lfs, &orphans[o], name, LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; uint32_t prng = 42+o; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &orphans[o], wbuf, CHUNK) => CHUNK; } } // and an unrelated file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 52; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, none of the orphans exist // via stat struct lfs_info info; for (lfs_size_t o = 0; o < N; o++) { char name[256]; sprintf(name, "aatman%03x", o); lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT; } lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; for (lfs_size_t o = 0; o < N; o++) { char name[256]; sprintf(name, "aatman%03x", o); // rdonly rejected lfsr_file_open(&lfs, &file_, name, LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, name, LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, name, LFS_O_RDWR) => LFS_ERR_NOENT; } // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // but we should still be able to read our orphans for (lfs_size_t o = 0; o < N; o++) { lfsr_file_rewind(&lfs, &orphans[o]) => 0; prng = 42+o; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &orphans[o], rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } lfsr_file_rewind(&lfs, &file) => 0; prng = 52; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close M orphans for (lfs_size_t o = 0; o < M && o < N; o++) { lfsr_file_close(&lfs, &orphans[o]) => 0; } // create a new unrelated file, this should trigger // and orphan cleanup lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "catman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; prng = 62; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } lfsr_file_close(&lfs, &file__); // and now close our original unrelated file lfsr_file_close(&lfs, &file) => 0; // and close the remaining orphans for (lfs_size_t o = M; o < N; o++) { lfsr_file_close(&lfs, &orphans[o]) => 0; } // now our file should exist, but none of our orphans // via stat for (lfs_size_t o = 0; o < N; o++) { char name[256]; sprintf(name, "aatman%03x", o); lfsr_stat(&lfs, name, &info) => LFS_ERR_NOENT; } lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, "catman", &info) => 0; assert(strcmp(info.name, "catman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "catman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open for (lfs_size_t o = 0; o < N; o++) { char name[256]; sprintf(name, "aatman%03x", o); // rdonly rejected lfsr_file_open(&lfs, &file_, name, LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, name, LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, name, LFS_O_RDWR) => LFS_ERR_NOENT; } lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 52; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_file_open(&lfs, &file_, "catman", LFS_O_RDONLY) => 0; prng = 62; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_open] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the orphan lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_excl] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the zombie // // orphaned files will never exist again, so LFS_O_EXCL should not // fail here // lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_mkdir] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new dir over the orphan lfsr_mkdir(&lfs, "batman") => 0; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new dir is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_rm] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // orphans aren't real, so remove should fail lfsr_remove(&lfs, "batman") => LFS_ERR_NOENT; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // just make sure things look ok // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_mv_dst] defines.DIR = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // make our src file before orphans, otherwise open just cleans // things up if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "a/datman" : "datman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/datman" : "datman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; } // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, (INTERDIR) ? "c/aatman%03x" : "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // rename onto orphan lfsr_rename(&lfs, (INTERDIR) ? "a/datman" : "datman", (INTERDIR) ? "c/batman" : "batman") => 0; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "c/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } lfsr_stat(&lfs, (INTERDIR) ? "a/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "c" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open if (DIR) { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "c/batman" : "batman", LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "c/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_orphan_mv_src] defines.ORPHANS = [1, 2, 3, 100] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create neighboring orphaned files // // more orphans requires different techniques for cleaning up orphans lfsr_file_t orphans[ORPHANS-1]; for (lfs_size_t i = 0; i < ORPHANS-1; i++) { char name[256]; sprintf(name, "aatman%03x", i); lfsr_file_open(&lfs, &orphans[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &orphans[i], "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // create an orphaned file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; lfsr_file_write(&lfs, &file, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); // close all orphans at once, or else the open calls would just // clean up each orphans for (lfs_size_t i = 0; i < ORPHANS-1; i++) { lfsr_file_close(&lfs, &orphans[i]) => 0; } lfsr_file_close(&lfs, &file) => 0; if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // orphans aren't real, so rename should fail lfsr_rename(&lfs, "batman", "catman") => LFS_ERR_NOENT; if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // just make sure things look ok // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; lfsr_stat(&lfs, "catman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; // but we should still be able to read our file handle lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; // because the file was removed, it should still not exist // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // even after a remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_posthumous] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; // but we should still be able to read our file handle lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; // because the file was removed, it should still not exist // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // even after a remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // via stat lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_rwrw] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; // but we should be able to sync our second file just fine lfsr_file_sync(&lfs, &file__) => 0; // second file should appear on disk now // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // the perhaps surprising thing is we should still be able // to read both file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; lfsr_file_close(&lfs, &file__) => 0; // check disk again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // check disk again again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_rwrw_posthumous] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the second file uint32_t prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // write to the first file prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; // but we should be able to sync our second file just fine lfsr_file_sync(&lfs, &file__) => 0; // second file should appear on disk now // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // the perhaps surprising thing is we should still be able // to read both file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; lfsr_file_close(&lfs, &file__) => 0; // check disk again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // check disk again again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_zombie_rwrwrw] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // need to sync so we can remove lfsr_file_sync(&lfs, &file__) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a third file lfsr_file_t file___; lfsr_file_open(&lfs, &file___, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the third file prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file___, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; lfsr_file_sync(&lfs, &file__) => LFS_ERR_NOENT; // but we should be able to sync our third file just fine lfsr_file_sync(&lfs, &file___) => 0; // third file should appear on disk now // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // the perhaps surprising thing is we should still be able // to read all file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; lfsr_file_close(&lfs, &file__) => 0; lfsr_file_close(&lfs, &file___) => 0; // check disk again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // check disk again again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_zombie_rwrwrw_posthumous] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can remove lfsr_file_sync(&lfs, &file) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can remove lfsr_file_sync(&lfs, &file__) => 0; // remove the file lfsr_remove(&lfs, "batman") => 0; // create a third file lfsr_file_t file___; lfsr_file_open(&lfs, &file___, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the third file uint32_t prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file___, wbuf, CHUNK) => CHUNK; } // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // write to the first file prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // as far as the filesystem is concerned, the file does not exist yet // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // rdonly rejected lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => LFS_ERR_NOENT; // non-create rejected lfsr_file_open(&lfs, &file_, "batman", LFS_O_WRONLY) => LFS_ERR_NOENT; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDWR) => LFS_ERR_NOENT; // removed files can not be synced lfsr_file_sync(&lfs, &file) => LFS_ERR_NOENT; lfsr_file_sync(&lfs, &file__) => LFS_ERR_NOENT; // but we should be able to sync our third file just fine lfsr_file_sync(&lfs, &file___) => 0; // third file should appear on disk now // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // the perhaps surprising thing is we should still be able // to read all file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file___) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file___, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; lfsr_file_close(&lfs, &file__) => 0; lfsr_file_close(&lfs, &file___) => 0; // check disk again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // check disk again again // via stat lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // via readdir lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; prng = 42+2; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_open] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the zombie lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_excl] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new file over the zombie // // zombie files will never exist again, so LFS_O_EXCL should not // fail here // lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_mkdir] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // create a new dir over the zombie lfsr_mkdir(&lfs, "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new dir is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_rm] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // zombies aren't real, so remove should fail lfsr_remove(&lfs, "batman") => LFS_ERR_NOENT; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // just make sure things look ok // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_mv_dst] defines.DIR = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // make our src file before zombies, just in case if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "c/datman" : "datman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; } // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, (INTERDIR) ? "a/batman" : "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // rename onto zombie lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } lfsr_stat(&lfs, (INTERDIR) ? "a/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open if (DIR) { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; } if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombie_mv_src] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // zombies aren't real, so rename should fail lfsr_rename(&lfs, "batman", "catman") => LFS_ERR_NOENT; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // just make sure things look ok // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; lfsr_stat(&lfs, "catman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombify_mkdir] defines.POSTHUMOUS = [false, true] defines.CLOSE = [false, true] defines.REMOUNT = [false, true] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a file, not a zombie yet lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // use mkdir on the same name, the file hasn't been created yet, // so this shouldn't fail, but because we have an open file handle // we create a zombie lfsr_mkdir(&lfs, "batman") => 0; if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new dir is readable // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; if (!CLOSE) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_zombify_mv_dst] defines.ORPHAN = [false, true] defines.DIR = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.POSTHUMOUS = [false, true] defines.CLOSE = [false, true] defines.REMOUNT = [false, true] defines.MKCONSISTENT = [false, true] if = [ 'REMOUNT <= CLOSE', '!DIR || ORPHAN', ] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // make our src file before zombies, just in case if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "c/datman" : "datman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; } // create a file, not a zombie yet lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } if (!ORPHAN) { lfsr_file_sync(&lfs, &zombie) => 0; } // rename onto the same name, this shouldn't fail (note the test // conditions), but because we have an open file handle we create // a zombie lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } lfsr_stat(&lfs, (INTERDIR) ? "a/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open if (DIR) { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; } if (!CLOSE) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_file_on_zombie_rm] defines.DIR = [false, true] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, "batman") => 0; // create a file on top of the zombie if (DIR) { lfsr_mkdir(&lfs, "batman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // remove the file lfsr_remove(&lfs, "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // just make sure things look ok // via stat struct lfs_info info; lfsr_stat(&lfs, "batman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_file_on_zombie_mv_dst] defines.DIR = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // make our src file before zombies, just in case if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "c/datman" : "datman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; } // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, (INTERDIR) ? "a/batman" : "batman") => 0; // create a file on top of the zombie if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "a/batman" : "batman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // rename onto the file lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our zombie lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } lfsr_stat(&lfs, (INTERDIR) ? "a/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open if (DIR) { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; } if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' # test that we actually cleanup orphans correctly [cases.test_forphans_cleanup] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # <=2 => grm-able # >2 => requires orphans defines.N = [0, 1, 2, 3, 10, 100] defines.REMOUNT = [false, true] defines.BOOKENDS = [0x0, 0x1, 0x2, 0x3] if = '(SIZE*N)/BLOCK_SIZE <= 32' in = 'lfs.c' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create some unrelated files to make sure cleaning up orphans // doesn't break other filesystem things uint32_t bookend_prngs[2] = {0, 0}; if (BOOKENDS & 0x1) { lfsr_file_t file; lfsr_file_open(&lfs, &file, "aatman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[0] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } if (BOOKENDS & 0x2) { lfsr_file_t file; lfsr_file_open(&lfs, &file, "catman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[1] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t files[N]; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "batman%03x", i); lfsr_file_open(&lfs, &files[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } // remount? this has no effect on orphans if (REMOUNT) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } // calling lfsr_fs_mkconsistent should clean things up lfsr_fs_mkconsistent(&lfs) => 0; // we should have cleaned up all grms/orphans assert(lfs.grm.mids[0] == -1); assert(lfs.grm.mids[1] == -1); assert(!(lfs.flags & LFS_I_MKCONSISTENT)); struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_MKCONSISTENT)); // double check the actual disk state, it's easy for littlefs to // lie here assert(lfsr_mtree_weight(&lfs) <= ((1+lfs_popc(BOOKENDS)) << lfs.mdir_bits)); lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight <= 1+lfs_popc(BOOKENDS)); // check that other files are unaffected for (int remount = 0; remount < 2; remount++) { if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (BOOKENDS & 0x1) { lfsr_file_t file; lfsr_file_open(&lfs, &file, "aatman", LFS_O_RDONLY) => 0; uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file); } if (BOOKENDS & 0x2) { lfsr_file_t file; lfsr_file_open(&lfs, &file, "catman", LFS_O_RDONLY) => 0; uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file); } } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_cleanup_open] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # <=2 => grm-able # >2 => requires orphans defines.N = [0, 1, 2, 3, 10, 100] defines.BOOKENDS = [0x0, 0x1, 0x2, 0x3] if = '(SIZE*N)/BLOCK_SIZE <= 32' in = 'lfs.c' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create some unrelated opened files to make sure cleaning up // orphans doesn't break other filesystem things // // note we leave these open in this test lfsr_file_t bookend_files[2]; uint32_t bookend_prngs[2] = {0, 0}; if (BOOKENDS & 0x1) { lfsr_file_open(&lfs, &bookend_files[0], "aatman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[0] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[0], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &bookend_files[0]) => 0; } if (BOOKENDS & 0x2) { lfsr_file_open(&lfs, &bookend_files[1], "catman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[1] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[1], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &bookend_files[1]) => 0; } // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t files[N]; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "batman%03x", i); lfsr_file_open(&lfs, &files[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } // calling lfsr_fs_mkconsistent should clean things up lfsr_fs_mkconsistent(&lfs) => 0; // we should have cleaned up all grms/orphans assert(lfs.grm.mids[0] == -1); assert(lfs.grm.mids[1] == -1); assert(!(lfs.flags & LFS_I_MKCONSISTENT)); struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_MKCONSISTENT)); // double check the actual disk state, it's easy for littlefs to // lie here assert(lfsr_mtree_weight(&lfs) <= ((1+lfs_popc(BOOKENDS)) << lfs.mdir_bits)); lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight <= 1+lfs_popc(BOOKENDS)); // check that other files are unaffected for (int remount = 0; remount < 2; remount++) { if (remount) { if (BOOKENDS & 0x1) { lfsr_file_close(&lfs, &bookend_files[0]) => 0; } if (BOOKENDS & 0x2) { lfsr_file_close(&lfs, &bookend_files[1]) => 0; } lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; if (BOOKENDS & 0x1) { lfsr_file_open(&lfs, &bookend_files[0], "aatman", LFS_O_RDONLY) => 0; } if (BOOKENDS & 0x2) { lfsr_file_open(&lfs, &bookend_files[1], "catman", LFS_O_RDONLY) => 0; } } if (BOOKENDS & 0x1) { lfsr_file_rewind(&lfs, &bookend_files[0]) => 0; uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[0], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } if (BOOKENDS & 0x2) { lfsr_file_rewind(&lfs, &bookend_files[1]) => 0; uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[1], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } if (BOOKENDS & 0x1) { lfsr_file_close(&lfs, &bookend_files[0]); } if (BOOKENDS & 0x2) { lfsr_file_close(&lfs, &bookend_files[1]); } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_cleanup_uncreat] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # <=2 => grm-able # >2 => requires orphans defines.N = [0, 1, 2, 3, 10, 100] defines.BOOKENDS = [0x0, 0x1, 0x2, 0x3] if = '(SIZE*N)/BLOCK_SIZE <= 32' in = 'lfs.c' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create some unrelated uncreat files to make sure cleaning up // orphans doesn't break other filesystem things // // note we leave these uncreated + open in this test lfsr_file_t bookend_files[2]; uint32_t bookend_prngs[2] = {0, 0}; if (BOOKENDS & 0x1) { lfsr_file_open(&lfs, &bookend_files[0], "aatman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[0] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[0], wbuf, SIZE) => SIZE; } if (BOOKENDS & 0x2) { lfsr_file_open(&lfs, &bookend_files[1], "catman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[1] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[1], wbuf, SIZE) => SIZE; } // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t files[N]; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "batman%03x", i); lfsr_file_open(&lfs, &files[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } // calling lfsr_fs_mkconsistent should clean things up lfsr_fs_mkconsistent(&lfs) => 0; // we should have cleaned up all grms/orphans assert(lfs.grm.mids[0] == -1); assert(lfs.grm.mids[1] == -1); assert(!(lfs.flags & LFS_I_MKCONSISTENT)); struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_MKCONSISTENT)); // double check the actual disk state, it's easy for littlefs to // lie here assert(lfsr_mtree_weight(&lfs) <= ((1+lfs_popc(BOOKENDS)) << lfs.mdir_bits)); lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight <= 1+lfs_popc(BOOKENDS)); // check that other files are unaffected for (int remount = 0; remount < 2; remount++) { if (remount) { if (BOOKENDS & 0x1) { lfsr_file_close(&lfs, &bookend_files[0]) => 0; } if (BOOKENDS & 0x2) { lfsr_file_close(&lfs, &bookend_files[1]) => 0; } lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; if (BOOKENDS & 0x1) { lfsr_file_open(&lfs, &bookend_files[0], "aatman", LFS_O_RDONLY) => 0; } if (BOOKENDS & 0x2) { lfsr_file_open(&lfs, &bookend_files[1], "catman", LFS_O_RDONLY) => 0; } } if (BOOKENDS & 0x1) { lfsr_file_rewind(&lfs, &bookend_files[0]) => 0; uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[0], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } if (BOOKENDS & 0x2) { lfsr_file_rewind(&lfs, &bookend_files[1]) => 0; uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[1], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } if (BOOKENDS & 0x1) { lfsr_file_close(&lfs, &bookend_files[0]); } if (BOOKENDS & 0x2) { lfsr_file_close(&lfs, &bookend_files[1]); } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_cleanup_zombie] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # <=2 => grm-able # >2 => requires orphans defines.N = [0, 1, 2, 3, 10, 100] defines.BOOKENDS = [0x0, 0x1, 0x2, 0x3] if = '(SIZE*N)/BLOCK_SIZE <= 32' in = 'lfs.c' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; uint32_t prng = 42; // create some unrelated zombie files to make sure cleaning up // orphans doesn't break other filesystem things // // note we leave these zombied + open in this test lfsr_file_t bookend_files[2]; uint32_t bookend_prngs[2] = {0, 0}; if (BOOKENDS & 0x1) { lfsr_file_open(&lfs, &bookend_files[0], "aatman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[0] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[0], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &bookend_files[0]) => 0; lfsr_remove(&lfs, "aatman") => 0; } if (BOOKENDS & 0x2) { lfsr_file_open(&lfs, &bookend_files[1], "catman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; bookend_prngs[1] = TEST_PRNG(&prng); uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } lfsr_file_write(&lfs, &bookend_files[1], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &bookend_files[1]) => 0; lfsr_remove(&lfs, "catman") => 0; } // create this many orphaned files // // anytime we close a not-yet-created desync file, we create an // orphan, but note we need these to be different files, and we need // to close them after all open calls, otherwise we just end up with // one orphan (littlefs is eager to clean up orphans) // lfsr_file_t files[N]; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "batman%03x", i); lfsr_file_open(&lfs, &files[i], name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL | LFS_O_DESYNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } // calling lfsr_fs_mkconsistent should clean things up lfsr_fs_mkconsistent(&lfs) => 0; // we should have cleaned up all grms/orphans assert(lfs.grm.mids[0] == -1); assert(lfs.grm.mids[1] == -1); assert(!(lfs.flags & LFS_I_MKCONSISTENT)); struct lfs_fsinfo fsinfo; lfsr_fs_stat(&lfs, &fsinfo) => 0; assert(!(fsinfo.flags & LFS_I_MKCONSISTENT)); // double check the actual disk state, it's easy for littlefs to // lie here assert(lfsr_mtree_weight(&lfs) <= ((1+lfs_popc(BOOKENDS)) << lfs.mdir_bits)); lfsr_mdir_t mdir; lfsr_mtree_lookup(&lfs, 0, &mdir) => 0; assert(mdir.rbyd.weight <= 1+lfs_popc(BOOKENDS)); // check that other files are unaffected if (BOOKENDS & 0x1) { lfsr_file_rewind(&lfs, &bookend_files[0]) => 0; uint32_t prng_ = bookend_prngs[0]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[0], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } if (BOOKENDS & 0x2) { lfsr_file_rewind(&lfs, &bookend_files[1]) => 0; uint32_t prng_ = bookend_prngs[1]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng_) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &bookend_files[1], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } if (BOOKENDS & 0x1) { lfsr_file_close(&lfs, &bookend_files[0]); } if (BOOKENDS & 0x2) { lfsr_file_close(&lfs, &bookend_files[1]); } lfsr_unmount(&lfs) => 0; ''' # these doesn't really involve scratch files, but we might as well test # them here [cases.test_forphans_mv] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // need to sync so we can rename lfsr_file_sync(&lfs, &file) => 0; // rename the file lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // the file should have been renamed // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // but we should still be able to read our file handle lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } // close lfsr_file_close(&lfs, &file) => 0; // close should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // remount should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_mv_postmv] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can rename lfsr_file_sync(&lfs, &file) => 0; // rename the file lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // the file should have been renamed, but zero sized // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => 0; lfsr_file_close(&lfs, &file_) => 0; // but we should still be able to read our file handle lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } if (SYNC) { // sync lfsr_file_sync(&lfs, &file) => 0; // the data should now be visible // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // we should still be able to read our file handle lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close lfsr_file_close(&lfs, &file) => 0; // the data should now be visible // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // remount should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_mv_rwrw] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // write to the file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // need to sync so we can rename lfsr_file_sync(&lfs, &file) => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_TRUNC) => 0; // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // rename the file lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // the file should have been renamed // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // but we should still be able to read both file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } if (SYNC) { // sync, note the order lfsr_file_sync(&lfs, &file__) => 0; lfsr_file_sync(&lfs, &file) => 0; // the second file's data should now be visible // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // both file handles should be updated lfsr_file_rewind(&lfs, &file) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close, note the order lfsr_file_close(&lfs, &file__) => 0; lfsr_file_close(&lfs, &file) => 0; // close should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // remount should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_mv_rwrw_postmv] defines.SIZE = [ 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.CHUNK = 'LFS_MIN(64, SIZE)' defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.SYNC = [false, true] defines.MKCONSISTENT = [false, true] code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; // need to sync so we can rename lfsr_file_sync(&lfs, &file) => 0; // create a second file lfsr_file_t file__; lfsr_file_open(&lfs, &file__, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_TRUNC) => 0; // rename the file lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; // write to the first file uint32_t prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file, wbuf, CHUNK) => CHUNK; } // write to the second file prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &file__, wbuf, CHUNK) => CHUNK; } // mkconsistent should have no effect if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // the file should have been renamed, but zero sized // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => 0; lfsr_file_close(&lfs, &file_) => 0; // but we should still be able to read both file handles lfsr_file_rewind(&lfs, &file) => 0; prng = 42; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } if (SYNC) { // sync, note the order lfsr_file_sync(&lfs, &file__) => 0; lfsr_file_sync(&lfs, &file) => 0; // the second file's data should now be visible // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // both file handles should be updated lfsr_file_rewind(&lfs, &file) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_rewind(&lfs, &file__) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file__, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } } // close, note the order lfsr_file_close(&lfs, &file__) => 0; lfsr_file_close(&lfs, &file) => 0; // close should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; // remount lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // remount should have no effect // via stat lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; prng = 42+1; for (lfs_off_t i = 0; i < SIZE; i += CHUNK) { uint8_t wbuf[CHUNK]; for (lfs_size_t j = 0; j < CHUNK; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[CHUNK]; lfsr_file_read(&lfs, &file_, rbuf, CHUNK) => CHUNK; assert(memcmp(wbuf, rbuf, CHUNK) == 0); } lfsr_file_close(&lfs, &file_) => 0; lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_mv_mv_src] defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.POSTMV = [false, true] defines.SYNC = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_sync(&lfs, &file) => 0; if (!POSTMV) { lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); if (SYNC) { lfsr_file_sync(&lfs, &file) => 0; } } if (CLOSE == 2) { lfsr_file_close(&lfs, &file) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // rename while open lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTMV) { lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); if (SYNC) { lfsr_file_sync(&lfs, &file) => 0; } } // we should still be able to read our file lfsr_file_rewind(&lfs, &file) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &file) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); if ((SYNC || CLOSE >= 1) && !(POSTMV && CLOSE >= 2)) { assert(info.size == strlen("catman!")); } else { assert(info.size == 0); } lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); assert(info.type == LFS_TYPE_REG); if ((SYNC || CLOSE >= 1) && !(POSTMV && CLOSE >= 2)) { assert(info.size == strlen("catman!")); } else { assert(info.size == 0); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; if ((SYNC || CLOSE >= 1) && !(POSTMV && CLOSE >= 2)) { lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); } else { lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => 0; } lfsr_file_close(&lfs, &file_) => 0; if (CLOSE == 0) { lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_file_on_zombie_mv_src] defines.DIR = [false, true] defines.EXISTS = [false, true] defines.INTERDIR = [false, true] defines.DISTANCE = [0, 1, 100] defines.POSTHUMOUS = [false, true] # CLOSE=0 => don't close (before end of test) # CLOSE=1 => close after op # CLOSE=2 => close before op defines.CLOSE = [0, 1, 2] # REMOUNT=0 => don't remount # REMOUNT=1 => remount after op # REMOUNT=2 => remount before op defines.REMOUNT = [0, 1, 2] defines.MKCONSISTENT = [false, true] if = 'REMOUNT <= CLOSE' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting directory structure if (INTERDIR) { lfsr_mkdir(&lfs, "a") => 0; lfsr_mkdir(&lfs, "b") => 0; lfsr_mkdir(&lfs, "c") => 0; } for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "b/catman%03x" : "catman%03x", i); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_close(&lfs, &file) => 0; } // create our destination first, just in case if (EXISTS) { if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "a/batman" : "batman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "a/batman" : "batman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "hmmmmmmmm", strlen("hmmmmmmmm")) => strlen("hmmmmmmmm"); lfsr_file_close(&lfs, &file) => 0; } } // create a zombie lfsr_file_t zombie; lfsr_file_open(&lfs, &zombie, (INTERDIR) ? "c/datman" : "datman", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; if (!POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } lfsr_file_sync(&lfs, &zombie) => 0; lfsr_remove(&lfs, (INTERDIR) ? "c/datman" : "datman") => 0; // create a file on top of the zombie if (DIR) { lfsr_mkdir(&lfs, (INTERDIR) ? "c/datman" : "datman") => 0; } else { lfsr_file_t file; lfsr_file_open(&lfs, &file, (INTERDIR) ? "c/datman" : "datman", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, "catman!", strlen("catman!")) => strlen("catman!"); lfsr_file_close(&lfs, &file) => 0; } if (CLOSE == 2) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 2) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // rename while open lfsr_rename(&lfs, (INTERDIR) ? "c/datman" : "datman", (INTERDIR) ? "a/batman" : "batman") => 0; if (CLOSE <= 1 && REMOUNT <= 1) { if (POSTHUMOUS) { lfsr_file_write(&lfs, &zombie, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) => strlen("WoOoOoOoOoO"); } // we should still be able to read our file lfsr_file_rewind(&lfs, &zombie) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &zombie, rbuf, sizeof(rbuf)) => strlen("WoOoOoOoOoO"); assert(memcmp(rbuf, "WoOoOoOoOoO", strlen("WoOoOoOoOoO")) == 0); if (CLOSE == 1) { lfsr_file_close(&lfs, &zombie) => 0; } if (REMOUNT == 1) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; } } if (MKCONSISTENT) { lfsr_fs_mkconsistent(&lfs) => 0; } // make sure the new file is readable // via stat struct lfs_info info; lfsr_stat(&lfs, (INTERDIR) ? "a/batman" : "batman", &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } lfsr_stat(&lfs, (INTERDIR) ? "c/datman" : "datman", &info) => LFS_ERR_NOENT; // via readdir lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, (INTERDIR) ? "a" : "/") => 0; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "batman") == 0); if (DIR) { assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == strlen("catman!")); } if (!INTERDIR) { for (lfs_size_t i = 0; i < DISTANCE; i++) { char name[256]; sprintf(name, (INTERDIR) ? "a/catman%03x" : "catman%03x", i); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == 0); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; // via open if (DIR) { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { lfsr_file_t file_; lfsr_file_open(&lfs, &file_, (INTERDIR) ? "a/batman" : "batman", LFS_O_RDONLY) => 0; uint8_t rbuf[256]; lfsr_file_read(&lfs, &file_, rbuf, sizeof(rbuf)) => strlen("catman!"); assert(memcmp(rbuf, "catman!", strlen("catman!")) == 0); lfsr_file_close(&lfs, &file_) => 0; } if (CLOSE == 0) { lfsr_file_close(&lfs, &zombie) => 0; } lfsr_unmount(&lfs) => 0; ''' # one particularly nasty case is renaming over an mdir split, since shrubs # can be moved around quite a few times when that happens # # here we spam renames over an increasing number of files to hopefully hit # that case # [cases.test_forphans_mv_split] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # keep unsynced data open? defines.UNSYNC = [false, true] # keep a desynced file open? defines.DESYNC = [false, true] # keep a zombie open? defines.ZOMBIE = [false, true] if = '(SIZE*N*(1+DESYNC+ZOMBIE))/BLOCK_SIZE <= 32' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // we need a file handle for each file + desync + zombie lfsr_file_t files[N]; lfsr_file_t desyncs[N]; lfsr_file_t zombies[N]; // create this many files while renaming uint32_t prng = 42; uint32_t unsync_prng = 43; uint32_t desync_prng = 44; uint32_t zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // create a zombie? if (ZOMBIE) { lfsr_file_open(&lfs, &zombies[i], "batman!!!", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } lfsr_file_write(&lfs, &zombies[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &zombies[i]) => 0; lfsr_remove(&lfs, "batman!!!") => 0; } // always create as first file lfsr_file_open(&lfs, &files[i], "batman!!!", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &files[i]) => 0; // keep unsynced data? if (UNSYNC) { lfsr_file_rewind(&lfs, &files[i]) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&unsync_prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } // keep a desynced file open? if (DESYNC) { lfsr_file_open(&lfs, &desyncs[i], "batman!!!", LFS_O_RDWR | LFS_O_DESYNC | LFS_O_TRUNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } lfsr_file_write(&lfs, &desyncs[i], wbuf, SIZE) => SIZE; } // rename! char name[256]; sprintf(name, "batman%03x", i); lfsr_rename(&lfs, "batman!!!", name) => 0; } // check that renames worked prng = 42; struct lfs_info info; lfsr_stat(&lfs, "batman!!!", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", i); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected prng = (!UNSYNC) ? 42 : 43; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // try syncing any unsynced files for (lfs_size_t i = 0; i < N; i++) { lfsr_file_sync(&lfs, &files[i]) => 0; } // check that sync worked prng = (!UNSYNC) ? 42 : 43; lfsr_stat(&lfs, "batman!!!", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", i); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected prng = (!UNSYNC) ? 42 : 43; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // try rewriting our open files uint32_t rewrite_prng = 52; for (lfs_size_t i = 0; i < N; i++) { lfsr_file_rewind(&lfs, &files[i]) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &files[i]) => 0; } // check that rewrites worked rewrite_prng = 52; lfsr_stat(&lfs, "batman!!!", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", i); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected rewrite_prng = 52; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // cleanup files for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } if (DESYNC) { for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &desyncs[i]) => 0; } } if (ZOMBIE) { for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &zombies[i]) => 0; } } lfsr_unmount(&lfs) => 0; ''' [cases.test_forphans_mv_split_backwards] defines.N = [1, 2, 4, 8, 16, 32, 64] defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] # keep unsynced data open? defines.UNSYNC = [false, true] # keep a desynced file open? defines.DESYNC = [false, true] # keep a zombie open? defines.ZOMBIE = [false, true] if = '(SIZE*N*(1+DESYNC+ZOMBIE))/BLOCK_SIZE <= 32' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // we need a file handle for each file + desync + zombie lfsr_file_t files[N]; lfsr_file_t desyncs[N]; lfsr_file_t zombies[N]; // create this many files while renaming uint32_t prng = 42; uint32_t unsync_prng = 43; uint32_t desync_prng = 44; uint32_t zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // create a zombie? if (ZOMBIE) { lfsr_file_open(&lfs, &zombies[i], "batman???", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } lfsr_file_write(&lfs, &zombies[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &zombies[i]) => 0; lfsr_remove(&lfs, "batman???") => 0; } // always create as last file lfsr_file_open(&lfs, &files[i], "batman???", LFS_O_RDWR | LFS_O_CREAT | LFS_O_EXCL) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &files[i]) => 0; // keep unsynced data? if (UNSYNC) { lfsr_file_rewind(&lfs, &files[i]) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&unsync_prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; } // keep a desynced file open? if (DESYNC) { lfsr_file_open(&lfs, &desyncs[i], "batman???", LFS_O_RDWR | LFS_O_DESYNC | LFS_O_TRUNC) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } lfsr_file_write(&lfs, &desyncs[i], wbuf, SIZE) => SIZE; } // rename! char name[256]; sprintf(name, "batman%03x", (uint32_t)(N-1-i)); lfsr_rename(&lfs, "batman???", name) => 0; } // check that renames worked prng = 42; struct lfs_info info; lfsr_stat(&lfs, "batman???", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", (uint32_t)(N-1-i)); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected prng = (!UNSYNC) ? 42 : 43; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // try syncing any unsynced files for (lfs_size_t i = 0; i < N; i++) { lfsr_file_sync(&lfs, &files[i]) => 0; } // check that sync worked prng = (!UNSYNC) ? 42 : 43; lfsr_stat(&lfs, "batman???", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", (uint32_t)(N-1-i)); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected prng = (!UNSYNC) ? 42 : 43; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // try rewriting our open files uint32_t rewrite_prng = 52; for (lfs_size_t i = 0; i < N; i++) { lfsr_file_rewind(&lfs, &files[i]) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } lfsr_file_write(&lfs, &files[i], wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &files[i]) => 0; } // check that rewrites worked rewrite_prng = 52; lfsr_stat(&lfs, "batman???", &info) => LFS_ERR_NOENT; for (lfs_size_t i = 0; i < N; i++) { // check with stat char name[256]; sprintf(name, "batman%03x", (uint32_t)(N-1-i)); lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } lfsr_file_t file; uint8_t rbuf[SIZE]; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that file handles are as expected rewrite_prng = 52; desync_prng = 44; zombie_prng = 45; for (lfs_size_t i = 0; i < N; i++) { // check size assert(lfsr_file_size(&lfs, &files[i]) == SIZE); // try reading the file uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&rewrite_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &files[i]) => 0; lfsr_file_read(&lfs, &files[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); // check any desynced files if (DESYNC) { assert(lfsr_file_size(&lfs, &desyncs[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&desync_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &desyncs[i]) => 0; lfsr_file_read(&lfs, &desyncs[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // check any zombied files if (ZOMBIE) { assert(lfsr_file_size(&lfs, &zombies[i]) == SIZE); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&zombie_prng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_rewind(&lfs, &zombies[i]) => 0; lfsr_file_read(&lfs, &zombies[i], rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } } // cleanup files for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &files[i]) => 0; } if (DESYNC) { for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &desyncs[i]) => 0; } } if (ZOMBIE) { for (lfs_size_t i = 0; i < N; i++) { lfsr_file_close(&lfs, &zombies[i]) => 0; } } lfsr_unmount(&lfs) => 0; ''' # fuzz tests involving many uncreats + zombies, this gets a bit crazy [cases.test_forphans_uz_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // set up a simulation to compare against lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); lfs_size_t sim_size = 0; typedef struct sim_file { lfs_size_t x; bool uncreat; bool zombie; uint32_t prng; lfsr_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs_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 lfs_size_t x = TEST_PRNG(&prng) % N; // already exists? bool uncreat = true; uint32_t wprng = 0; for (lfs_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { uncreat = false; wprng = sim_prngs[j]; break; } } // choose a random seed if we don't exist if (uncreat) { wprng = TEST_PRNG(&prng); } // open in our sim lfs_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); sim_files[j]->x = x; sim_files[j]->uncreat = uncreat; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfsr_file_open(&lfs, &sim_files[j]->file, name, LFS_O_RDWR | LFS_O_CREAT) => 0; // write some initial data if we don't exist if (uncreat) { uint8_t wbuf[SIZE]; for (lfs_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // insert into our sim for (lfs_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(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; } break; } } // update related sim files for (lfs_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { sim_files[k]->uncreat = false; sim_files[k]->prng = wprng; } } } // write to the file lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &sim_files[j]->file) => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; // 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 lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; lfsr_file_close(&lfs, &sim_files[j]->file) => 0; // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs_size_t j = TEST_PRNG(&prng) % sim_size; lfs_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); sim_size -= 1; // mark any related sim files as zombied for (lfs_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // delete this file char name[256]; sprintf(name, "batman%03x", x); lfsr_remove(&lfs, name) => 0; // 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 lfs_size_t j = TEST_PRNG(&prng) % sim_size; lfs_size_t x = sim[j]; lfs_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; // update our sim for (lfs_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(lfs_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(lfs_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(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); sim[k] = y; sim_prngs[k] = wprng; } break; } } // update any related sim files for (lfs_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; } } // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfsr_rename(&lfs, old_name, new_name) => 0; } } // check that disk matches our simulation for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs_info info; lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); } lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } // check that our file handles match our simulation for (lfs_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs free(sim); free(sim_prngs); for (lfs_size_t j = 0; j < sim_file_count; j++) { lfsr_file_close(&lfs, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfsr_unmount(&lfs) => 0; ''' # fuzz tests involving many uncreats + zombies + dirs, this gets a bit crazy [cases.test_forphans_uzd_fuzz] defines.N = [1, 2, 4, 8, 16, 32, 64] # do more ops than files to encourage file rewrites defines.OPS = '2*N' defines.SIZE = [ '0', 'FILE_BUFFER_SIZE/2', '2*FILE_BUFFER_SIZE', 'BLOCK_SIZE/2', 'BLOCK_SIZE', '2*BLOCK_SIZE', '4*BLOCK_SIZE', ] defines.SEED = 'range(20)' fuzz = 'SEED' if = '(SIZE*N)/BLOCK_SIZE <= 16' code = ''' lfs_t lfs; lfsr_format(&lfs, LFS_F_RDWR, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // set up a simulation to compare against lfs_size_t *sim = malloc(N*sizeof(lfs_size_t)); uint32_t *sim_prngs = malloc(N*sizeof(uint32_t)); bool *sim_isdirs = malloc(N*sizeof(bool)); lfs_size_t sim_size = 0; typedef struct sim_file { lfs_size_t x; bool uncreat; bool zombie; uint32_t prng; lfsr_file_t file; } sim_file_t; sim_file_t **sim_files = malloc(N*sizeof(sim_file_t*)); lfs_size_t sim_file_count = 0; uint32_t prng = SEED; for (lfs_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 lfs_size_t x = TEST_PRNG(&prng) % N; // already exists? bool uncreat = true; uint32_t wprng = 0; for (lfs_size_t j = 0; j < sim_size; j++) { if (sim[j] == x) { if (sim_isdirs[j]) { goto nonsense; } uncreat = false; wprng = sim_prngs[j]; break; } } // choose a random seed if we don't exist if (uncreat) { wprng = TEST_PRNG(&prng); } // open in our sim lfs_size_t j = sim_file_count; sim_files[j] = malloc(sizeof(sim_file_t)); sim_files[j]->x = x; sim_files[j]->uncreat = uncreat; sim_files[j]->zombie = false; sim_files[j]->prng = wprng; sim_file_count++; // open the actual file char name[256]; sprintf(name, "batman%03x", x); lfsr_file_open(&lfs, &sim_files[j]->file, name, LFS_O_RDWR | LFS_O_CREAT) => 0; // write some initial data if we don't exist if (uncreat) { uint8_t wbuf[SIZE]; for (lfs_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; } // write/rewrite a file? } else if (op == 1) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; lfs_size_t x = sim_files[j]->x; // choose a random seed uint32_t wprng = TEST_PRNG(&prng); // update sim sim_files[j]->prng = wprng; if (!sim_files[j]->zombie) { // insert into our sim for (lfs_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(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim_size += 1; sim[k] = x; sim_prngs[k] = wprng; sim_isdirs[k] = false; } break; } } // update related sim files for (lfs_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x && !sim_files[k]->zombie) { sim_files[k]->uncreat = false; sim_files[k]->prng = wprng; } } } // write to the file lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; uint8_t wbuf[SIZE]; for (lfs_size_t k = 0; k < SIZE; k++) { wbuf[k] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_write(&lfs, &sim_files[j]->file, wbuf, SIZE) => SIZE; lfsr_file_sync(&lfs, &sim_files[j]->file) => (!sim_files[j]->zombie) ? 0 : LFS_ERR_NOENT; // close a file? } else if (op == 2) { if (sim_file_count == 0) { goto nonsense; } // choose a random file handle lfs_size_t j = TEST_PRNG(&prng) % sim_file_count; // 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 lfsr_file_desync(&lfs, &sim_files[j]->file) => 0; lfsr_file_close(&lfs, &sim_files[j]->file) => 0; // remove from list free(sim_files[j]); sim_files[j] = sim_files[sim_file_count-1]; sim_file_count -= 1; // remove a file? } else if (op == 3) { if (sim_size == 0) { goto nonsense; } // choose a random file to delete lfs_size_t j = TEST_PRNG(&prng) % sim_size; lfs_size_t x = sim[j]; // delete from our sim memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); 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 (lfs_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // delete this file char name[256]; sprintf(name, "batman%03x", x); lfsr_remove(&lfs, name) => 0; // 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 lfs_size_t j = TEST_PRNG(&prng) % sim_size; lfs_size_t x = sim[j]; lfs_size_t y = TEST_PRNG(&prng) % N; uint32_t wprng = sim_prngs[j]; bool isdir = sim_isdirs[j]; // update our sim for (lfs_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] != isdir) { goto nonsense; } // delete the original entry memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); 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 sim_prngs[k] = wprng; // just renaming } else { // first delete memmove(&sim[j], &sim[j+1], (sim_size-(j+1))*sizeof(lfs_size_t)); memmove(&sim_prngs[j], &sim_prngs[j+1], (sim_size-(j+1))*sizeof(uint32_t)); 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(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); memmove(&sim_isdirs[k+1], &sim_isdirs[k], (sim_size-k)*sizeof(bool)); sim[k] = y; sim_prngs[k] = wprng; sim_isdirs[k] = isdir; } break; } } // update any related sim files for (lfs_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; } } // rename this file char old_name[256]; sprintf(old_name, "batman%03x", x); char new_name[256]; sprintf(new_name, "batman%03x", y); lfsr_rename(&lfs, old_name, new_name) => 0; // toss a directory into the mix } else if (op == 5) { // choose a pseudo-random number lfs_size_t x = TEST_PRNG(&prng) % N; // insert into our sim, use negative numbers for dirs for (lfs_size_t k = 0;; k++) { if (k >= sim_size || sim[k] >= x) { // already seen? if (k < sim_size && sim[k] == x) { goto nonsense; } else { // insert memmove(&sim[k+1], &sim[k], (sim_size-k)*sizeof(lfs_size_t)); memmove(&sim_prngs[k+1], &sim_prngs[k], (sim_size-k)*sizeof(uint32_t)); 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 (lfs_size_t k = 0; k < sim_file_count; k++) { if (sim_files[k]->x == x) { sim_files[k]->zombie = true; } } // make the directory char name[256]; sprintf(name, "batman%03x", x); lfsr_mkdir(&lfs, name) => 0; } } // check that disk matches our simulation for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); struct lfs_info info; lfsr_stat(&lfs, name, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS_TYPE_DIR); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); } } lfsr_dir_t dir; lfsr_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, ".") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, "..") == 0); assert(info.type == LFS_TYPE_DIR); assert(info.size == 0); for (lfs_size_t j = 0; j < sim_size; j++) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfsr_dir_read(&lfs, &dir, &info) => 0; assert(strcmp(info.name, name) == 0); if (sim_isdirs[j]) { assert(info.type == LFS_TYPE_DIR); } else { assert(info.type == LFS_TYPE_REG); assert(info.size == SIZE); } } lfsr_dir_read(&lfs, &dir, &info) => LFS_ERR_NOENT; lfsr_dir_close(&lfs, &dir) => 0; for (lfs_size_t j = 0; j < sim_size; j++) { if (sim_isdirs[j]) { char name[256]; sprintf(name, "batman%03x", sim[j]); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => LFS_ERR_ISDIR; } else { char name[256]; sprintf(name, "batman%03x", sim[j]); lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; uint32_t wprng = sim_prngs[j]; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } // check that our file handles match our simulation for (lfs_size_t j = 0; j < sim_file_count; j++) { uint32_t wprng = sim_files[j]->prng; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&wprng) % 26); } lfsr_file_rewind(&lfs, &sim_files[j]->file) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &sim_files[j]->file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); } // clean up sim/lfs free(sim); free(sim_prngs); for (lfs_size_t j = 0; j < sim_file_count; j++) { lfsr_file_close(&lfs, &sim_files[j]->file) => 0; free(sim_files[j]); } free(sim_files); lfsr_unmount(&lfs) => 0; '''