From 06159a1465fc97d8d7b72b9bea39a396f6e7057c Mon Sep 17 00:00:00 2001 From: Gabriel Dalimonte Date: Mon, 17 Feb 2025 13:26:44 -0500 Subject: fs: fat: add rename The implementation roughly follows the POSIX specification for rename() [1]. The ordering of operations attempting to minimize the chance for data loss in unexpected circumstances. The 'mv' command was implemented as a front end for the rename operation as that is what most users are likely familiar with in terms of behavior. The 'FAT_RENAME' Kconfig option was added to prevent code size increase on size-oriented builds like SPL. [1] https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html Signed-off-by: Gabriel Dalimonte --- test/py/tests/test_fs/conftest.py | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) (limited to 'test/py/tests/test_fs/conftest.py') diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py index af2adaf1645..7bfcf41ed6f 100644 --- a/test/py/tests/test_fs/conftest.py +++ b/test/py/tests/test_fs/conftest.py @@ -18,6 +18,7 @@ supported_fs_fat = ['fat12', 'fat16'] supported_fs_mkdir = ['fat12', 'fat16', 'fat32'] supported_fs_unlink = ['fat12', 'fat16', 'fat32'] supported_fs_symlink = ['ext4'] +supported_fs_rename = ['fat12', 'fat16', 'fat32'] # # Filesystem test specific setup @@ -55,6 +56,7 @@ def pytest_configure(config): global supported_fs_mkdir global supported_fs_unlink global supported_fs_symlink + global supported_fs_rename def intersect(listA, listB): return [x for x in listA if x in listB] @@ -68,6 +70,7 @@ def pytest_configure(config): supported_fs_mkdir = intersect(supported_fs, supported_fs_mkdir) supported_fs_unlink = intersect(supported_fs, supported_fs_unlink) supported_fs_symlink = intersect(supported_fs, supported_fs_symlink) + supported_fs_rename = intersect(supported_fs, supported_fs_rename) def pytest_generate_tests(metafunc): """Parametrize fixtures, fs_obj_xxx @@ -99,6 +102,9 @@ def pytest_generate_tests(metafunc): if 'fs_obj_symlink' in metafunc.fixturenames: metafunc.parametrize('fs_obj_symlink', supported_fs_symlink, indirect=True, scope='module') + if 'fs_obj_rename' in metafunc.fixturenames: + metafunc.parametrize('fs_obj_rename', supported_fs_rename, + indirect=True, scope='module') # # Helper functions @@ -527,6 +533,121 @@ def fs_obj_symlink(request, u_boot_config): call('rm -rf %s' % scratch_dir, shell=True) call('rm -f %s' % fs_img, shell=True) +# +# Fixture for rename test +# +@pytest.fixture() +def fs_obj_rename(request, u_boot_config): + """Set up a file system to be used in rename tests. + + Args: + request: Pytest request object. + u_boot_config: U-Boot configuration. + + Return: + A fixture for rename tests, i.e. a triplet of file system type, + volume file name, and dictionary of test identifier and md5val. + """ + def new_rand_file(path): + check_call('dd if=/dev/urandom of=%s bs=1K count=1' % path, shell=True) + + def file_hash(path): + out = check_output( + 'dd if=%s bs=1K skip=0 count=1 2> /dev/null | md5sum' % path, + shell=True + ) + return out.decode().split()[0] + + fs_type = request.param + fs_img = '' + + fs_ubtype = fstype_to_ubname(fs_type) + check_ubconfig(u_boot_config, fs_ubtype) + + mount_dir = u_boot_config.persistent_data_dir + '/scratch' + + try: + check_call('mkdir -p %s' % mount_dir, shell=True) + except CalledProcessError as err: + pytest.skip('Preparing mount folder failed for filesystem: ' + fs_type + '. {}'.format(err)) + call('rm -f %s' % fs_img, shell=True) + return + + try: + md5val = {} + # Test Case 1 + check_call('mkdir %s/test1' % mount_dir, shell=True) + new_rand_file('%s/test1/file1' % mount_dir) + md5val['test1'] = file_hash('%s/test1/file1' % mount_dir) + + # Test Case 2 + check_call('mkdir %s/test2' % mount_dir, shell=True) + new_rand_file('%s/test2/file1' % mount_dir) + new_rand_file('%s/test2/file_exist' % mount_dir) + md5val['test2'] = file_hash('%s/test2/file1' % mount_dir) + + # Test Case 3 + check_call('mkdir -p %s/test3/dir1' % mount_dir, shell=True) + new_rand_file('%s/test3/dir1/file1' % mount_dir) + md5val['test3'] = file_hash('%s/test3/dir1/file1' % mount_dir) + + # Test Case 4 + check_call('mkdir -p %s/test4/dir1' % mount_dir, shell=True) + check_call('mkdir -p %s/test4/dir2/dir1' % mount_dir, shell=True) + new_rand_file('%s/test4/dir1/file1' % mount_dir) + md5val['test4'] = file_hash('%s/test4/dir1/file1' % mount_dir) + + # Test Case 5 + check_call('mkdir -p %s/test5/dir1' % mount_dir, shell=True) + new_rand_file('%s/test5/file2' % mount_dir) + md5val['test5'] = file_hash('%s/test5/file2' % mount_dir) + + # Test Case 6 + check_call('mkdir -p %s/test6/dir2/existing' % mount_dir, shell=True) + new_rand_file('%s/test6/existing' % mount_dir) + md5val['test6'] = file_hash('%s/test6/existing' % mount_dir) + + # Test Case 7 + check_call('mkdir -p %s/test7/dir1' % mount_dir, shell=True) + check_call('mkdir -p %s/test7/dir2/dir1' % mount_dir, shell=True) + new_rand_file('%s/test7/dir2/dir1/file1' % mount_dir) + md5val['test7'] = file_hash('%s/test7/dir2/dir1/file1' % mount_dir) + + # Test Case 8 + check_call('mkdir -p %s/test8/dir1' % mount_dir, shell=True) + new_rand_file('%s/test8/dir1/file1' % mount_dir) + md5val['test8'] = file_hash('%s/test8/dir1/file1' % mount_dir) + + # Test Case 9 + check_call('mkdir -p %s/test9/dir1/nested/inner' % mount_dir, shell=True) + new_rand_file('%s/test9/dir1/nested/inner/file1' % mount_dir) + + # Test Case 10 + check_call('mkdir -p %s/test10' % mount_dir, shell=True) + new_rand_file('%s/test10/file1' % mount_dir) + md5val['test10'] = file_hash('%s/test10/file1' % mount_dir) + + # Test Case 11 + check_call('mkdir -p %s/test11/dir1' % mount_dir, shell=True) + new_rand_file('%s/test11/dir1/file1' % mount_dir) + md5val['test11'] = file_hash('%s/test11/dir1/file1' % mount_dir) + + try: + # 128MiB volume + fs_img = fs_helper.mk_fs(u_boot_config, fs_type, 0x8000000, '128MB', mount_dir) + except CalledProcessError as err: + pytest.skip('Creating failed for filesystem: ' + fs_type + '. {}'.format(err)) + return + + except CalledProcessError: + pytest.skip('Setup failed for filesystem: ' + fs_type) + return + else: + yield [fs_ubtype, fs_img, md5val] + finally: + call('rm -rf %s' % mount_dir, shell=True) + call('rm -f %s' % fs_img, shell=True) + # # Fixture for fat test # -- cgit v1.2.3 From d9ed4b75add4b4ccc37cf32b54cd9c77f48e3396 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 9 Feb 2025 09:07:15 -0700 Subject: test/py: Drop u_boot_ prefix on test files We know this is U-Boot so the prefix serves no purpose other than to make things longer and harder to read. Drop it and rename the files. Signed-off-by: Simon Glass Reviewed-by: Mattijs Korpershoek # test_android / test_dfu --- test/py/tests/test_fs/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/py/tests/test_fs/conftest.py') diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py index 7bfcf41ed6f..5b259796ebe 100644 --- a/test/py/tests/test_fs/conftest.py +++ b/test/py/tests/test_fs/conftest.py @@ -8,7 +8,7 @@ import pytest import re from subprocess import call, check_call, check_output, CalledProcessError from fstest_defs import * -import u_boot_utils as util +import utils as util # pylint: disable=E0611 from tests import fs_helper -- cgit v1.2.3 From dd693ecb60384049dd8c3f6a36331c1a70b6558f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 9 Feb 2025 09:07:16 -0700 Subject: test/py: Drop importing utils as util Now that we have a shorter name, we don't need this sort of thing. Drop it. Signed-off-by: Simon Glass Reviewed-by: Mattijs Korpershoek # test_android --- test/py/tests/test_fs/conftest.py | 1 - 1 file changed, 1 deletion(-) (limited to 'test/py/tests/test_fs/conftest.py') diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py index 5b259796ebe..47a584ffe7c 100644 --- a/test/py/tests/test_fs/conftest.py +++ b/test/py/tests/test_fs/conftest.py @@ -8,7 +8,6 @@ import pytest import re from subprocess import call, check_call, check_output, CalledProcessError from fstest_defs import * -import utils as util # pylint: disable=E0611 from tests import fs_helper -- cgit v1.2.3 From 6592425c6d7e4e010f3de88bc8ced7163e4f12e2 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 17 Mar 2025 04:12:42 +0100 Subject: test_fs: Allow testing FS_GENERIC The generic filesystem interface was so far untested. The interface is similar to the FS specific interfaces with FS specific prefixes, like ext4ls, fatmkdir, ... but it does not have any prefixes, i.e. it provides plain ls, mkdir, ... commands. Extend the test parameters to include 'fs_cmd_prefix' and optionally 'fs_cmd_write' parameters. The 'fs_cmd_prefix' allow specifying the filesystem specific command prefix, like 'ext4' in 'ext4ls'. The 'fs_cmd_write' allows selecting between 'write'/'save' command name for storing files into the filesystem, see last paragraph. Introduce new 'fs_generic' fs_type which is used to parametrize existing tests and run them without any prefixes if detected, thus testing the generic filesystem interface. Use the fatfs as the backing store for the generic FS tests. The check_ubconfig needs to be slightly adjusted to avoid test for CMD_FS_GENERIC_WRITE which does not exist separately from CMD_FS_GENERIC. The CMD_FS_GENERIC does not provide generic 'write' command, instead the generic equivalent command is called 'save' . Add simple ternary oeprator to use 'save' command for CMD_FS_GENERIC tests and '..write' commands for filesystem specific tests. Enable generic filesystem tests for basic/extended/mkdir/unlink tests. Signed-off-by: Marek Vasut --- test/py/tests/test_fs/conftest.py | 40 +++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'test/py/tests/test_fs/conftest.py') diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py index 47a584ffe7c..691bdf41ede 100644 --- a/test/py/tests/test_fs/conftest.py +++ b/test/py/tests/test_fs/conftest.py @@ -11,11 +11,11 @@ from fstest_defs import * # pylint: disable=E0611 from tests import fs_helper -supported_fs_basic = ['fat16', 'fat32', 'ext4'] -supported_fs_ext = ['fat12', 'fat16', 'fat32'] +supported_fs_basic = ['fat16', 'fat32', 'ext4', 'fs_generic'] +supported_fs_ext = ['fat12', 'fat16', 'fat32', 'fs_generic'] supported_fs_fat = ['fat12', 'fat16'] -supported_fs_mkdir = ['fat12', 'fat16', 'fat32'] -supported_fs_unlink = ['fat12', 'fat16', 'fat32'] +supported_fs_mkdir = ['fat12', 'fat16', 'fat32', 'fs_generic'] +supported_fs_unlink = ['fat12', 'fat16', 'fat32', 'fs_generic'] supported_fs_symlink = ['ext4'] supported_fs_rename = ['fat12', 'fat16', 'fat32'] @@ -108,6 +108,22 @@ def pytest_generate_tests(metafunc): # # Helper functions # +def fstype_to_prefix(fs_type): + """Convert a file system type to an U-Boot command prefix + + Args: + fs_type: File system type. + + Return: + A corresponding command prefix for file system type. + """ + if fs_type == 'fs_generic': + return '' + elif re.match('fat', fs_type): + return 'fat' + else: + return fs_type + def fstype_to_ubname(fs_type): """Convert a file system type to an U-Boot specific string @@ -141,6 +157,8 @@ def check_ubconfig(config, fs_type): """ if not config.buildconfig.get('config_cmd_%s' % fs_type, None): pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper()) + if fs_type == 'fs_generic': + return if not config.buildconfig.get('config_%s_write' % fs_type, None): pytest.skip('.config feature "%s_WRITE" not enabled' % fs_type.upper()) @@ -178,6 +196,8 @@ def fs_obj_basic(request, u_boot_config): volume file name and a list of MD5 hashes. """ fs_type = request.param + fs_cmd_prefix = fstype_to_prefix(fs_type) + fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) @@ -267,7 +287,7 @@ def fs_obj_basic(request, u_boot_config): pytest.skip('Setup failed for filesystem: ' + fs_type + '. {}'.format(err)) return else: - yield [fs_ubtype, fs_img, md5val] + yield [fs_ubtype, fs_cmd_prefix, fs_cmd_write, fs_img, md5val] finally: call('rm -rf %s' % scratch_dir, shell=True) call('rm -f %s' % fs_img, shell=True) @@ -288,6 +308,8 @@ def fs_obj_ext(request, u_boot_config): volume file name and a list of MD5 hashes. """ fs_type = request.param + fs_cmd_prefix = fstype_to_prefix(fs_type) + fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) @@ -357,7 +379,7 @@ def fs_obj_ext(request, u_boot_config): pytest.skip('Setup failed for filesystem: ' + fs_type) return else: - yield [fs_ubtype, fs_img, md5val] + yield [fs_ubtype, fs_cmd_prefix, fs_cmd_write, fs_img, md5val] finally: call('rm -rf %s' % scratch_dir, shell=True) call('rm -f %s' % fs_img, shell=True) @@ -378,6 +400,7 @@ def fs_obj_mkdir(request, u_boot_config): volume file name. """ fs_type = request.param + fs_cmd_prefix = fstype_to_prefix(fs_type) fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) @@ -390,7 +413,7 @@ def fs_obj_mkdir(request, u_boot_config): pytest.skip('Setup failed for filesystem: ' + fs_type) return else: - yield [fs_ubtype, fs_img] + yield [fs_ubtype, fs_cmd_prefix, fs_img] call('rm -f %s' % fs_img, shell=True) # @@ -409,6 +432,7 @@ def fs_obj_unlink(request, u_boot_config): volume file name. """ fs_type = request.param + fs_cmd_prefix = fstype_to_prefix(fs_type) fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) @@ -456,7 +480,7 @@ def fs_obj_unlink(request, u_boot_config): pytest.skip('Setup failed for filesystem: ' + fs_type) return else: - yield [fs_ubtype, fs_img] + yield [fs_ubtype, fs_cmd_prefix, fs_img] finally: call('rm -rf %s' % scratch_dir, shell=True) call('rm -f %s' % fs_img, shell=True) -- cgit v1.2.3 From 8d0cc62a60b5b92a010f75fd61d9eb9cb8299567 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Mon, 17 Mar 2025 04:12:50 +0100 Subject: test_fs: Add exfat tests Add tests for the exfat filesystem. These tests are largely an extension of the FS_GENERIC tests with the following notable exceptions. The filesystem image for exfat tests is generated using combination of exfatprogs mkfs.exfat and python fattools. The fattols are capable of generating exfat filesystem images too, but this is not used, the fattools are only used as a replacement for dosfstools 'mcopy' and 'mdir', which are used to insert files and directories into existing fatfs images and list existing fatfs images respectively, without the need for superuser access to mount such images. The exfat filesystem has no filesystem specific command, there is only the generic filesystem command interface, therefore check_ubconfig() has to special case exfat and skip check for CONFIG_CMD_EXFAT and instead check for CONFIG_FS_EXFAT. Signed-off-by: Marek Vasut --- test/py/tests/test_fs/conftest.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'test/py/tests/test_fs/conftest.py') diff --git a/test/py/tests/test_fs/conftest.py b/test/py/tests/test_fs/conftest.py index 691bdf41ede..c73fb4abbcb 100644 --- a/test/py/tests/test_fs/conftest.py +++ b/test/py/tests/test_fs/conftest.py @@ -11,11 +11,11 @@ from fstest_defs import * # pylint: disable=E0611 from tests import fs_helper -supported_fs_basic = ['fat16', 'fat32', 'ext4', 'fs_generic'] -supported_fs_ext = ['fat12', 'fat16', 'fat32', 'fs_generic'] +supported_fs_basic = ['fat16', 'fat32', 'exfat', 'ext4', 'fs_generic'] +supported_fs_ext = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic'] supported_fs_fat = ['fat12', 'fat16'] -supported_fs_mkdir = ['fat12', 'fat16', 'fat32', 'fs_generic'] -supported_fs_unlink = ['fat12', 'fat16', 'fat32', 'fs_generic'] +supported_fs_mkdir = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic'] +supported_fs_unlink = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic'] supported_fs_symlink = ['ext4'] supported_fs_rename = ['fat12', 'fat16', 'fat32'] @@ -117,7 +117,7 @@ def fstype_to_prefix(fs_type): Return: A corresponding command prefix for file system type. """ - if fs_type == 'fs_generic': + if fs_type == 'fs_generic' or fs_type == 'exfat': return '' elif re.match('fat', fs_type): return 'fat' @@ -155,9 +155,11 @@ def check_ubconfig(config, fs_type): Return: Nothing. """ - if not config.buildconfig.get('config_cmd_%s' % fs_type, None): + if fs_type == 'exfat' and not config.buildconfig.get('config_fs_%s' % fs_type, None): + pytest.skip('.config feature "FS_%s" not enabled' % fs_type.upper()) + if fs_type != 'exfat' and not config.buildconfig.get('config_cmd_%s' % fs_type, None): pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper()) - if fs_type == 'fs_generic': + if fs_type == 'fs_generic' or fs_type == 'exfat': return if not config.buildconfig.get('config_%s_write' % fs_type, None): pytest.skip('.config feature "%s_WRITE" not enabled' @@ -197,7 +199,7 @@ def fs_obj_basic(request, u_boot_config): """ fs_type = request.param fs_cmd_prefix = fstype_to_prefix(fs_type) - fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' + fs_cmd_write = 'save' if fs_type == 'fs_generic' or fs_type == 'exfat' else 'write' fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) @@ -309,7 +311,7 @@ def fs_obj_ext(request, u_boot_config): """ fs_type = request.param fs_cmd_prefix = fstype_to_prefix(fs_type) - fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' + fs_cmd_write = 'save' if fs_type == 'fs_generic' or fs_type == 'exfat' else 'write' fs_img = '' fs_ubtype = fstype_to_ubname(fs_type) -- cgit v1.2.3