Files
littlefs/tests/test_incompat.toml
T
Christopher Haster c72e3b24da Made unknown file types a hard mount error, reverting
I realized we really can't do anything if we find a file of unknown
type... If we don't understand a file's data structure, we can't really
do any bookkeeping. Allocating new blocks will probably corrupt unknown
files since we can't traverse any related B-trees, and mdir compaction
would be an absolute mess.

So, instead, just print an error and bail during mount.

Eventually we could at least fallback to readonly mode, but this is
currently a TODO item.

This also means the LFS_ERR_NOTSUP logic in lfsr_mtree_pathlookup is no
longer needed. Since, even with readonly fallback, we should never
mutate a filesystem with unknown file types.

Maybe in the future we could have a sort of known-but-not-supported mode
for file types? So special file types could not be support, but at least
understood enough to support traversal/remove/rename/etc?

Code changes:

           code          stack
  before: 33694           2592
  after:  33674 (-0.1%)   2592 (+0.0%)
2024-06-10 03:31:06 -05:00

357 lines
10 KiB
TOML

# Test (in)compatibility-related things
#
# Unlike test_compat, this focuses on specific corner-cases
#
after = ['test_mtree', 'test_dirs', 'test_files']
## incompatiblity tests ##
# test that we fail to mount after a major version bump
[cases.test_incompat_major]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// bump the major version
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_VERSION, 0,
LFSR_DATA_BUF(((const uint8_t[2]){
LFS_DISK_VERSION_MAJOR+1,
0}), 2)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount after a minor version bump
[cases.test_incompat_minor]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// bump the minor version
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_VERSION, 0,
LFSR_DATA_BUF(((const uint8_t[2]){
LFS_DISK_VERSION_MAJOR,
LFS_DISK_VERSION_MINOR+1}), 2)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount incompatible rcompat flags
[cases.test_incompat_rcompat]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set the nonstandard rcompat flag, this will always be incompatible
// with standard littlefs
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_RCOMPAT, 0,
LFSR_DATA_RCOMPAT(
LFSR_RCOMPAT_COMPAT
| LFSR_RCOMPAT_NONSTANDARD)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount incompatible wcompat flags
[cases.test_incompat_wcompat]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set the nonstandard rcompat flag, this will always be incompatible
// with standard littlefs
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_WCOMPAT, 0,
LFSR_DATA_WCOMPAT(
LFSR_WCOMPAT_COMPAT
| LFSR_WCOMPAT_NONSTANDARD)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that an incompatible ocompat flag is a noop
[cases.test_incompat_ocompat]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set the nonstandard ocompat flag, this will always be incompatible
// with standard littlefs
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_OCOMPAT, 0,
LFSR_DATA_OCOMPAT(
LFSR_OCOMPAT_COMPAT
| LFSR_OCOMPAT_NONSTANDARD)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should _not_ fail, ocompat should always be ignored
lfsr_mount(&lfs, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# these are just a bit harder to detect
[cases.test_incompat_rcompat_overflow]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set a really far rcompat flag
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR_CAT(
LFSR_TAG_RCOMPAT, 0,
LFSR_DATA_RCOMPAT(LFSR_RCOMPAT_COMPAT),
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
[cases.test_incompat_wcompat_overflow]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set a really far wcompat flag
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR_CAT(
LFSR_TAG_WCOMPAT, 0,
LFSR_DATA_WCOMPAT(LFSR_WCOMPAT_COMPAT),
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
[cases.test_incompat_ocompat_overflow]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set a really far ocompat flag
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR_CAT(
LFSR_TAG_OCOMPAT, 0,
LFSR_DATA_OCOMPAT(LFSR_OCOMPAT_COMPAT),
LFSR_DATA_BUF("\x00\x00\x00\x00\x80", 5)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should _not_ fail, ocompat should always be ignored
lfsr_mount(&lfs, CFG) => 0;
lfsr_unmount(&lfs) => 0;
'''
# test that we fail to mount incompatible block sizes
[cases.test_incompat_block_size]
defines.INC_BLOCK_SIZE = ['BLOCK_SIZE/2', 'BLOCK_SIZE*2']
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set an incompatible block size
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_GEOMETRY, 0,
LFSR_DATA_GEOMETRY((&(lfsr_geometry_t){
INC_BLOCK_SIZE,
BLOCK_COUNT}))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount after incompatible block counts
[cases.test_incompat_block_count]
defines.INC_BLOCK_COUNT = ['BLOCK_COUNT*2']
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set an incompatible block count
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_GEOMETRY, 0,
LFSR_DATA_GEOMETRY((&(lfsr_geometry_t){
BLOCK_SIZE,
INC_BLOCK_COUNT}))))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount after incompatible name limit
[cases.test_incompat_name_limit]
defines.INC_NAME_LIMIT = ['LFS_NAME_MAX*2']
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set an incompatible block size
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR(
LFSR_TAG_NAMELIMIT, 0,
LFSR_DATA_LLEB128(INC_NAME_LIMIT)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test that we fail to mount after incompatible file limit
[cases.test_incompat_file_limit]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// set an incompatible file limit
//
// note we're messing around with internals to do this! this
// is not a user API
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_commit(&lfs, &lfs.mroot, LFSR_ATTRS(
LFSR_ATTR_CAT(
LFSR_TAG_FILELIMIT, 0,
// it's a bit difficult to test this since file limit
// is usually our integer limit, but we can force a
// larger value by inserting an extra byte into our
// leb128 encoding
LFSR_DATA_BUF("\xff", 1),
LFSR_DATA_LEB128(LFS_FILE_MAX)))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''
# test what happens if we find an unknown file type
[cases.test_incompat_unknown]
in = 'lfs.c'
code = '''
// create a superblock
lfs_t lfs;
lfsr_format(&lfs, CFG) => 0;
// create some files
lfsr_mount(&lfs, CFG) => 0;
lfsr_file_t file;
lfsr_file_open(&lfs, &file, "a",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, "hi a!", strlen("hi a!")) => strlen("hi a!");
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "b",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, "hi b!", strlen("hi b!")) => strlen("hi b!");
lfsr_file_close(&lfs, &file) => 0;
lfsr_file_open(&lfs, &file, "c",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfsr_file_write(&lfs, &file, "hi c!", strlen("hi c!")) => strlen("hi c!");
lfsr_file_close(&lfs, &file) => 0;
lfsr_unmount(&lfs) => 0;
// change a file's type to something unknown
lfsr_mount(&lfs, CFG) => 0;
lfsr_mdir_t mdir;
lfsr_did_t did;
const char *name;
lfs_size_t name_size;
lfsr_mtree_pathlookup(&lfs, &lfs.mtree, "b",
&mdir, NULL,
&did, &name, &name_size) => LFS_ERR_EXIST;
lfsr_mdir_commit(&lfs, &mdir, LFSR_ATTRS(
LFSR_ATTR_NAME(
LFSR_TAG_SUB | (LFSR_TAG_NAME + 0x13), 0,
did, name, name_size))) => 0;
lfsr_unmount(&lfs) => 0;
// mount should now fail
lfsr_mount(&lfs, CFG) => LFS_ERR_INVAL;
'''