# Test checksum validation things after = ['test_traversal', 'test_gc', 'test_mount'] # Test filesystem-level checksum things # test we can detect at least fully clobbered blocks [cases.test_ck_ckmeta_easy] # METHOD=0 => lfsr_fs_ckmeta # METHOD=1 => lfsr_fs_gc # METHOD=2 => lfsr_traversal_read # METHOD=3 => lfsr_mount defines.METHOD = [0, 1, 2, 3] 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', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // find clobbered blocks with lfsr_fs_ckmeta if (METHOD == 0) { lfsr_fs_ckmeta(&lfs) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_fs_gc } else if (METHOD == 1) { lfsr_fs_gc(&lfs, -1, LFS_GC_CKMETA) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_traversal_read } else if (METHOD == 2) { lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_CKMETA) => 0; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { break; } } lfsr_traversal_close(&lfs, &t) => 0; // find clobbered blocks with lfsr_mount } else if (METHOD == 3) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKMETA, CFG) => LFS_ERR_CORRUPT; } else { assert(false); } if (METHOD != 3) { lfsr_unmount(&lfs) => 0; } } done:; ''' [cases.test_ck_ckdata_easy] # METHOD=0 => lfsr_fs_ckdata # METHOD=1 => lfsr_fs_gc # METHOD=2 => lfsr_traversal_read # METHOD=3 => lfsr_mount defines.METHOD = [0, 1, 2, 3] 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', '8*BLOCK_SIZE', ] if = '(SIZE*N)/BLOCK_SIZE <= 32' code = ''' lfs_block_t i = 0; while (true) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting filesystem uint32_t prng = 42; for (lfs_size_t i = 0; i < N; i++) { char name[256]; sprintf(name, "squid%03x", i); uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfsr_file_t file; lfsr_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfsr_file_write(&lfs, &file, wbuf, SIZE) => SIZE; lfsr_file_close(&lfs, &file) => 0; } // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; goto done; } // this gets a bit tricky be cause we need to clobber both // blocks in mdir pairs if (tinfo.btype == LFS_BTYPE_MDIR || tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_BTYPE_DATA) { if (k == i || k == i+1) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; if (tinfo.btype != LFS_BTYPE_MDIR || k == i+1) { i += (tinfo.btype == LFS_BTYPE_MDIR) ? 2 : 1; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } } k += 1; } } clobbered:; // find clobbered blocks with lfsr_fs_ckmeta if (METHOD == 0) { lfsr_fs_ckdata(&lfs) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_fs_gc } else if (METHOD == 1) { lfsr_fs_gc(&lfs, -1, LFS_GC_CKDATA) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_traversal_read } else if (METHOD == 2) { lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, LFS_T_CKDATA) => 0; for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops LFS_ASSERT(i < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { break; } } lfsr_traversal_close(&lfs, &t) => 0; // find clobbered blocks with lfsr_mount } else if (METHOD == 3) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKDATA, CFG) => LFS_ERR_CORRUPT; } else { assert(false); } if (METHOD != 3) { lfsr_unmount(&lfs) => 0; } } done:; ''' # Test file-level checksum things # test we can detect at least fully clobbered blocks [cases.test_ck_file_ckmeta_easy] # METHOD=0 => lfsr_file_ckmeta # METHOD=1 => lfsr_file_close+open+ckmeta # METHOD=2 => lfsr_file_close+open defines.METHOD = [0, 1] 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', '8*BLOCK_SIZE', ] code = ''' for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting file uint32_t prng = 42; lfsr_file_t file; lfsr_file_open(&lfs, &file, "octopus", 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, &file, wbuf, SIZE) => SIZE; // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; goto done; } if (tinfo.btype == LFS_BTYPE_BTREE) { if (k == i) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } k += 1; } } clobbered:; // find clobbered blocks with lfsr_file_ckmeta if (METHOD == 0) { lfsr_file_ckmeta(&lfs, &file) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_file_close+open+ckmeta } else if (METHOD == 1) { lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; lfsr_file_ckmeta(&lfs, &file) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_file_close+open } else if (METHOD == 2) { lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY | LFS_O_CKMETA) => LFS_ERR_CORRUPT; } else { assert(false); } if (METHOD != 2) { lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; } done:; ''' # test we can detect at least fully clobbered blocks [cases.test_ck_file_ckdata_easy] # METHOD=0 => lfsr_file_ckdata # METHOD=1 => lfsr_file_close+open+ckdata # METHOD=2 => lfsr_file_close+open defines.METHOD = [0, 1] 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', '8*BLOCK_SIZE', ] code = ''' for (lfs_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR, CFG) => 0; // create an interesting file uint32_t prng = 42; lfsr_file_t file; lfsr_file_open(&lfs, &file, "octopus", 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, &file, wbuf, SIZE) => SIZE; // traverse to find blocks lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t k = 0; for (lfs_block_t j = 0;; j++) { assert(j < 2*BLOCK_COUNT); struct lfs_tinfo tinfo; int err = lfsr_traversal_read(&lfs, &t, &tinfo); assert(!err || err == LFS_ERR_NOENT); if (err == LFS_ERR_NOENT) { lfsr_traversal_close(&lfs, &t) => 0; lfsr_file_close(&lfs, &file) => 0; lfsr_unmount(&lfs) => 0; goto done; } if (tinfo.btype == LFS_BTYPE_BTREE || tinfo.btype == LFS_BTYPE_DATA) { if (k == i) { // clobber this block printf("clobbering 0x%x\n", tinfo.block); uint8_t clobber_buf[BLOCK_SIZE]; memset(clobber_buf, 0xcc, BLOCK_SIZE); CFG->erase(CFG, tinfo.block) => 0; CFG->prog(CFG, tinfo.block, 0, clobber_buf, BLOCK_SIZE) => 0; lfsr_traversal_close(&lfs, &t) => 0; goto clobbered; } k += 1; } } clobbered:; // find clobbered blocks with lfsr_file_ckmeta if (METHOD == 0) { lfsr_file_ckdata(&lfs, &file) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_file_close+open+ckmeta } else if (METHOD == 1) { lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY) => 0; lfsr_file_ckdata(&lfs, &file) => LFS_ERR_CORRUPT; // find clobbered blocks with lfsr_file_close+open } else if (METHOD == 2) { lfsr_file_close(&lfs, &file) => 0; lfsr_file_open(&lfs, &file, "octopus", LFS_O_RDONLY | LFS_O_CKDATA) => LFS_ERR_CORRUPT; } else { assert(false); } if (METHOD != 2) { lfsr_file_close(&lfs, &file) => 0; } lfsr_unmount(&lfs) => 0; } done:; ''' # Some simple ckprog tests # # We test these much more aggressively in test_badblocks # test every single-bit error in block 0/1 [cases.test_ck_ckprogs_mroot] defines.BADBLOCK = [0, 1] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' # this should stay inlined defines.SIZE = 'BLOCK_SIZE/16' code = ''' // test all bad bits in the mroot for (lfs_size_t i = 0; i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // mark our badbit as bad lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // formatting the filesystem may already find the bit error lfs_t lfs; int err = lfsr_format(&lfs, CFG); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { goto corrupt; } lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; { // create a file lfsr_file_t file; err = lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { goto corrupt_mounted; } err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // if we made it here without erroring we should be able to // read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; } lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; corrupt:; // reset badbit lfs_emubd_markgood(CFG, BADBLOCK) => 0; } ''' # test every single-bit error in a file's data block [cases.test_ck_ckprogs_data] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' # this should create a single block file defines.SIZE = 'BLOCK_SIZE' code = ''' // first we need to figure out where the data block will actually // end up, fortunately our block randomization is intentionally // consistent // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; 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; // find the data block lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t badblock; while (true) { struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; if (tinfo.btype == LFS_BTYPE_DATA) { badblock = tinfo.block; break; } } lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; // now test all bad bits in the data block for (lfs_size_t i = 0; i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; { // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } int err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // if we made it here without erroring we should be able to // read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; } lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; // reset badbit lfs_emubd_markgood(CFG, badblock) => 0; } ''' # test every single-bit error in a file's btree node [cases.test_ck_ckprogs_btree] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = 'LFS_EMUBD_BADBLOCK_PROGFLIP' # force the file to create a btree defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' defines.SIZE = '2*FRAGMENT_SIZE' code = ''' // first we need to figure out where the btree block will actually // end up, fortunately our block randomization is intentionally // consistent // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; 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; // find the btree block lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t badblock; while (true) { struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; if (tinfo.btype == LFS_BTYPE_BTREE) { badblock = tinfo.block; break; } } lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; // now test all bad bits in the btree block for (lfs_size_t i = 0; i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; { // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } int err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // if we made it here without erroring we should be able to // read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKPROGS, CFG) => 0; } lfsr_file_open(&lfs, &file, "stygiomedusa", LFS_O_RDONLY) => 0; uint8_t rbuf[SIZE]; lfsr_file_read(&lfs, &file, rbuf, SIZE) => SIZE; assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; // reset badbit lfs_emubd_markgood(CFG, badblock) => 0; } ''' # Some simple ckread tests # # We test these much more aggressively in test_badblocks # These tests were originally intended to test all single-bit # metastability errors with ckreads, however they quickly found that # ckreads can't actually guarantee single-bit error-detection since # the bit flip may alter the leb128 encoded size field and find a new, # erronous, parity bit. # # For example, one bit flip: # # 40 0c 00 12 80 0d ff ff # '----.----' ^--------------------. # '- altble 0xc w0 -18 parity=1 # # 40 0c 80 12 80 0d ff ff # '-------.-------' ^----------------------. # '- altble 0xc w2304 -1664 parity=1 # # This doesn't make ckreads _completely_ useless, just mostly useless. # We can still use it to check parity bits, but without a systematic # proof. # # So for now these tests are sort of in stasis, limited to testing # metastability in areas we know we can detect (revision counts, raw # data blocks, etc). Maybe future features will make them more useful. # # test every single-bit error in block 0/1 [cases.test_ck_ckreads_mroot] defines.BADBLOCK = [0, 1] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGFLIP', 'LFS_EMUBD_BADBLOCK_READFLIP', ] # this should stay inlined defines.SIZE = 'BLOCK_SIZE/16' ifdef = 'LFS_CKREADS' code = ''' // test all bad bits in the mroot for (lfs_size_t i = 0; // we can't detect metastable tags, so limit read-flips // to our revision count i < ((BADBIT == -1) ? 8*4 : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility lfs_emubd_seed(CFG, 42) => 0; // mark our badbit as bad lfs_emubd_markbadbit(CFG, BADBLOCK, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", (lfs_size_t)BADBLOCK, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing // for is no internal/external asserts failing. // format lfs_t lfs; int err = lfsr_format(&lfs, CFG); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { goto corrupt; } err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { goto corrupt; } { // create a file lfsr_file_t file; err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL); assert(!err || err == LFS_ERR_CORRUPT); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // try to read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; err = lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG); if (err == LFS_ERR_CORRUPT) { goto corrupt; } } // yes reads can fail here err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); assert(!err || err == LFS_ERR_CORRUPT // metastability can also cause our fs state to "rollback", // which is not great but we can't solve this with ckreads // alone || err == LFS_ERR_NOENT); if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { goto corrupt_mounted; } uint8_t rbuf[SIZE]; lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; corrupt:; // reset badbit lfs_emubd_markgood(CFG, BADBLOCK) => 0; } ''' # test every single-bit error in a file's data block [cases.test_ck_ckreads_data] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGFLIP', 'LFS_EMUBD_BADBLOCK_READFLIP', ] # this should create a single block file defines.SIZE = 'BLOCK_SIZE' ifdef = 'LFS_CKREADS' code = ''' // first we need to figure out where the data block will actually // end up, fortunately our block randomization is intentionally // consistent // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; 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; // find the data block lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t badblock; while (true) { struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; if (tinfo.btype == LFS_BTYPE_DATA) { badblock = tinfo.block; break; } } lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; // now test all bad bits in the data block for (lfs_size_t i = 0; i < ((BADBIT == -1) ? 8*BLOCK_SIZE : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility lfs_emubd_seed(CFG, 42) => 0; // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing // for is no internal/external asserts failing. // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; { // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } int err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // try to read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; } // yes reads can fail here err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); assert(!err || err == LFS_ERR_CORRUPT // metastability can also cause our fs state to "rollback", // which is not great but we can't solve this with ckreads // alone || err == LFS_ERR_NOENT); if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { goto corrupt_mounted; } uint8_t rbuf[SIZE]; lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; // reset badbit lfs_emubd_markgood(CFG, badblock) => 0; } ''' # test every single-bit error in a file's btree node [cases.test_ck_ckreads_btree] defines.BADBIT = -1 defines.BADBLOCK_BEHAVIOR = [ 'LFS_EMUBD_BADBLOCK_PROGFLIP', 'LFS_EMUBD_BADBLOCK_READFLIP', ] # force the file to create a btree defines.INLINE_SIZE = 0 defines.CRYSTAL_THRESH = -1 defines.FRAGMENT_SIZE = 'BLOCK_SIZE/8' defines.SIZE = '2*FRAGMENT_SIZE' ifdef = 'LFS_CKREADS' code = ''' // first we need to figure out where the btree block will actually // end up, fortunately our block randomization is intentionally // consistent // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; 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; // find the btree block lfsr_traversal_t t; lfsr_traversal_open(&lfs, &t, 0) => 0; lfs_block_t badblock; while (true) { struct lfs_tinfo tinfo; lfsr_traversal_read(&lfs, &t, &tinfo) => 0; if (tinfo.btype == LFS_BTYPE_BTREE) { badblock = tinfo.block; break; } } lfsr_traversal_close(&lfs, &t) => 0; lfsr_unmount(&lfs) => 0; // now test all bad bits in the btree block for (lfs_size_t i = 0; // we can't detect metastable tags, so limit read-flips // to our revision count i < ((BADBIT == -1) ? 8*4 : 1); i++) { lfs_size_t badbit = (BADBIT == -1) ? i : BADBIT; // reset the bd prng every run for reproducibility lfs_emubd_seed(CFG, 42) => 0; // mark our badbit as bad lfs_emubd_markbadbit(CFG, badblock, badbit) => 0; printf("--- badblock: 0x%x.%x, badbit: 0x%x (0x%x+%x) ---\n", badblock, badbit/8, badbit, badbit/8, badbit%8); // With metastability, basically any filesystem operation can // return LFS_ERR_CORRUPT. This is ok, what we're really testing // for is no internal/external asserts failing. // format lfs_t lfs; lfsr_format(&lfs, CFG) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; { // create a file lfsr_file_t file; lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; uint32_t prng = 42; uint8_t wbuf[SIZE]; for (lfs_size_t j = 0; j < SIZE; j++) { wbuf[j] = 'a' + (TEST_PRNG(&prng) % 26); } lfs_ssize_t res = lfsr_file_write(&lfs, &file, wbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } int err = lfsr_file_close(&lfs, &file); if (err == LFS_ERR_CORRUPT) { goto corrupt_mounted; } // try to read our file for (int remount = 0; remount < 2; remount++) { // remount? if (remount) { lfsr_unmount(&lfs) => 0; lfsr_mount(&lfs, LFS_M_RDWR | LFS_M_CKREADS, CFG) => 0; } // yes reads can fail here err = lfsr_file_open(&lfs, &file, "bathykorus", LFS_O_RDONLY); assert(!err || err == LFS_ERR_CORRUPT // metastability can also cause our fs state to "rollback", // which is not great but we can't solve this with ckreads // alone || err == LFS_ERR_NOENT); if (err == LFS_ERR_CORRUPT || err == LFS_ERR_NOENT) { goto corrupt_mounted; } uint8_t rbuf[SIZE]; lfs_ssize_t res = lfsr_file_read(&lfs, &file, rbuf, SIZE); assert(res == SIZE || res == LFS_ERR_CORRUPT); if (res == LFS_ERR_CORRUPT) { lfsr_file_close(&lfs, &file) => 0; goto corrupt_mounted; } assert(memcmp(rbuf, wbuf, SIZE) == 0); lfsr_file_close(&lfs, &file) => 0; } } corrupt_mounted:; lfsr_unmount(&lfs) => 0; // reset badbit lfs_emubd_markgood(CFG, badblock) => 0; } '''