# Test for compatibility between different littlefs versions # # Note, these tests are a bit special. They expect to be linked against two # different versions of littlefs: # - lfs => the new/current version of littlefs # - lfsp => the previous version of littlefs # # If lfsp is not linked, and LFSP is not defined, these tests will alias # the relevant lfs types/functions as necessary so at least the tests can # themselves be tested locally. # # But to get value from these tests, it's expected that the previous version # of littlefs be linked in during CI, with the help of scripts/changeprefix.py # # alias littlefs symbols as needed # # there may be a better way to do this, but oh well, explicit aliases works code = ''' #ifdef LFSP #define STRINGIZE(x) STRINGIZE_(x) #define STRINGIZE_(x) #x #include STRINGIZE(LFSP) #else #define LFSP_DISK_VERSION LFS_DISK_VERSION #define LFSP_DISK_VERSION_MAJOR LFS_DISK_VERSION_MAJOR #define LFSP_DISK_VERSION_MINOR LFS_DISK_VERSION_MINOR #define lfsp_t lfs_t #define lfsp_config lfs_config #define lfsp_format lfs_format #define lfsp_mount lfs_mount #define lfsp_unmount lfs_unmount #define lfsp_fsinfo lfs_fsinfo #define lfsp_fs_stat lfs_fs_stat #define lfsp_dir_t lfs_dir_t #define lfsp_info lfs_info #define LFSP_TYPE_REG LFS_TYPE_REG #define LFSP_TYPE_DIR LFS_TYPE_DIR #define lfsp_mkdir lfs_mkdir #define lfsp_dir_open lfs_dir_open #define lfsp_dir_read lfs_dir_read #define lfsp_dir_close lfs_dir_close #define lfsp_file_t lfs_file_t #define LFSP_O_RDONLY LFS_O_RDONLY #define LFSP_O_WRONLY LFS_O_WRONLY #define LFSP_O_CREAT LFS_O_CREAT #define LFSP_O_EXCL LFS_O_EXCL #define LFSP_SEEK_SET LFS_SEEK_SET #define lfsp_file_open lfs_file_open #define lfsp_file_write lfs_file_write #define lfsp_file_read lfs_file_read #define lfsp_file_seek lfs_file_seek #define lfsp_file_close lfs_file_close #endif ''' ## forward-compatibility tests ## # test we can mount in a new version [cases.test_compat_forward_mount] if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // confirm the previous mount works lfsp_mount(&lfsp, &cfgp) => 0; lfsp_unmount(&lfsp) => 0; // now test the new mount lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); lfs_unmount(&lfs) => 0; ''' # test we can read dirs in a new version [cases.test_compat_forward_read_dirs] defines.COUNT = 5 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write COUNT dirs lfsp_mount(&lfsp, &cfgp) => 0; for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfsp_mkdir(&lfsp, name) => 0; } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; lfs_unmount(&lfs) => 0; ''' # test we can read files in a new version [cases.test_compat_forward_read_files] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write COUNT files lfsp_mount(&lfsp, &cfgp) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_file_t file; char name[8]; sprintf(name, "file%03d", i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the files? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_REG); char name[8]; sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); } lfs_dir_read(&lfs, &dir, &info) => 0; // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfs_file_t file; char name[8]; sprintf(name, "file%03d", i); lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; ''' # test we can read files in dirs in a new version [cases.test_compat_forward_read_files_in_dirs] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write COUNT files+dirs lfsp_mount(&lfsp, &cfgp) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { char name[16]; sprintf(name, "dir%03d", i); lfsp_mkdir(&lfsp, name) => 0; lfsp_file_t file; sprintf(name, "dir%03d/file%03d", i, i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; // can we list the files? for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfs_dir_t dir; lfs_dir_open(&lfs, &dir, name) => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_REG); sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; } // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfs_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; ''' # test we can write dirs in a new version [cases.test_compat_forward_write_dirs] defines.COUNT = 10 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write COUNT/2 dirs lfsp_mount(&lfsp, &cfgp) => 0; for (lfs_size_t i = 0; i < COUNT/2; i++) { char name[8]; sprintf(name, "dir%03d", i); lfsp_mkdir(&lfsp, name) => 0; } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfs_mkdir(&lfs, name) => 0; } // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; lfs_unmount(&lfs) => 0; ''' # test we can write files in a new version [cases.test_compat_forward_write_files] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write half COUNT files lfsp_mount(&lfsp, &cfgp) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // write half lfsp_file_t file; char name[8]; sprintf(name, "file%03d", i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; // skip the other half but keep our prng reproducible for (lfs_size_t j = SIZE/2; j < SIZE; j++) { TEST_PRNG(&prng); } } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // skip half but keep our prng reproducible for (lfs_size_t j = 0; j < SIZE/2; j++) { TEST_PRNG(&prng); } // write the other half lfs_file_t file; char name[8]; sprintf(name, "file%03d", i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0; lfs_file_seek(&lfs, &file, SIZE/2, LFS_SEEK_SET) => SIZE/2; for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; } // can we list the files? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_REG); char name[8]; sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); } lfs_dir_read(&lfs, &dir, &info) => 0; // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfs_file_t file; char name[8]; sprintf(name, "file%03d", i); lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; ''' # test we can write files in dirs in a new version [cases.test_compat_forward_write_files_in_dirs] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 if = ''' LFS_DISK_VERSION_MAJOR == LFSP_DISK_VERSION_MAJOR && DISK_VERSION == 0 ''' code = ''' // create the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_format(&lfsp, &cfgp) => 0; // write half COUNT files lfsp_mount(&lfsp, &cfgp) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { char name[16]; sprintf(name, "dir%03d", i); lfsp_mkdir(&lfsp, name) => 0; // write half lfsp_file_t file; sprintf(name, "dir%03d/file%03d", i, i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY | LFSP_O_CREAT | LFSP_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; // skip the other half but keep our prng reproducible for (lfs_size_t j = SIZE/2; j < SIZE; j++) { TEST_PRNG(&prng); } } lfsp_unmount(&lfsp) => 0; // mount the new version lfs_t lfs; lfs_mount(&lfs, cfg) => 0; // we should be able to read the version using lfs_fs_stat struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFSP_DISK_VERSION); // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // skip half but keep our prng reproducible for (lfs_size_t j = 0; j < SIZE/2; j++) { TEST_PRNG(&prng); } // write the other half lfs_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY) => 0; lfs_file_seek(&lfs, &file, SIZE/2, LFS_SEEK_SET) => SIZE/2; for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; } // can we list the directories? lfs_dir_t dir; lfs_dir_open(&lfs, &dir, "/") => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; // can we list the files? for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfs_dir_t dir; lfs_dir_open(&lfs, &dir, name) => 0; struct lfs_info info; lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_DIR); assert(strcmp(info.name, "..") == 0); lfs_dir_read(&lfs, &dir, &info) => 1; assert(info.type == LFS_TYPE_REG); sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_close(&lfs, &dir) => 0; } // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfs_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfs_file_open(&lfs, &file, name, LFS_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfs_file_read(&lfs, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; ''' ## backwards-compatibility tests ## # test we can mount in an old version [cases.test_compat_backward_mount] if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the new version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // confirm the new mount works lfs_mount(&lfs, cfg) => 0; lfs_unmount(&lfs) => 0; // now test the previous mount struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; lfsp_unmount(&lfsp) => 0; ''' # test we can read dirs in an old version [cases.test_compat_backward_read_dirs] defines.COUNT = 5 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the new version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write COUNT dirs lfs_mount(&lfs, cfg) => 0; for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfs_mkdir(&lfs, name) => 0; } lfs_unmount(&lfs) => 0; // mount the new version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; lfsp_unmount(&lfsp) => 0; ''' # test we can read files in an old version [cases.test_compat_backward_read_files] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the new version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write COUNT files lfs_mount(&lfs, cfg) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfs_file_t file; char name[8]; sprintf(name, "file%03d", i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; // mount the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // can we list the files? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_REG); char name[8]; sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); } lfsp_dir_read(&lfsp, &dir, &info) => 0; // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_file_t file; char name[8]; sprintf(name, "file%03d", i); lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; ''' # test we can read files in dirs in an old version [cases.test_compat_backward_read_files_in_dirs] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 4 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the new version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write COUNT files+dirs lfs_mount(&lfs, cfg) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { char name[16]; sprintf(name, "dir%03d", i); lfs_mkdir(&lfs, name) => 0; lfs_file_t file; sprintf(name, "dir%03d/file%03d", i, i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; } lfs_unmount(&lfs) => 0; // mount the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; // can we list the files? for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, name) => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_REG); sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; } // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; ''' # test we can write dirs in an old version [cases.test_compat_backward_write_dirs] defines.COUNT = 10 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the new version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write COUNT/2 dirs lfs_mount(&lfs, cfg) => 0; for (lfs_size_t i = 0; i < COUNT/2; i++) { char name[8]; sprintf(name, "dir%03d", i); lfs_mkdir(&lfs, name) => 0; } lfs_unmount(&lfs) => 0; // mount the previous version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // write another COUNT/2 dirs for (lfs_size_t i = COUNT/2; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfsp_mkdir(&lfsp, name) => 0; } // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; lfsp_unmount(&lfsp) => 0; ''' # test we can write files in an old version [cases.test_compat_backward_write_files] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the previous version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write half COUNT files lfs_mount(&lfs, cfg) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // write half lfs_file_t file; char name[8]; sprintf(name, "file%03d", i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; // skip the other half but keep our prng reproducible for (lfs_size_t j = SIZE/2; j < SIZE; j++) { TEST_PRNG(&prng); } } lfs_unmount(&lfs) => 0; // mount the new version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // skip half but keep our prng reproducible for (lfs_size_t j = 0; j < SIZE/2; j++) { TEST_PRNG(&prng); } // write the other half lfsp_file_t file; char name[8]; sprintf(name, "file%03d", i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY) => 0; lfsp_file_seek(&lfsp, &file, SIZE/2, LFSP_SEEK_SET) => SIZE/2; for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; } // can we list the files? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_REG); char name[8]; sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); } lfsp_dir_read(&lfsp, &dir, &info) => 0; // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_file_t file; char name[8]; sprintf(name, "file%03d", i); lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; ''' # test we can write files in dirs in an old version [cases.test_compat_backward_write_files_in_dirs] defines.COUNT = 5 defines.SIZE = [4, 32, 512, 8192] defines.CHUNK = 2 if = ''' LFS_DISK_VERSION == LFSP_DISK_VERSION && DISK_VERSION == 0 ''' code = ''' // create the previous version lfs_t lfs; lfs_format(&lfs, cfg) => 0; // write half COUNT files lfs_mount(&lfs, cfg) => 0; uint32_t prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { char name[16]; sprintf(name, "dir%03d", i); lfs_mkdir(&lfs, name) => 0; // write half lfs_file_t file; sprintf(name, "dir%03d/file%03d", i, i); lfs_file_open(&lfs, &file, name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; for (lfs_size_t j = 0; j < SIZE/2; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfs_file_write(&lfs, &file, chunk, CHUNK) => CHUNK; } lfs_file_close(&lfs, &file) => 0; // skip the other half but keep our prng reproducible for (lfs_size_t j = SIZE/2; j < SIZE; j++) { TEST_PRNG(&prng); } } lfs_unmount(&lfs) => 0; // mount the new version struct lfsp_config cfgp; memcpy(&cfgp, cfg, sizeof(cfgp)); lfsp_t lfsp; lfsp_mount(&lfsp, &cfgp) => 0; // write half COUNT files prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { // skip half but keep our prng reproducible for (lfs_size_t j = 0; j < SIZE/2; j++) { TEST_PRNG(&prng); } // write the other half lfsp_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfsp_file_open(&lfsp, &file, name, LFSP_O_WRONLY) => 0; lfsp_file_seek(&lfsp, &file, SIZE/2, LFSP_SEEK_SET) => SIZE/2; for (lfs_size_t j = SIZE/2; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; for (lfs_size_t k = 0; k < CHUNK; k++) { chunk[k] = TEST_PRNG(&prng) & 0xff; } lfsp_file_write(&lfsp, &file, chunk, CHUNK) => CHUNK; } lfsp_file_close(&lfsp, &file) => 0; } // can we list the directories? lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, "/") => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); char name[8]; sprintf(name, "dir%03d", i); assert(strcmp(info.name, name) == 0); } lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; // can we list the files? for (lfs_size_t i = 0; i < COUNT; i++) { char name[8]; sprintf(name, "dir%03d", i); lfsp_dir_t dir; lfsp_dir_open(&lfsp, &dir, name) => 0; struct lfsp_info info; lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, ".") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_DIR); assert(strcmp(info.name, "..") == 0); lfsp_dir_read(&lfsp, &dir, &info) => 1; assert(info.type == LFSP_TYPE_REG); sprintf(name, "file%03d", i); assert(strcmp(info.name, name) == 0); assert(info.size == SIZE); lfsp_dir_read(&lfsp, &dir, &info) => 0; lfsp_dir_close(&lfsp, &dir) => 0; } // now can we read the files? prng = 42; for (lfs_size_t i = 0; i < COUNT; i++) { lfsp_file_t file; char name[16]; sprintf(name, "dir%03d/file%03d", i, i); lfsp_file_open(&lfsp, &file, name, LFSP_O_RDONLY) => 0; for (lfs_size_t j = 0; j < SIZE; j += CHUNK) { uint8_t chunk[CHUNK]; lfsp_file_read(&lfsp, &file, chunk, CHUNK) => CHUNK; for (lfs_size_t k = 0; k < CHUNK; k++) { assert(chunk[k] == TEST_PRNG(&prng) & 0xff); } } lfsp_file_close(&lfsp, &file) => 0; } lfsp_unmount(&lfsp) => 0; ''' ## incompatiblity tests ## # test that we fail to mount after a major version bump [cases.test_compat_major_incompat] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfs_format(&lfs, cfg) => 0; // bump the major version // // note we're messing around with internals to do this! this // is not a user API lfs_mount(&lfs, cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_superblock_t superblock = { .version = LFS_DISK_VERSION + 0x00010000, .block_size = lfs.cfg->block_size, .block_count = lfs.cfg->block_count, .name_max = lfs.name_max, .file_max = lfs.file_max, .attr_max = lfs.attr_max, }; lfs_superblock_tole32(&superblock); lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})) => 0; lfs_unmount(&lfs) => 0; // mount should now fail lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' # test that we fail to mount after a minor version bump [cases.test_compat_minor_incompat] in = 'lfs.c' code = ''' // create a superblock lfs_t lfs; lfs_format(&lfs, cfg) => 0; // bump the minor version // // note we're messing around with internals to do this! this // is not a user API lfs_mount(&lfs, cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_superblock_t superblock = { .version = LFS_DISK_VERSION + 0x00000001, .block_size = lfs.cfg->block_size, .block_count = lfs.cfg->block_count, .name_max = lfs.name_max, .file_max = lfs.file_max, .attr_max = lfs.attr_max, }; lfs_superblock_tole32(&superblock); lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})) => 0; lfs_unmount(&lfs) => 0; // mount should now fail lfs_mount(&lfs, cfg) => LFS_ERR_INVAL; ''' # test that we correctly bump the minor version [cases.test_compat_minor_bump] in = 'lfs.c' if = ''' LFS_DISK_VERSION_MINOR > 0 && DISK_VERSION == 0 ''' code = ''' // create a superblock lfs_t lfs; lfs_format(&lfs, cfg) => 0; lfs_mount(&lfs, cfg) => 0; lfs_file_t file; lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0; lfs_file_write(&lfs, &file, "testtest", 8) => 8; lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; // write an old minor version // // note we're messing around with internals to do this! this // is not a user API lfs_mount(&lfs, cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_superblock_t superblock = { .version = LFS_DISK_VERSION - 0x00000001, .block_size = lfs.cfg->block_size, .block_count = lfs.cfg->block_count, .name_max = lfs.name_max, .file_max = lfs.file_max, .attr_max = lfs.attr_max, }; lfs_superblock_tole32(&superblock); lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), &superblock})) => 0; lfs_unmount(&lfs) => 0; // mount should still work lfs_mount(&lfs, cfg) => 0; struct lfs_fsinfo fsinfo; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; uint8_t buffer[8]; lfs_file_read(&lfs, &file, buffer, 8) => 8; assert(memcmp(buffer, "testtest", 8) == 0); lfs_file_close(&lfs, &file) => 0; // minor version should be unchanged lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_unmount(&lfs) => 0; // if we write, we need to bump the minor version lfs_mount(&lfs, cfg) => 0; lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION-1); lfs_file_open(&lfs, &file, "test", LFS_O_WRONLY | LFS_O_TRUNC) => 0; lfs_file_write(&lfs, &file, "teeeeest", 8) => 8; lfs_file_close(&lfs, &file) => 0; // minor version should be changed lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_unmount(&lfs) => 0; // and of course mount should still work lfs_mount(&lfs, cfg) => 0; // minor version should have changed lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_file_open(&lfs, &file, "test", LFS_O_RDONLY) => 0; lfs_file_read(&lfs, &file, buffer, 8) => 8; assert(memcmp(buffer, "teeeeest", 8) == 0); lfs_file_close(&lfs, &file) => 0; // yep, still changed lfs_fs_stat(&lfs, &fsinfo) => 0; assert(fsinfo.disk_version == LFS_DISK_VERSION); lfs_unmount(&lfs) => 0; '''