diff --git a/doc/releases/release-notes-3.5.rst b/doc/releases/release-notes-3.5.rst index a6bb1beaec3d6..fe2379cf2114c 100644 --- a/doc/releases/release-notes-3.5.rst +++ b/doc/releases/release-notes-3.5.rst @@ -283,6 +283,10 @@ Libraries / Subsystems :c:struct:`mgmt_group` when registering a transport. See :c:type:`smp_translate_error_fn` for function details. +* File systems + + * Added support for ext2 file system. + HALs **** @@ -313,5 +317,8 @@ Documentation Tests and Samples ***************** +* Created common sample for file systems (`fs_sample`). It originates from sample for FAT + (`fat_fs`) and supports both FAT and ext2 file systems. + Known Issues ************ diff --git a/include/zephyr/fs/ext2.h b/include/zephyr/fs/ext2.h new file mode 100644 index 0000000000000..087134ce7f899 --- /dev/null +++ b/include/zephyr/fs/ext2.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_FS_EXT2_H_ +#define ZEPHYR_INCLUDE_FS_EXT2_H_ + +#include + +/** @brief Configuration used to format ext2 file system. + * + * If a field is set to 0 then default value is used. + * (In volume name the first cell of an array must be 0 to use default value.) + * + * @param block_size Requested size of block. + * @param fs_size Requested size of file system. If 0 then whole available memory is used. + * @param bytes_per_inode Requested memory for one inode. It is used to calculate number of inodes + * in created file system. + * @param uuid UUID for created file system. Used when set_uuid is true. + * @param volume_name Name for created file system. + * @param set_uuid If true then UUID from that structure is used in created file system. + * If false then UUID (ver4) is generated. + */ +struct ext2_cfg { + uint32_t block_size; + uint32_t fs_size; /* Number of blocks that we want to take. */ + uint32_t bytes_per_inode; + uint8_t uuid[16]; + uint8_t volume_name[17]; /* If first byte is 0 then name ext2" is given. */ + bool set_uuid; +}; + +#define FS_EXT2_DECLARE_DEFAULT_CONFIG(name) \ + static struct ext2_cfg name = { \ + .block_size = 1024, \ + .fs_size = 0x800000, \ + .bytes_per_inode = 4096, \ + .volume_name = {'e', 'x', 't', '2', '\0'}, \ + .set_uuid = false, \ + } + + +#endif /* ZEPHYR_INCLUDE_FS_EXT2_H_ */ diff --git a/include/zephyr/fs/fs.h b/include/zephyr/fs/fs.h index 699b5e9ec6b3f..ee628dc1b8b08 100644 --- a/include/zephyr/fs/fs.h +++ b/include/zephyr/fs/fs.h @@ -56,6 +56,9 @@ enum { /** Identifier for in-tree LittleFS file system. */ FS_LITTLEFS, + /** Identifier for in-tree Ext2 file system. */ + FS_EXT2, + /** Base identifier for external file systems. */ FS_TYPE_EXTERNAL_BASE, }; diff --git a/include/zephyr/fs/fs_interface.h b/include/zephyr/fs/fs_interface.h index d1d78f8cb4fca..2692ea168a7cd 100644 --- a/include/zephyr/fs/fs_interface.h +++ b/include/zephyr/fs/fs_interface.h @@ -15,20 +15,33 @@ extern "C" { #if (CONFIG_FILE_SYSTEM_MAX_FILE_NAME - 0) > 0 #define MAX_FILE_NAME CONFIG_FILE_SYSTEM_MAX_FILE_NAME + #else /* CONFIG_FILE_SYSTEM_MAX_FILE_NAME */ /* Select from enabled file systems */ -#if defined(CONFIG_FILE_SYSTEM_LITTLEFS) -#define MAX_FILE_NAME 256 -#elif defined(CONFIG_FAT_FILESYSTEM_ELM) + +#if defined(CONFIG_FAT_FILESYSTEM_ELM) + #if defined(CONFIG_FS_FATFS_LFN) #define MAX_FILE_NAME CONFIG_FS_FATFS_MAX_LFN #else /* CONFIG_FS_FATFS_LFN */ #define MAX_FILE_NAME 12 /* Uses 8.3 SFN */ #endif /* CONFIG_FS_FATFS_LFN */ -#else /* filesystem selection */ + +#endif + +#if !defined(MAX_FILE_NAME) && defined(CONFIG_FILE_SYSTEM_EXT2) +#define MAX_FILE_NAME 255 +#endif + +#if !defined(MAX_FILE_NAME) && defined(CONFIG_FILE_SYSTEM_LITTLEFS) +#define MAX_FILE_NAME 256 +#endif + +#if !defined(MAX_FILE_NAME) /* filesystem selection */ /* Use standard 8.3 when no filesystem is explicitly selected */ #define MAX_FILE_NAME 12 #endif /* filesystem selection */ + #endif /* CONFIG_FILE_SYSTEM_MAX_FILE_NAME */ diff --git a/samples/subsys/fs/fat_fs/CMakeLists.txt b/samples/subsys/fs/fs_sample/CMakeLists.txt similarity index 100% rename from samples/subsys/fs/fat_fs/CMakeLists.txt rename to samples/subsys/fs/fs_sample/CMakeLists.txt diff --git a/samples/subsys/fs/fat_fs/Kconfig b/samples/subsys/fs/fs_sample/Kconfig similarity index 62% rename from samples/subsys/fs/fat_fs/Kconfig rename to samples/subsys/fs/fs_sample/Kconfig index 06b3720fb6528..25d7b95e6cd2a 100644 --- a/samples/subsys/fs/fat_fs/Kconfig +++ b/samples/subsys/fs/fs_sample/Kconfig @@ -1,20 +1,21 @@ # # Copyright (c) 2023 Nordic Semiconductor ASA +# Copyright (c) 2023 Antmicro # # SPDX-License-Identifier: Apache-2.0 # -mainmenu "FAT Filesystem Sample Application" +mainmenu "Filesystems Sample Application" -config SAMPLE_FATFS_CREATE_SOME_ENTRIES +config FS_SAMPLE_CREATE_SOME_ENTRIES bool "When no files are found on mounted partition create some" default y help In case when no files could be listed, because there are none, "some.dir" directory and "other.txt" file will be created and list will run again to show them. This is useful when - showing how FAT works on non-SD devices like internal flash - or (Q)SPI connected memories, where it is not possible to + showing how file system works on non-SD devices like internal + flash or (Q)SPI connected memories, where it is not possible to easily add files with use of other device. source "Kconfig.zephyr" diff --git a/samples/subsys/fs/fat_fs/README.rst b/samples/subsys/fs/fs_sample/README.rst similarity index 62% rename from samples/subsys/fs/fat_fs/README.rst rename to samples/subsys/fs/fs_sample/README.rst index 2f7ecb4952d79..6a441e201cda6 100644 --- a/samples/subsys/fs/fat_fs/README.rst +++ b/samples/subsys/fs/fs_sample/README.rst @@ -1,12 +1,12 @@ -.. _fat_fs: +.. _fs_sample: -FAT Filesystem Sample Application +Filesystems Sample Application ################################### Overview ******** -This sample app demonstrates use of the filesystem API and uses the FAT file +This sample app demonstrates use of the file system API and uses the FAT or Ext2 file system driver with SDHC card, SoC flash or external flash chip. To access device the sample uses :ref:`disk_access_api`. @@ -14,13 +14,11 @@ To access device the sample uses :ref:`disk_access_api`. Requirements for SD card support ******************************** -This project requires SD card support and microSD card formatted with FAT filesystem. -See the :ref:`disk_access_api` documentation for Zephyr implementation details. -Boards that by default use SD card for storage: -``arduino_mkrzero``, ``esp_wrover_kit``, ``mimxrt1050_evk``, ``nrf52840_blip`` -and ``olimexino_stm32``. -The sample should be able to run with any other board that has "zephyr,sdmmc-disk" -DT node enabled. +This project requires SD card support and microSD card formatted with proper file system +(FAT or Ext2) See the :ref:`disk_access_api` documentation for Zephyr implementation details. +Boards that by default use SD card for storage: ``arduino_mkrzero``, ``esp_wrover_kit``, +``mimxrt1050_evk``, ``nrf52840_blip`` and ``olimexino_stm32``. The sample should be able +to run with any other board that has "zephyr,sdmmc-disk" DT node enabled. Requirements for setting up FAT FS on SoC flash *********************************************** @@ -38,14 +36,14 @@ This type of configuration requires external flash device to be available on DK board. Currently following boards support the configuration: ``nrf52840dk_nrf52840`` by ``nrf52840dk_nrf52840_qspi`` configuration. -Building and Running -******************** +Building and Running FAT samples +******************************** Boards with default configurations, for example ``arduino_mkrzero`` or ``nrf52840dk_nrf52840`` using internal flash can be build using command: .. zephyr-app-commands:: - :zephyr-app: samples/subsys/fs/fat_fs + :zephyr-app: samples/subsys/fs/fs_sample :board: nrf52840_blip :goals: build :compact: @@ -57,7 +55,7 @@ for example ``nrf52840dk_nrf52840`` with MX25 device over QSPI, configuration and DTS overlays need to be also selected. The command would look like this: .. zephyr-app-commands:: - :zephyr-app: samples/subsys/fs/fat_fs + :zephyr-app: samples/subsys/fs/fs_sample :board: nrf52840dk_nrf52840 :gen-args: -DEXTRA_CONF_FILE=nrf52840dk_nrf52840_qspi.conf -DDTC_OVERLAY_FILE=nrf52840dk_nrf52840_qspi.overlay :goals: build @@ -70,3 +68,20 @@ sample lists them out on the debug serial output. .. warning:: In case when mount fails the device may get re-formatted to FAT FS. To disable this behaviour disable :kconfig:option:`CONFIG_FS_FATFS_MOUNT_MKFS` . + +Building and Running EXT2 samples +********************************* + +Ext2 sample can be build for ``hifive_unmatched`` or ``bl5340_dvk_cpuapp``. Because +FAT is default file system for this sample, additional flags must be passed to build +the sample. + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/fs/fs_sample + :board: hifive_unmatched + :gen-args: -DCONF_FILE=prj_ext.conf + :goals: build + :compact: + +A microSD card must be present in a microSD card slot of the board, for the sample to execute. +After starting the sample a contents of a root directory should be printed on the console. diff --git a/samples/subsys/fs/fat_fs/boards/esp_wrover_kit.conf b/samples/subsys/fs/fs_sample/boards/esp_wrover_kit.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/esp_wrover_kit.conf rename to samples/subsys/fs/fs_sample/boards/esp_wrover_kit.conf diff --git a/samples/subsys/fs/fat_fs/boards/esp_wrover_kit.overlay b/samples/subsys/fs/fs_sample/boards/esp_wrover_kit.overlay similarity index 100% rename from samples/subsys/fs/fat_fs/boards/esp_wrover_kit.overlay rename to samples/subsys/fs/fs_sample/boards/esp_wrover_kit.overlay diff --git a/samples/subsys/fs/fat_fs/boards/frdm_k64f.conf b/samples/subsys/fs/fs_sample/boards/frdm_k64f.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/frdm_k64f.conf rename to samples/subsys/fs/fs_sample/boards/frdm_k64f.conf diff --git a/samples/subsys/fs/fs_sample/boards/hifive_unmatched.overlay b/samples/subsys/fs/fs_sample/boards/hifive_unmatched.overlay new file mode 100644 index 0000000000000..730370e1b5a8b --- /dev/null +++ b/samples/subsys/fs/fs_sample/boards/hifive_unmatched.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&spi2 { + status = "okay"; + + sdhc0: sdhc@0 { + compatible = "zephyr,sdhc-spi-slot"; + reg = <0>; + status = "okay"; + mmc { + compatible = "zephyr,sdmmc-disk"; + status = "okay"; + }; + spi-max-frequency = <20000000>; + }; +}; diff --git a/samples/subsys/fs/fat_fs/boards/mg100.conf b/samples/subsys/fs/fs_sample/boards/mg100.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/mg100.conf rename to samples/subsys/fs/fs_sample/boards/mg100.conf diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840_blip.conf b/samples/subsys/fs/fs_sample/boards/nrf52840_blip.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nrf52840_blip.conf rename to samples/subsys/fs/fs_sample/boards/nrf52840_blip.conf diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840_blip.overlay b/samples/subsys/fs/fs_sample/boards/nrf52840_blip.overlay similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nrf52840_blip.overlay rename to samples/subsys/fs/fs_sample/boards/nrf52840_blip.overlay diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840.conf b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840.conf similarity index 87% rename from samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840.conf rename to samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840.conf index a040d02ec0685..8bd2daab5a542 100644 --- a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840.conf +++ b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840.conf @@ -9,4 +9,4 @@ CONFIG_DISK_DRIVER_FLASH=y # There may be no files on internal SoC flash, so this Kconfig # options has ben enabled to create some if listing does not # find in the first place. -CONFIG_SAMPLE_FATFS_CREATE_SOME_ENTRIES=y +CONFIG_FS_SAMPLE_CREATE_SOME_ENTRIES=y diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840.overlay b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840.overlay similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840.overlay rename to samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840.overlay diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840_qspi.conf b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840_qspi.conf similarity index 90% rename from samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840_qspi.conf rename to samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840_qspi.conf index 99626d1d43c5b..892157f3a9cb5 100644 --- a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840_qspi.conf +++ b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840_qspi.conf @@ -12,4 +12,4 @@ CONFIG_DISK_DRIVER_FLASH=y # There may be no files on internal SoC flash, so this Kconfig # options has ben enabled to create some if listing does not # find in the first place. -CONFIG_SAMPLE_FATFS_CREATE_SOME_ENTRIES=y +CONFIG_FS_SAMPLE_CREATE_SOME_ENTRIES=y diff --git a/samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840_qspi.overlay b/samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840_qspi.overlay similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nrf52840dk_nrf52840_qspi.overlay rename to samples/subsys/fs/fs_sample/boards/nrf52840dk_nrf52840_qspi.overlay diff --git a/samples/subsys/fs/fat_fs/boards/nucleo_f429zi.conf b/samples/subsys/fs/fs_sample/boards/nucleo_f429zi.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nucleo_f429zi.conf rename to samples/subsys/fs/fs_sample/boards/nucleo_f429zi.conf diff --git a/samples/subsys/fs/fat_fs/boards/nucleo_f429zi.overlay b/samples/subsys/fs/fs_sample/boards/nucleo_f429zi.overlay similarity index 100% rename from samples/subsys/fs/fat_fs/boards/nucleo_f429zi.overlay rename to samples/subsys/fs/fs_sample/boards/nucleo_f429zi.overlay diff --git a/samples/subsys/fs/fat_fs/boards/olimexino_stm32.conf b/samples/subsys/fs/fs_sample/boards/olimexino_stm32.conf similarity index 100% rename from samples/subsys/fs/fat_fs/boards/olimexino_stm32.conf rename to samples/subsys/fs/fs_sample/boards/olimexino_stm32.conf diff --git a/samples/subsys/fs/fat_fs/prj.conf b/samples/subsys/fs/fs_sample/prj.conf similarity index 100% rename from samples/subsys/fs/fat_fs/prj.conf rename to samples/subsys/fs/fs_sample/prj.conf diff --git a/samples/subsys/fs/fs_sample/prj_ext.conf b/samples/subsys/fs/fs_sample/prj_ext.conf new file mode 100644 index 0000000000000..4c9fd15c510bf --- /dev/null +++ b/samples/subsys/fs/fs_sample/prj_ext.conf @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y + +CONFIG_MAIN_STACK_SIZE=2048 + +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_EXT2=y + +# Enable to allow formatting +# CONFIG_FILE_SYSTEM_MKFS=y +# CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVER_SDMMC=y + +# First block of first partition after GPT +CONFIG_EXT2_DISK_STARTING_SECTOR=2082 diff --git a/samples/subsys/fs/fat_fs/sample.yaml b/samples/subsys/fs/fs_sample/sample.yaml similarity index 88% rename from samples/subsys/fs/fat_fs/sample.yaml rename to samples/subsys/fs/fs_sample/sample.yaml index 7a820a150336f..e022ca1e4c164 100644 --- a/samples/subsys/fs/fat_fs/sample.yaml +++ b/samples/subsys/fs/fs_sample/sample.yaml @@ -1,5 +1,5 @@ sample: - name: Fat filesystem sample + name: Filesystems sample common: tags: filesystem modules: @@ -42,3 +42,6 @@ tests: filter: dt_compat_enabled("zephyr,sdmmc-disk") integration_platforms: - frdm_k64f + sample.filesystem.ext2: + extra_args: CONF_FILE="prj_ext.conf" + platform_allow: hifive_unmatched bl5340_dvk_cpuapp diff --git a/samples/subsys/fs/fat_fs/src/main.c b/samples/subsys/fs/fs_sample/src/main.c similarity index 85% rename from samples/subsys/fs/fat_fs/src/main.c rename to samples/subsys/fs/fs_sample/src/main.c index a1837d0d3192c..52c74cc74b1d8 100644 --- a/samples/subsys/fs/fat_fs/src/main.c +++ b/samples/subsys/fs/fs_sample/src/main.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2019 Tavish Naruka * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2023 Antmicro * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,16 +13,17 @@ #include #include #include -#include -LOG_MODULE_REGISTER(main); +#if defined(CONFIG_FAT_FILESYSTEM_ELM) + +#include +/* + * Note the fatfs library is able to mount only strings inside _VOLUME_STRS + * in ffconf.h + */ #define DISK_DRIVE_NAME "SD" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" -#define MAX_PATH 128 -#define SOME_FILE_NAME "some.dat" -#define SOME_DIR_NAME "some" -#define SOME_REQUIRED_LEN MAX(sizeof(SOME_FILE_NAME), sizeof(SOME_DIR_NAME)) static FATFS fat_fs; /* mounting info */ @@ -30,8 +32,31 @@ static struct fs_mount_t mp = { .fs_data = &fat_fs, }; +#elif defined(CONFIG_FILE_SYSTEM_EXT2) + +#include + +#define DISK_DRIVE_NAME "SDMMC" +#define DISK_MOUNT_PT "/ext" + +static struct fs_mount_t mp = { + .type = FS_EXT2, + .flags = FS_MOUNT_FLAG_NO_FORMAT, + .storage_dev = (void *)DISK_DRIVE_NAME, + .mnt_point = "/ext", +}; + +#endif + +LOG_MODULE_REGISTER(main); + +#define MAX_PATH 128 +#define SOME_FILE_NAME "some.dat" +#define SOME_DIR_NAME "some" +#define SOME_REQUIRED_LEN MAX(sizeof(SOME_FILE_NAME), sizeof(SOME_DIR_NAME)) + static int lsdir(const char *path); -#ifdef CONFIG_SAMPLE_FATFS_CREATE_SOME_ENTRIES +#ifdef CONFIG_FS_SAMPLE_CREATE_SOME_ENTRIES static bool create_some_entries(const char *base_path) { char path[MAX_PATH]; @@ -71,10 +96,6 @@ static bool create_some_entries(const char *base_path) } #endif -/* -* Note the fatfs library is able to mount only strings inside _VOLUME_STRS -* in ffconf.h -*/ static const char *disk_mount_pt = DISK_MOUNT_PT; int main(void) @@ -113,10 +134,14 @@ int main(void) int res = fs_mount(&mp); +#if defined(CONFIG_FAT_FILESYSTEM_ELM) if (res == FR_OK) { +#else + if (res == 0) { +#endif printk("Disk mounted.\n"); if (lsdir(disk_mount_pt) == 0) { -#ifdef CONFIG_SAMPLE_FATFS_CREATE_SOME_ENTRIES +#ifdef CONFIG_FS_SAMPLE_CREATE_SOME_ENTRIES if (create_some_entries(disk_mount_pt)) { lsdir(disk_mount_pt); } @@ -126,6 +151,8 @@ int main(void) printk("Error mounting disk.\n"); } + fs_unmount(&mp); + while (1) { k_sleep(K_MSEC(1000)); } diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index f8a7a51c5c9c9..0c1bce7425f16 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -14,10 +14,13 @@ if(CONFIG_FILE_SYSTEM) LFS_CONFIG=zephyr_lfs_config.h ) + add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_EXT2 ext2) + zephyr_library_link_libraries(FS) target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT) target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS FS INTERFACE LITTLEFS) + target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_EXT2 FS INTERFACE EXT2) endif() add_subdirectory_ifdef(CONFIG_FCB ./fcb) diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index a019b01395be0..fcc38312a6642 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -85,6 +85,7 @@ config FUSE_FS_ACCESS rsource "Kconfig.fatfs" rsource "Kconfig.littlefs" +rsource "ext2/Kconfig" endif # FILE_SYSTEM diff --git a/subsys/fs/ext2/CMakeLists.txt b/subsys/fs/ext2/CMakeLists.txt new file mode 100644 index 0000000000000..e66c8895a4cd6 --- /dev/null +++ b/subsys/fs/ext2/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +add_library(EXT2 INTERFACE) +target_include_directories(EXT2 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +zephyr_library() +zephyr_library_sources( + ext2_ops.c + ext2_impl.c + ext2_disk_access.c + ext2_bitmap.c + ext2_diskops.c +) +zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_MKFS ext2_format.c) + +zephyr_library_link_libraries(EXT2) diff --git a/subsys/fs/ext2/Kconfig b/subsys/fs/ext2/Kconfig new file mode 100644 index 0000000000000..f3beec883716f --- /dev/null +++ b/subsys/fs/ext2/Kconfig @@ -0,0 +1,48 @@ +# Copyright (c) 2023 Antmicro +# SPDX-License-Identifier: Apache-2.0 + +# Ext2 file system + +config FILE_SYSTEM_EXT2 + bool "Ext2 file system support" + depends on FILE_SYSTEM + help + Enable Ext2 file system support. + +module = EXT2 +module-str = Ext2 +source "subsys/logging/Kconfig.template.log_config" + +if FILE_SYSTEM_EXT2 + +menu "Ext2 file system Settings" + visible if FILE_SYSTEM_EXT2 + +config MAX_FILES + int "Maximum number of opened inodes" + default 10 + +config EXT2_MAX_BLOCK_SIZE + int "Maximum size of supported block" + range 1024 4096 + default 4096 + help + This flag is used to determine size of internal structures that + are used to store fetched blocks. + +config EXT2_MAX_BLOCK_COUNT + int "Maximum number of blocks that might be used" + default 10 + help + This flag is used to determine size of internal structures that + are used to store fetched blocks. + +config EXT2_DISK_STARTING_SECTOR + int "Ext2 starting sector" + default 0 + help + The current Ext2 implementation does not support GUID Partition Table. The starting sector + of the file system must be specified by this option. + +endmenu +endif diff --git a/subsys/fs/ext2/ext2.h b/subsys/fs/ext2/ext2.h new file mode 100644 index 0000000000000..394229fa2e26c --- /dev/null +++ b/subsys/fs/ext2/ext2.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __EXT2_H__ +#define __EXT2_H__ + +#define EXT2_SUPERBLOCK_OFFSET 1024 +#define EXT2_MAGIC_NUMBER 0xEF53 +#define EXT2_MAX_FILE_NAME 255 +#define EXT2_ROOT_INODE 2 +#define EXT2_RESERVED_INODES 10 +#define EXT2_GOOD_OLD_INODE_SIZE 128 + +#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 /* Disk/File compression is used */ +#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 /* Directory entries record the file type */ +#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Filesystem needs recovery */ +#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Filesystem has a separate journal device */ +#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 /* Meta block groups */ + +#define EXT2_FEATURE_INCOMPAT_SUPPORTED (EXT2_FEATURE_INCOMPAT_FILETYPE) + +#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 /* Sparse Superblock */ +#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 /* Large file support, 64-bit file size */ +#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 /* Binary tree sorted directory files */ + +#define EXT2_FEATURE_RO_COMPAT_SUPPORTED (0) + +#define EXT2_INODE_BLOCKS 15 /* number of blocks referenced by inode i_block field */ +#define EXT2_INODE_BLOCK_DIRECT 12 +#define EXT2_INODE_BLOCK_1LVL 12 +#define EXT2_INODE_BLOCK_2LVL 13 +#define EXT2_INODE_BLOCK_3LVL 14 + +/* Inode mode flags */ +#define EXT2_S_IFMT 0xF000 /* format mask */ +#define EXT2_S_IFSOCK 0xC000 /* socket */ +#define EXT2_S_IFLNK 0xA000 /* symbolic link */ +#define EXT2_S_IFREG 0x8000 /* regular file */ +#define EXT2_S_IFBLK 0x6000 /* block device */ +#define EXT2_S_IFDIR 0x4000 /* directory */ +#define EXT2_S_IFCHR 0x2000 /* character device */ +#define EXT2_S_IFIFO 0x1000 /* fifo */ + +#define EXT2_S_IRUSR 0x100 /* owner may read */ +#define EXT2_S_IWUSR 0x080 /* owner may write */ +#define EXT2_S_IXUSR 0x040 /* owner may execute */ +#define EXT2_S_IRGRP 0x020 /* group members may read */ +#define EXT2_S_IWGRP 0x010 /* group members may write */ +#define EXT2_S_IXGRP 0x008 /* group members may execute */ +#define EXT2_S_IROTH 0x004 /* others may read */ +#define EXT2_S_IWOTH 0x002 /* others may write */ +#define EXT2_S_IXOTH 0x001 /* others may execute */ + +/* Default file mode: rw-r--r-- */ +#define EXT2_DEF_FILE_MODE \ + (EXT2_S_IFREG | \ + EXT2_S_IRUSR | EXT2_S_IWUSR | \ + EXT2_S_IRGRP | \ + EXT2_S_IROTH) + +/* Default dir mode: rwxr-xr-x */ +#define EXT2_DEF_DIR_MODE \ + (EXT2_S_IFDIR | \ + EXT2_S_IRUSR | EXT2_S_IWUSR | EXT2_S_IXUSR | \ + EXT2_S_IRGRP | EXT2_S_IXGRP | \ + EXT2_S_IROTH | EXT2_S_IXOTH) + +#define IS_REG_FILE(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFREG) +#define IS_DIR(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFDIR) + +/* Directory file type flags */ +#define EXT2_FT_UNKNOWN 0 +#define EXT2_FT_REG_FILE 1 +#define EXT2_FT_DIR 2 +#define EXT2_FT_CHRDEV 3 +#define EXT2_FT_BLKDEV 4 +#define EXT2_FT_FIFO 5 +#define EXT2_FT_SOCK 6 +#define EXT2_FT_SYMLINK 7 +#define EXT2_FT_MAX 8 + +/* Superblock status flags. + * When file system is mounted the status is set to EXT2_ERROR_FS. + * When file system is cleanly unmounted then flag is reset to EXT2_VALID_FS. + */ +#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */ +#define EXT2_ERROR_FS 0x0002 /* Errors detected */ + +/* Revision flags. */ +#define EXT2_GOOD_OLD_REV 0x0 /* Revision 0 */ +#define EXT2_DYNAMIC_REV 0x1 /* Revision 1 */ + +/* Strategy when error detected. */ +#define EXT2_ERRORS_CONTINUE 1 /* Continue as if nothing happened. */ +#define EXT2_ERRORS_RO 2 /* Mount read only. */ +#define EXT2_ERRORS_PANIC 3 /* Cause kernel panic. */ + +#endif /* __EXT2_H__ */ diff --git a/subsys/fs/ext2/ext2_bitmap.c b/subsys/fs/ext2/ext2_bitmap.c new file mode 100644 index 0000000000000..3eeb2e9db1b9f --- /dev/null +++ b/subsys/fs/ext2/ext2_bitmap.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "ext2_bitmap.h" + +LOG_MODULE_DECLARE(ext2); + +/* NOTICE: Offsets in bitmap start with 0 */ + +int ext2_bitmap_set(uint8_t *bm, uint32_t index, uint32_t size) +{ + LOG_DBG("Setting %d bit in bitmap", index); + + uint32_t idx = index / 8; + uint32_t off = index % 8; + + if (idx >= size) { + LOG_ERR("Tried to set value outside of bitmap (%d)", index); + return -EINVAL; + } + + __ASSERT((bm[idx] & BIT(off)) == 0, "Bit %d set in bitmap", index); + + LOG_DBG("Bitmap %d: %x", idx, bm[idx]); + bm[idx] |= BIT(off); + LOG_DBG("Bitmap %d: %x", idx, bm[idx]); + + return 0; +} + +int ext2_bitmap_unset(uint8_t *bm, uint32_t index, uint32_t size) +{ + LOG_DBG("Unsetting %d bit in bitmap", index); + + uint32_t idx = index / 8; + uint32_t off = index % 8; + + if (idx >= size) { + LOG_ERR("Tried to unset value outside of bitmap (%d)", index); + return -EINVAL; + } + + __ASSERT(bm[idx] & BIT(off), "Bit %d not set in bitmap", index); + + LOG_DBG("Bitmap %d: %x", idx, bm[idx]); + bm[idx] &= ~BIT(off); + LOG_DBG("Bitmap %d: %x", idx, bm[idx]); + + return 0; +} + +int32_t ext2_bitmap_find_free(uint8_t *bm, uint32_t size) +{ + for (int i = 0; i < size; ++i) { + LOG_DBG("Bitmap %d: %x (%x)", i, bm[i], ~bm[i]); + if (bm[i] < UINT8_MAX) { + /* not all bits are set here */ + int off = find_lsb_set(~bm[i]) - 1; + + LOG_DBG("off: %d", off); + return off + i * 8; + } + } + return -ENOSPC; +} + +uint32_t ext2_bitmap_count_set(uint8_t *bm, uint32_t size) +{ + int32_t count = 0; + + for (uint32_t i = 0; i < size; i += 8) { + uint8_t val = bm[i / 8]; + + for (int b = 0; b < 8 && i + b < size; ++b) { + count += (val >> b) & BIT(0); + } + + } + return count; +} diff --git a/subsys/fs/ext2/ext2_bitmap.h b/subsys/fs/ext2/ext2_bitmap.h new file mode 100644 index 0000000000000..949194ad74226 --- /dev/null +++ b/subsys/fs/ext2/ext2_bitmap.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __EXT2_BITMAP_H__ +#define __EXT2_BITMAP_H__ + +#include +#include + +/* Functions to make operations on bitmaps. + * + * NOTICE: Assumed size of the bitmap is 256 B (1024 bits). + * (Hence, the greatest valid index is 1023.) + */ + +/** + * @brief Set bit at given index to one + * + * @param bm Pointer to bitmap + * @param index Index in bitmap + * @param size Size of bitmap in bytes + * + * @retval 0 on success; + * @retval -EINVAL when index is too big; + */ +int ext2_bitmap_set(uint8_t *bm, uint32_t index, uint32_t size); + +/** + * @brief Set bit at given index to zero + * + * @param bm Pointer to bitmap + * @param index Index in bitmap + * @param size Size of bitmap in bytes + * + * @retval 0 on success; + * @retval -EINVAL when index is too big; + */ +int ext2_bitmap_unset(uint8_t *bm, uint32_t index, uint32_t size); + +/** + * @brief Find first bit set to zero in bitmap + * + * @param bm Pointer to bitmap + * @param size Size of bitmap in bytes + * + * @retval >0 found inode number; + * @retval -ENOSPC when not found; + */ +int32_t ext2_bitmap_find_free(uint8_t *bm, uint32_t size); + +/** + * @brief Helper function to count bits set in bitmap + * + * @param bm Pointer to bitmap + * @param size Size of bitmap in bits + * + * @retval Number of set bits in bitmap; + */ +uint32_t ext2_bitmap_count_set(uint8_t *bm, uint32_t size); + +#endif /* __EXT2_BITMAP_H__ */ diff --git a/subsys/fs/ext2/ext2_disk_access.c b/subsys/fs/ext2/ext2_disk_access.c new file mode 100644 index 0000000000000..96ded31f8e371 --- /dev/null +++ b/subsys/fs/ext2/ext2_disk_access.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "ext2.h" +#include "ext2_struct.h" + +LOG_MODULE_DECLARE(ext2); + +static struct disk_data { + const char *name; + uint32_t sector_size; + uint32_t sector_count; +} disk_data; + +static int64_t disk_access_device_size(struct ext2_data *fs) +{ + struct disk_data *disk = fs->backend; + + return disk->sector_count * disk->sector_size; +} + +static int64_t disk_access_write_size(struct ext2_data *fs) +{ + struct disk_data *disk = fs->backend; + + return disk->sector_size; +} + +static int disk_read(const char *disk, uint8_t *buf, uint32_t start, uint32_t num) +{ + int rc, loop = 0; + + do { + rc = disk_access_ioctl(disk, DISK_IOCTL_CTRL_SYNC, NULL); + if (rc == 0) { + rc = disk_access_read(disk, buf, start, num); + LOG_DBG("disk read: (start:%d, num:%d) (ret: %d)", start, num, rc); + } + } while ((rc == -EBUSY) && (loop++ < 16)); + return rc; +} + +static int disk_write(const char *disk, const uint8_t *buf, uint32_t start, uint32_t num) +{ + int rc, loop = 0; + + do { + rc = disk_access_ioctl(disk, DISK_IOCTL_CTRL_SYNC, NULL); + if (rc == 0) { + rc = disk_access_write(disk, buf, start, num); + LOG_DBG("disk write: (start:%d, num:%d) (ret: %d)", start, num, rc); + } + } while ((rc == -EBUSY) && (loop++ < 16)); + return rc; +} + +static int disk_prepare_range(struct disk_data *disk, uint32_t addr, uint32_t size, + uint32_t *s_start, uint32_t *s_count) +{ + *s_start = CONFIG_EXT2_DISK_STARTING_SECTOR + addr / disk->sector_size; + *s_count = size / disk->sector_size; + + LOG_DBG("addr:0x%x size:0x%x -> sector_start:%d sector_count:%d", + addr, size, *s_start, *s_count); + + /* Check for overflow. */ + if (*s_count > UINT32_MAX - *s_start) { + LOG_ERR("Requested range (%d:+%d) can't be accessed due to overflow.", + *s_start, *s_count); + return -ENOSPC; + } + + /* Cannot read or write outside the disk. */ + if (*s_start + *s_count > disk->sector_count) { + LOG_ERR("Requested sectors: %d-%d are outside of disk (num_sectors: %d)", + *s_start, *s_start + *s_count, disk->sector_count); + return -ENOSPC; + } + return 0; +} + +static int disk_access_read_block(struct ext2_data *fs, void *buf, uint32_t block) +{ + int rc; + struct disk_data *disk = fs->backend; + uint32_t sector_start, sector_count; + + rc = disk_prepare_range(disk, block * fs->block_size, fs->block_size, + §or_start, §or_count); + if (rc < 0) { + return rc; + } + return disk_read(disk->name, buf, sector_start, sector_count); +} + +static int disk_access_write_block(struct ext2_data *fs, const void *buf, uint32_t block) +{ + int rc; + struct disk_data *disk = fs->backend; + uint32_t sector_start, sector_count; + + rc = disk_prepare_range(disk, block * fs->block_size, fs->block_size, + §or_start, §or_count); + if (rc < 0) { + return rc; + } + return disk_write(disk->name, buf, sector_start, sector_count); +} + +static int disk_access_read_superblock(struct ext2_data *fs, struct ext2_disk_superblock *sb) +{ + int rc; + struct disk_data *disk = fs->backend; + uint32_t sector_start, sector_count; + + rc = disk_prepare_range(disk, EXT2_SUPERBLOCK_OFFSET, sizeof(struct ext2_disk_superblock), + §or_start, §or_count); + if (rc < 0) { + return rc; + } + return disk_read(disk->name, (uint8_t *)sb, sector_start, sector_count); +} + +static int disk_access_sync(struct ext2_data *fs) +{ + struct disk_data *disk = fs->backend; + + LOG_DBG("Sync disk %s", disk->name); + return disk_access_ioctl(disk->name, DISK_IOCTL_CTRL_SYNC, NULL); +} + +static const struct ext2_backend_ops disk_access_ops = { + .get_device_size = disk_access_device_size, + .get_write_size = disk_access_write_size, + .read_block = disk_access_read_block, + .write_block = disk_access_write_block, + .read_superblock = disk_access_read_superblock, + .sync = disk_access_sync, +}; + +int ext2_init_disk_access_backend(struct ext2_data *fs, const void *storage_dev, int flags) +{ + int rc; + uint32_t sector_size, sector_count; + const char *name = (const char *)storage_dev; + + rc = disk_access_init(name); + if (rc < 0) { + LOG_ERR("FAIL: unable to find disk %s: %d\n", name, rc); + return rc; + } + + rc = disk_access_ioctl(name, DISK_IOCTL_GET_SECTOR_COUNT, §or_count); + if (rc < 0) { + LOG_ERR("Disk access (sector count) error: %d", rc); + return rc; + } + + rc = disk_access_ioctl(name, DISK_IOCTL_GET_SECTOR_SIZE, §or_size); + if (rc < 0) { + LOG_ERR("Disk access (sector size) error: %d", rc); + return rc; + } + + disk_data = (struct disk_data) { + .name = storage_dev, + .sector_size = sector_size, + .sector_count = sector_count, + }; + + fs->backend = &disk_data; + fs->backend_ops = &disk_access_ops; + return 0; +} diff --git a/subsys/fs/ext2/ext2_diskops.c b/subsys/fs/ext2/ext2_diskops.c new file mode 100644 index 0000000000000..3713f7cf69da5 --- /dev/null +++ b/subsys/fs/ext2/ext2_diskops.c @@ -0,0 +1,1164 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "ext2.h" +#include "ext2_struct.h" +#include "ext2_impl.h" +#include "ext2_diskops.h" +#include "ext2_bitmap.h" + +LOG_MODULE_DECLARE(ext2); + +/* Static declarations */ +static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offsets[4]); +static inline uint32_t get_ngroups(struct ext2_data *fs); + +#define MAX_OFFSETS_SIZE 4 +/* Array of zeros to be used in inode block calculation */ +static const uint32_t zero_offsets[MAX_OFFSETS_SIZE]; + +static void fill_sblock(struct ext2_superblock *sb, struct ext2_disk_superblock *disk_sb) +{ + sb->s_inodes_count = sys_le32_to_cpu(disk_sb->s_inodes_count); + sb->s_blocks_count = sys_le32_to_cpu(disk_sb->s_blocks_count); + sb->s_free_blocks_count = sys_le32_to_cpu(disk_sb->s_free_blocks_count); + sb->s_free_inodes_count = sys_le32_to_cpu(disk_sb->s_free_inodes_count); + sb->s_first_data_block = sys_le32_to_cpu(disk_sb->s_first_data_block); + sb->s_log_block_size = sys_le32_to_cpu(disk_sb->s_log_block_size); + sb->s_log_frag_size = sys_le32_to_cpu(disk_sb->s_log_frag_size); + sb->s_blocks_per_group = sys_le32_to_cpu(disk_sb->s_blocks_per_group); + sb->s_frags_per_group = sys_le32_to_cpu(disk_sb->s_frags_per_group); + sb->s_inodes_per_group = sys_le32_to_cpu(disk_sb->s_inodes_per_group); + sb->s_mnt_count = sys_le16_to_cpu(disk_sb->s_mnt_count); + sb->s_max_mnt_count = sys_le16_to_cpu(disk_sb->s_max_mnt_count); + sb->s_magic = sys_le16_to_cpu(disk_sb->s_magic); + sb->s_state = sys_le16_to_cpu(disk_sb->s_state); + sb->s_errors = sys_le16_to_cpu(disk_sb->s_errors); + sb->s_creator_os = sys_le32_to_cpu(disk_sb->s_creator_os); + sb->s_rev_level = sys_le32_to_cpu(disk_sb->s_rev_level); + sb->s_first_ino = sys_le32_to_cpu(disk_sb->s_first_ino); + sb->s_inode_size = sys_le16_to_cpu(disk_sb->s_inode_size); + sb->s_block_group_nr = sys_le16_to_cpu(disk_sb->s_block_group_nr); + sb->s_feature_compat = sys_le32_to_cpu(disk_sb->s_feature_compat); + sb->s_feature_incompat = sys_le32_to_cpu(disk_sb->s_feature_incompat); + sb->s_feature_ro_compat = sys_le32_to_cpu(disk_sb->s_feature_ro_compat); +} + +static void fill_disk_sblock(struct ext2_disk_superblock *disk_sb, struct ext2_superblock *sb) +{ + disk_sb->s_inodes_count = sys_cpu_to_le32(sb->s_inodes_count); + disk_sb->s_blocks_count = sys_cpu_to_le32(sb->s_blocks_count); + disk_sb->s_free_blocks_count = sys_cpu_to_le32(sb->s_free_blocks_count); + disk_sb->s_free_inodes_count = sys_cpu_to_le32(sb->s_free_inodes_count); + disk_sb->s_first_data_block = sys_cpu_to_le32(sb->s_first_data_block); + disk_sb->s_log_block_size = sys_cpu_to_le32(sb->s_log_block_size); + disk_sb->s_log_frag_size = sys_cpu_to_le32(sb->s_log_frag_size); + disk_sb->s_blocks_per_group = sys_cpu_to_le32(sb->s_blocks_per_group); + disk_sb->s_frags_per_group = sys_cpu_to_le32(sb->s_frags_per_group); + disk_sb->s_inodes_per_group = sys_cpu_to_le32(sb->s_inodes_per_group); + disk_sb->s_mnt_count = sys_cpu_to_le16(sb->s_mnt_count); + disk_sb->s_max_mnt_count = sys_cpu_to_le16(sb->s_max_mnt_count); + disk_sb->s_magic = sys_cpu_to_le16(sb->s_magic); + disk_sb->s_state = sys_cpu_to_le16(sb->s_state); + disk_sb->s_errors = sys_cpu_to_le16(sb->s_errors); + disk_sb->s_creator_os = sys_cpu_to_le32(sb->s_creator_os); + disk_sb->s_rev_level = sys_cpu_to_le32(sb->s_rev_level); + disk_sb->s_first_ino = sys_cpu_to_le32(sb->s_first_ino); + disk_sb->s_inode_size = sys_cpu_to_le16(sb->s_inode_size); + disk_sb->s_block_group_nr = sys_cpu_to_le16(sb->s_block_group_nr); + disk_sb->s_feature_compat = sys_cpu_to_le32(sb->s_feature_compat); + disk_sb->s_feature_incompat = sys_cpu_to_le32(sb->s_feature_incompat); + disk_sb->s_feature_ro_compat = sys_cpu_to_le32(sb->s_feature_ro_compat); +} + +static void fill_bgroup(struct ext2_bgroup *bg, struct ext2_disk_bgroup *disk_bg) +{ + bg->bg_block_bitmap = sys_le32_to_cpu(disk_bg->bg_block_bitmap); + bg->bg_inode_bitmap = sys_le32_to_cpu(disk_bg->bg_inode_bitmap); + bg->bg_inode_table = sys_le32_to_cpu(disk_bg->bg_inode_table); + bg->bg_free_blocks_count = sys_le16_to_cpu(disk_bg->bg_free_blocks_count); + bg->bg_free_inodes_count = sys_le16_to_cpu(disk_bg->bg_free_inodes_count); + bg->bg_used_dirs_count = sys_le16_to_cpu(disk_bg->bg_used_dirs_count); +} + +static void fill_disk_bgroup(struct ext2_disk_bgroup *disk_bg, struct ext2_bgroup *bg) +{ + disk_bg->bg_block_bitmap = sys_cpu_to_le32(bg->bg_block_bitmap); + disk_bg->bg_inode_bitmap = sys_cpu_to_le32(bg->bg_inode_bitmap); + disk_bg->bg_inode_table = sys_cpu_to_le32(bg->bg_inode_table); + disk_bg->bg_free_blocks_count = sys_cpu_to_le16(bg->bg_free_blocks_count); + disk_bg->bg_free_inodes_count = sys_cpu_to_le16(bg->bg_free_inodes_count); + disk_bg->bg_used_dirs_count = sys_cpu_to_le16(bg->bg_used_dirs_count); +} + +static void fill_inode(struct ext2_inode *inode, struct ext2_disk_inode *dino) +{ + inode->i_mode = sys_le16_to_cpu(dino->i_mode); + inode->i_size = sys_le32_to_cpu(dino->i_size); + inode->i_links_count = sys_le16_to_cpu(dino->i_links_count); + inode->i_blocks = sys_le32_to_cpu(dino->i_blocks); + for (int i = 0; i < EXT2_INODE_BLOCKS; i++) { + inode->i_block[i] = sys_le32_to_cpu(dino->i_block[i]); + } +} + +static void fill_disk_inode(struct ext2_disk_inode *dino, struct ext2_inode *inode) +{ + dino->i_mode = sys_cpu_to_le16(inode->i_mode); + dino->i_size = sys_cpu_to_le32(inode->i_size); + dino->i_links_count = sys_cpu_to_le16(inode->i_links_count); + dino->i_blocks = sys_cpu_to_le32(inode->i_blocks); + for (int i = 0; i < EXT2_INODE_BLOCKS; i++) { + dino->i_block[i] = sys_cpu_to_le32(inode->i_block[i]); + } +} + +struct ext2_direntry *ext2_fetch_direntry(struct ext2_disk_direntry *disk_de) +{ + + if (disk_de->de_name_len > EXT2_MAX_FILE_NAME) { + return NULL; + } + uint32_t prog_rec_len = sizeof(struct ext2_direntry) + disk_de->de_name_len; + struct ext2_direntry *de = k_heap_alloc(&direntry_heap, prog_rec_len, K_FOREVER); + + __ASSERT(de != NULL, "allocated direntry can't be NULL"); + + de->de_inode = sys_le32_to_cpu(disk_de->de_inode); + de->de_rec_len = sys_le16_to_cpu(disk_de->de_rec_len); + de->de_name_len = disk_de->de_name_len; + de->de_file_type = disk_de->de_file_type; + memcpy(de->de_name, disk_de->de_name, de->de_name_len); + return de; +} + +void ext2_write_direntry(struct ext2_disk_direntry *disk_de, struct ext2_direntry *de) +{ + disk_de->de_inode = sys_le32_to_cpu(de->de_inode); + disk_de->de_rec_len = sys_le16_to_cpu(de->de_rec_len); + disk_de->de_name_len = de->de_name_len; + disk_de->de_file_type = de->de_file_type; + memcpy(disk_de->de_name, de->de_name, de->de_name_len); +} + +uint32_t ext2_get_disk_direntry_inode(struct ext2_disk_direntry *de) +{ + return sys_le32_to_cpu(de->de_inode); +} + +uint32_t ext2_get_disk_direntry_reclen(struct ext2_disk_direntry *de) +{ + return sys_le16_to_cpu(de->de_rec_len); +} + +uint8_t ext2_get_disk_direntry_namelen(struct ext2_disk_direntry *de) +{ + return de->de_name_len; +} + +uint8_t ext2_get_disk_direntry_type(struct ext2_disk_direntry *de) +{ + return de->de_file_type; +} + +void ext2_set_disk_direntry_inode(struct ext2_disk_direntry *de, uint32_t inode) +{ + de->de_inode = sys_cpu_to_le32(inode); +} + +void ext2_set_disk_direntry_reclen(struct ext2_disk_direntry *de, uint16_t reclen) +{ + de->de_rec_len = sys_cpu_to_le16(reclen); +} + +void ext2_set_disk_direntry_namelen(struct ext2_disk_direntry *de, uint8_t namelen) +{ + de->de_name_len = namelen; +} + +void ext2_set_disk_direntry_type(struct ext2_disk_direntry *de, uint8_t type) +{ + de->de_file_type = type; +} + +void ext2_set_disk_direntry_name(struct ext2_disk_direntry *de, const char *name, size_t len) +{ + memcpy(de->de_name, name, len); +} + +int ext2_fetch_superblock(struct ext2_data *fs) +{ + struct ext2_block *b; + uint32_t sblock_offset; + + if (fs->block_size == 1024) { + sblock_offset = 0; + b = ext2_get_block(fs, 1); + } else { + sblock_offset = 1024; + b = ext2_get_block(fs, 0); + } + if (b == NULL) { + return -ENOENT; + } + + struct ext2_disk_superblock *disk_sb = + (struct ext2_disk_superblock *)(b->data + sblock_offset); + + fill_sblock(&fs->sblock, disk_sb); + + ext2_drop_block(b); + return 0; +} + +static inline uint32_t get_ngroups(struct ext2_data *fs) +{ + uint32_t ngroups = + fs->sblock.s_blocks_count / fs->sblock.s_blocks_per_group; + + if (fs->sblock.s_blocks_count % fs->sblock.s_blocks_per_group != 0) { + /* there is one more group if the last group is incomplete */ + ngroups += 1; + } + return ngroups; +} + +int ext2_fetch_block_group(struct ext2_data *fs, uint32_t group) +{ + struct ext2_bgroup *bg = &fs->bgroup; + + /* Check if block group is cached */ + if (group == bg->num) { + return 0; + } + + uint32_t ngroups = get_ngroups(fs); + + LOG_DBG("ngroups:%d", ngroups); + LOG_DBG("cur_group:%d fetch_group:%d", bg->num, group); + + if (group > ngroups) { + return -ERANGE; + } + + uint32_t groups_per_block = fs->block_size / sizeof(struct ext2_disk_bgroup); + uint32_t block = group / groups_per_block; + uint32_t offset = group % groups_per_block; + uint32_t global_block = fs->sblock.s_first_data_block + 1 + block; + + struct ext2_block *b = ext2_get_block(fs, global_block); + + if (b == NULL) { + return -ENOENT; + } + + struct ext2_disk_bgroup *disk_bg = ((struct ext2_disk_bgroup *)b->data) + offset; + + fill_bgroup(bg, disk_bg); + + /* Drop unused block */ + ext2_drop_block(b); + + /* Invalidate previously fetched blocks */ + ext2_drop_block(bg->inode_table); + ext2_drop_block(bg->inode_bitmap); + ext2_drop_block(bg->block_bitmap); + bg->inode_table = bg->inode_bitmap = bg->block_bitmap = NULL; + + bg->fs = fs; + bg->num = group; + + LOG_DBG("[BG:%d] itable:%d free_blk:%d free_ino:%d useddirs:%d bbitmap:%d ibitmap:%d", + group, bg->bg_inode_table, + bg->bg_free_blocks_count, + bg->bg_free_inodes_count, + bg->bg_used_dirs_count, + bg->bg_block_bitmap, + bg->bg_inode_bitmap); + return 0; +} + +int ext2_fetch_bg_itable(struct ext2_bgroup *bg, uint32_t block) +{ + if (bg->inode_table && bg->inode_table_block == block) { + return 0; + } + + struct ext2_data *fs = bg->fs; + uint32_t global_block = bg->bg_inode_table + block; + + ext2_drop_block(bg->inode_table); + bg->inode_table = ext2_get_block(fs, global_block); + if (bg->inode_table == NULL) { + return -ENOENT; + } + + bg->inode_table_block = block; + return 0; +} + +int ext2_fetch_bg_ibitmap(struct ext2_bgroup *bg) +{ + if (bg->inode_bitmap) { + return 0; + } + + struct ext2_data *fs = bg->fs; + uint32_t global_block = bg->bg_inode_bitmap; + + bg->inode_bitmap = ext2_get_block(fs, global_block); + if (bg->inode_bitmap == NULL) { + return -ENOENT; + } + return 0; +} + +int ext2_fetch_bg_bbitmap(struct ext2_bgroup *bg) +{ + if (bg->block_bitmap) { + return 0; + } + + struct ext2_data *fs = bg->fs; + uint32_t global_block = bg->bg_block_bitmap; + + bg->block_bitmap = ext2_get_block(fs, global_block); + if (bg->block_bitmap == NULL) { + return -ENOENT; + } + return 0; +} + +/** + * @brief Fetch block group and inode table of given inode. + * + * @return Offset of inode in currently fetched inode table block. + */ +static int32_t get_itable_entry(struct ext2_data *fs, uint32_t ino) +{ + int rc; + uint32_t ino_group = (ino - 1) / fs->sblock.s_inodes_per_group; + uint32_t ino_index = (ino - 1) % fs->sblock.s_inodes_per_group; + + LOG_DBG("ino_group:%d ino_index:%d", ino_group, ino_index); + + rc = ext2_fetch_block_group(fs, ino_group); + if (rc < 0) { + return rc; + } + + uint32_t inode_size = fs->sblock.s_inode_size; + uint32_t inodes_per_block = fs->block_size / inode_size; + + uint32_t block_index = ino_index / inodes_per_block; + uint32_t block_offset = ino_index % inodes_per_block; + + LOG_DBG("block_index:%d block_offset:%d", block_index, block_offset); + + rc = ext2_fetch_bg_itable(&fs->bgroup, block_index); + if (rc < 0) { + return rc; + } + return block_offset; +} + +int ext2_fetch_inode(struct ext2_data *fs, uint32_t ino, struct ext2_inode *inode) +{ + + int32_t itable_offset = get_itable_entry(fs, ino); + + LOG_DBG("fetch inode: %d", ino); + + if (itable_offset < 0) { + return itable_offset; + } + + struct ext2_disk_inode *dino = &BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; + + fill_inode(inode, dino); + + /* Copy needed data into inode structure */ + inode->i_fs = fs; + inode->flags = 0; + inode->i_id = ino; + + LOG_DBG("mode:%d size:%d links:%d", dino->i_mode, dino->i_size, dino->i_links_count); + return 0; +} + +/* + * @param try_current -- if true then check if searched offset matches offset of currently fetched + * block on that level. If they match then it is the block we are looking for. + */ +static int fetch_level_blocks(struct ext2_inode *inode, uint32_t offsets[4], int lvl, int max_lvl, + bool try_current) +{ + uint32_t block; + bool already_fetched = try_current && (offsets[lvl] == inode->offsets[lvl]); + + /* all needed blocks fetched */ + if (lvl > max_lvl) { + return 0; + } + + /* If already fetched block matches desired one we can use it and move to the next level. */ + if (!already_fetched) { + /* Fetched block on current level was wrong. + * We can't use fetched blocks on this and next levels. + */ + try_current = false; + + ext2_drop_block(inode->blocks[lvl]); + + if (lvl == 0) { + block = inode->i_block[offsets[0]]; + } else { + uint32_t *list = (uint32_t *)inode->blocks[lvl - 1]->data; + + block = sys_le32_to_cpu(list[offsets[lvl]]); + } + + if (block == 0) { + inode->blocks[lvl] = ext2_get_empty_block(inode->i_fs); + } else { + inode->blocks[lvl] = ext2_get_block(inode->i_fs, block); + } + + if (inode->blocks[lvl] == NULL) { + return -ENOENT; + } + LOG_DBG("[fetch] lvl:%d off:%d num:%d", lvl, offsets[lvl], block); + } + return fetch_level_blocks(inode, offsets, lvl + 1, max_lvl, try_current); +} + +int ext2_fetch_inode_block(struct ext2_inode *inode, uint32_t block) +{ + /* Check if correct inode block is cached. */ + if (inode->flags & INODE_FETCHED_BLOCK && inode->block_num == block) { + return 0; + } + + LOG_DBG("inode:%d cur_blk:%d fetch_blk:%d", inode->i_id, inode->block_num, block); + + struct ext2_data *fs = inode->i_fs; + int max_lvl, ret; + uint32_t offsets[MAX_OFFSETS_SIZE]; + bool try_current = inode->flags & INODE_FETCHED_BLOCK; + + max_lvl = get_level_offsets(fs, block, offsets); + + ret = fetch_level_blocks(inode, offsets, 0, max_lvl, try_current); + if (ret < 0) { + ext2_inode_drop_blocks(inode); + return ret; + } + + memcpy(inode->offsets, offsets, MAX_OFFSETS_SIZE * sizeof(uint32_t)); + inode->block_lvl = max_lvl; + inode->block_num = block; + inode->flags |= INODE_FETCHED_BLOCK; + + LOG_DBG("[ino:%d fetch]\t Lvl:%d {%d, %d, %d, %d}", inode->i_id, inode->block_lvl, + inode->offsets[0], inode->offsets[1], inode->offsets[2], inode->offsets[3]); + return 0; +} + +static bool all_zero(const uint32_t *offsets, int lvl) +{ + for (int i = 0; i < lvl; ++i) { + if (offsets[i] > 0) { + return false; + } + } + return true; +} + +/** + * @brief delete blocks from one described with offsets array + * + * NOTE: To use this function safely drop all fetched inode blocks + * + * @retval >=0 Number of removed blocks (only the blocks with actual inode data) + * @retval <0 Error + */ +static int64_t delete_blocks(struct ext2_data *fs, uint32_t block_num, int lvl, + const uint32_t *offsets) +{ + __ASSERT(block_num != 0, "Can't delete zero block"); + __ASSERT(lvl >= 0 && lvl < MAX_OFFSETS_SIZE, + "Expected 0 <= lvl < %d (got: lvl=%d)", lvl, MAX_OFFSETS_SIZE); + + int ret; + int64_t removed = 0, rem; + uint32_t *list, start_blk; + struct ext2_block *list_block = NULL; + bool remove_current = false; + bool block_dirty = false; + + if (lvl == 0) { + /* If we got here we will remove this block + * and it is also a block with actual inode data, hence we count it. + */ + remove_current = true; + removed++; + } else { + /* Current block holds a list of blocks. */ + list_block = ext2_get_block(fs, block_num); + + if (list_block == NULL) { + return -ENOENT; + } + list = (uint32_t *)list_block->data; + + if (all_zero(offsets, lvl)) { + /* We remove all blocks that are referenced by current block, hence current + * block isn't needed anymore. + */ + remove_current = true; + start_blk = 0; + + } else if (lvl == 1) { + /* We are on one before last layer of inode block table. The next layer are + * single blocks, hence we will just remove them. + * We can just set start_blk here and remove blocks in loop at the end of + * this function. + */ + start_blk = offsets[0]; + + } else { + uint32_t block_num = sys_le32_to_cpu(list[offsets[0]]); + + /* We don't remove all blocks referenced by current block. We have to use + * offsets to decide which part of next block we want to remove. + */ + if (block_num == 0) { + LOG_ERR("Inode block that references other blocks must be nonzero"); + fs->flags |= EXT2_DATA_FLAGS_ERR; + removed = -EINVAL; + goto out; + } + + /* We will start removing whole blocks from next block on this level */ + start_blk = offsets[0] + 1; + + /* Remove desired part of lower level block. */ + rem = delete_blocks(fs, block_num, lvl - 1, &offsets[1]); + if (rem < 0) { + removed = rem; + goto out; + } + removed += rem; + } + + /* Iterate over blocks that will be entirely deleted */ + for (uint32_t i = start_blk; i < fs->block_size / EXT2_BLOCK_NUM_SIZE; ++i) { + uint32_t block_num = sys_le32_to_cpu(list[i]); + + if (block_num == 0) { + continue; + } + rem = delete_blocks(fs, block_num, lvl - 1, zero_offsets); + if (rem < 0) { + removed = rem; + goto out; + } + removed += rem; + list[i] = 0; + block_dirty = true; + } + } + + if (remove_current) { + LOG_DBG("free block %d (lvl %d)", block_num, lvl); + + /* If we remove current block, we don't have to write it's updated content. */ + if (list_block) { + block_dirty = false; + } + + ret = ext2_free_block(fs, block_num); + if (ret < 0) { + removed = ret; + } + } +out: + if (removed >= 0 && list_block && block_dirty) { + ret = ext2_write_block(fs, list_block); + if (ret < 0) { + removed = ret; + } + } + ext2_drop_block(list_block); + + /* On error removed will contain negative error code */ + return removed; +} + +static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offsets[4]) +{ + const uint32_t B = fs->block_size / EXT2_BLOCK_NUM_SIZE; + const uint32_t lvl0_blks = EXT2_INODE_BLOCK_1LVL; + const uint32_t lvl1_blks = B; + const uint32_t lvl2_blks = B * B; + const uint32_t lvl3_blks = B * B * B; + + if (block < 0) { + return -EINVAL; + } + + /* Level 0 */ + if (block < lvl0_blks) { + offsets[0] = block; + return 0; + } + + /* Level 1 */ + block -= lvl0_blks; + if (block < lvl1_blks) { + offsets[0] = EXT2_INODE_BLOCK_1LVL; + offsets[1] = block; + return 1; + } + + /* Level 2 */ + block -= lvl1_blks; + if (block < lvl2_blks) { + offsets[0] = EXT2_INODE_BLOCK_2LVL; + offsets[1] = block / B; + offsets[2] = block % B; + return 2; + } + + /* Level 3 */ + if (block < lvl3_blks) { + block -= lvl2_blks; + offsets[0] = EXT2_INODE_BLOCK_3LVL; + offsets[1] = block / (B * B); + offsets[2] = (block % (B * B)) / B; + offsets[3] = (block % (B * B)) % B; + return 3; + } + /* Block number is too large */ + return -EINVAL; +} + +static int block0_level(uint32_t block) +{ + if (block >= EXT2_INODE_BLOCK_1LVL) { + return block - EXT2_INODE_BLOCK_1LVL + 1; + } + return 0; +} + +int64_t ext2_inode_remove_blocks(struct ext2_inode *inode, uint32_t first) +{ + uint32_t start; + int max_lvl; + int64_t ret, removed = 0; + uint32_t offsets[4]; + struct ext2_data *fs = inode->i_fs; + + max_lvl = get_level_offsets(inode->i_fs, first, offsets); + + if (all_zero(offsets, max_lvl)) { + /* We remove also the first block because all blocks referenced from it will be + * deleted. + */ + start = offsets[0]; + } else { + /* There will be some blocks referenced from first affected block hence we can't + * remove it. + */ + if (inode->i_block[offsets[0]] == 0) { + LOG_ERR("Inode block that references other blocks must be nonzero"); + fs->flags |= EXT2_DATA_FLAGS_ERR; + return -EINVAL; + } + + start = offsets[0] + 1; + ret = delete_blocks(inode->i_fs, inode->i_block[offsets[0]], + block0_level(offsets[0]), &offsets[1]); + if (ret < 0) { + return ret; + } + removed += ret; + } + + for (uint32_t i = start; i < EXT2_INODE_BLOCKS; i++) { + if (inode->i_block[i] == 0) { + continue; + } + ret = delete_blocks(inode->i_fs, inode->i_block[i], block0_level(i), + zero_offsets); + if (ret < 0) { + return ret; + } + removed += ret; + inode->i_block[i] = 0; + } + return removed; +} +static int alloc_level_blocks(struct ext2_inode *inode) +{ + int ret = 0; + uint32_t *block; + bool allocated = false; + struct ext2_data *fs = inode->i_fs; + + for (int lvl = 0; lvl <= inode->block_lvl; ++lvl) { + if (lvl == 0) { + block = &inode->i_block[inode->offsets[lvl]]; + } else { + block = &((uint32_t *)inode->blocks[lvl - 1]->data)[inode->offsets[lvl]]; + *block = sys_le32_to_cpu(*block); + } + + if (*block == 0) { + ret = ext2_assign_block_num(fs, inode->blocks[lvl]); + if (ret < 0) { + return ret; + } + + /* Update block from higher level. */ + *block = sys_cpu_to_le32(inode->blocks[lvl]->num); + if (lvl > 0) { + ret = ext2_write_block(fs, inode->blocks[lvl-1]); + if (ret < 0) { + return ret; + } + } + allocated = true; + /* Allocating block on that level implies that blocks on lower levels will + * be allocated too hence we can set allocated here. + */ + LOG_DBG("Alloc lvl:%d (num: %d) %s", lvl, *block, + lvl == inode->block_lvl ? "data" : "indirect"); + } + } + if (allocated) { + /* Update number of reserved blocks. + * (We are always counting 512 size blocks.) + */ + inode->i_blocks += fs->block_size / 512; + ret = ext2_commit_inode(inode); + } + return ret; +} + +int ext2_commit_superblock(struct ext2_data *fs) +{ + int ret; + struct ext2_block *b; + uint32_t sblock_offset; + + if (fs->block_size == 1024) { + sblock_offset = 0; + b = ext2_get_block(fs, 1); + } else { + sblock_offset = 1024; + b = ext2_get_block(fs, 0); + } + if (b == NULL) { + return -ENOENT; + } + + struct ext2_disk_superblock *disk_sb = + (struct ext2_disk_superblock *)(b->data + sblock_offset); + + fill_disk_sblock(disk_sb, &fs->sblock); + + ret = ext2_write_block(fs, b); + if (ret < 0) { + return ret; + } + ext2_drop_block(b); + return 0; +} + +int ext2_commit_bg(struct ext2_data *fs) +{ + int ret; + struct ext2_bgroup *bg = &fs->bgroup; + + uint32_t groups_per_block = fs->block_size / sizeof(struct ext2_disk_bgroup); + uint32_t block = bg->num / groups_per_block; + uint32_t offset = bg->num % groups_per_block; + uint32_t global_block = fs->sblock.s_first_data_block + 1 + block; + + struct ext2_block *b = ext2_get_block(fs, global_block); + + if (b == NULL) { + return -ENOENT; + } + + struct ext2_disk_bgroup *disk_bg = ((struct ext2_disk_bgroup *)b->data) + offset; + + fill_disk_bgroup(disk_bg, bg); + + ret = ext2_write_block(fs, b); + if (ret < 0) { + return ret; + } + ext2_drop_block(b); + return 0; +} + +int ext2_commit_inode(struct ext2_inode *inode) +{ + struct ext2_data *fs = inode->i_fs; + + int32_t itable_offset = get_itable_entry(fs, inode->i_id); + + if (itable_offset < 0) { + return itable_offset; + } + + /* get pointer to proper inode in fetched block */ + struct ext2_disk_inode *dino = &BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; + + /* fill dinode */ + fill_disk_inode(dino, inode); + + return ext2_write_block(fs, fs->bgroup.inode_table); +} + +int ext2_commit_inode_block(struct ext2_inode *inode) +{ + if (!(inode->flags & INODE_FETCHED_BLOCK)) { + return -EINVAL; + } + + int ret; + + LOG_DBG("inode:%d current_blk:%d", inode->i_id, inode->block_num); + + ret = alloc_level_blocks(inode); + if (ret < 0) { + return ret; + } + ret = ext2_write_block(inode->i_fs, inode_current_block(inode)); + return ret; +} + +int ext2_clear_inode(struct ext2_data *fs, uint32_t ino) +{ + int ret; + int32_t itable_offset = get_itable_entry(fs, ino); + + if (itable_offset < 0) { + return itable_offset; + } + + memset(&BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset], 0, sizeof(struct ext2_disk_inode)); + ret = ext2_write_block(fs, fs->bgroup.inode_table); + return ret; +} + +int64_t ext2_alloc_block(struct ext2_data *fs) +{ + int rc, bitmap_slot; + uint32_t group = 0, set; + int32_t total; + + rc = ext2_fetch_block_group(fs, group); + if (rc < 0) { + return rc; + } + + LOG_DBG("Free blocks: %d", fs->bgroup.bg_free_blocks_count); + while ((rc >= 0) && (fs->bgroup.bg_free_blocks_count == 0)) { + group++; + rc = ext2_fetch_block_group(fs, group); + if (rc == -ERANGE) { + /* reached last group */ + return -ENOSPC; + } + } + if (rc < 0) { + return rc; + } + + rc = ext2_fetch_bg_bbitmap(&fs->bgroup); + if (rc < 0) { + return rc; + } + + bitmap_slot = ext2_bitmap_find_free(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->block_size); + if (bitmap_slot < 0) { + LOG_WRN("Cannot find free block in group %d (rc: %d)", group, bitmap_slot); + return bitmap_slot; + } + + /* In bitmap blocks are counted from s_first_data_block hence we have to add this offset. */ + total = group * fs->sblock.s_blocks_per_group + bitmap_slot + fs->sblock.s_first_data_block; + + LOG_DBG("Found free block %d in group %d (total: %d)", bitmap_slot, group, total); + + rc = ext2_bitmap_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), bitmap_slot, fs->block_size); + if (rc < 0) { + return rc; + } + + fs->bgroup.bg_free_blocks_count -= 1; + fs->sblock.s_free_blocks_count -= 1; + + set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->sblock.s_blocks_count); + + if (set != (fs->sblock.s_blocks_count - fs->sblock.s_free_blocks_count)) { + error_behavior(fs, "Wrong number of used blocks in superblock and bitmap"); + return -EINVAL; + } + + rc = ext2_commit_superblock(fs); + if (rc < 0) { + LOG_DBG("super block write returned: %d", rc); + return -EIO; + } + rc = ext2_commit_bg(fs); + if (rc < 0) { + LOG_DBG("block group write returned: %d", rc); + return -EIO; + } + rc = ext2_write_block(fs, fs->bgroup.block_bitmap); + if (rc < 0) { + LOG_DBG("block bitmap write returned: %d", rc); + return -EIO; + } + return total; +} + +static int check_zero_inode(struct ext2_data *fs, uint32_t ino) +{ + int32_t itable_offset = get_itable_entry(fs, ino); + + if (itable_offset < 0) { + return itable_offset; + } + + uint8_t *bytes = (uint8_t *)&BGROUP_INODE_TABLE(&fs->bgroup)[itable_offset]; + + for (int i = 0; i < sizeof(struct ext2_disk_inode); ++i) { + if (bytes[i] != 0) { + return -EINVAL; + } + } + return 0; +} + +int32_t ext2_alloc_inode(struct ext2_data *fs) +{ + int rc, r; + uint32_t group = 0, set; + int32_t global_idx; + + rc = ext2_fetch_block_group(fs, group); + + while (fs->bgroup.bg_free_inodes_count == 0 && rc >= 0) { + group++; + rc = ext2_fetch_block_group(fs, group); + if (rc == -ERANGE) { + /* reached last group */ + return -ENOSPC; + } + } + + if (rc < 0) { + return rc; + } + + LOG_DBG("Free inodes (bg): %d", fs->bgroup.bg_free_inodes_count); + LOG_DBG("Free inodes (sb): %d", fs->sblock.s_free_inodes_count); + + rc = ext2_fetch_bg_ibitmap(&fs->bgroup); + if (rc < 0) { + return rc; + } + + r = ext2_bitmap_find_free(BGROUP_INODE_BITMAP(&fs->bgroup), fs->block_size); + if (r < 0) { + LOG_DBG("Cannot find free inode in group %d (rc: %d)", group, r); + return r; + } + + /* Add 1 because inodes are counted from 1 not 0. */ + global_idx = group * fs->sblock.s_inodes_per_group + r + 1; + + /* Inode table entry for found inode must be cleared. */ + if (check_zero_inode(fs, global_idx) != 0) { + error_behavior(fs, "Inode is not cleared in inode table!"); + return -EINVAL; + } + + LOG_DBG("Found free inode %d in group %d (global_idx: %d)", r, group, global_idx); + + rc = ext2_bitmap_set(BGROUP_INODE_BITMAP(&fs->bgroup), r, fs->block_size); + if (rc < 0) { + return rc; + } + + fs->bgroup.bg_free_inodes_count -= 1; + fs->sblock.s_free_inodes_count -= 1; + + set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), fs->sblock.s_inodes_count); + + if (set != fs->sblock.s_inodes_count - fs->sblock.s_free_inodes_count) { + error_behavior(fs, "Wrong number of used inodes in superblock and bitmap"); + return -EINVAL; + } + + rc = ext2_commit_superblock(fs); + if (rc < 0) { + LOG_DBG("super block write returned: %d", rc); + return -EIO; + } + rc = ext2_commit_bg(fs); + if (rc < 0) { + LOG_DBG("block group write returned: %d", rc); + return -EIO; + } + rc = ext2_write_block(fs, fs->bgroup.inode_bitmap); + if (rc < 0) { + LOG_DBG("block bitmap write returned: %d", rc); + return -EIO; + } + + LOG_DBG("Free inodes (bg): %d", fs->bgroup.bg_free_inodes_count); + LOG_DBG("Free inodes (sb): %d", fs->sblock.s_free_inodes_count); + + return global_idx; +} + +int ext2_free_block(struct ext2_data *fs, uint32_t block) +{ + LOG_DBG("Free block %d", block); + + /* Block bitmaps tracks blocks starting from s_first_data_block. */ + block -= fs->sblock.s_first_data_block; + + int rc; + uint32_t group = block / fs->sblock.s_blocks_per_group; + uint32_t off = block % fs->sblock.s_blocks_per_group; + uint32_t set; + + rc = ext2_fetch_block_group(fs, group); + if (rc < 0) { + return rc; + } + + rc = ext2_fetch_bg_bbitmap(&fs->bgroup); + if (rc < 0) { + return rc; + } + + rc = ext2_bitmap_unset(BGROUP_BLOCK_BITMAP(&fs->bgroup), off, fs->block_size); + if (rc < 0) { + return rc; + } + + fs->bgroup.bg_free_blocks_count += 1; + fs->sblock.s_free_blocks_count += 1; + + set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs->sblock.s_blocks_count); + + if (set != fs->sblock.s_blocks_count - fs->sblock.s_free_blocks_count) { + error_behavior(fs, "Wrong number of used blocks in superblock and bitmap"); + return -EINVAL; + } + + rc = ext2_commit_superblock(fs); + if (rc < 0) { + LOG_DBG("super block write returned: %d", rc); + return -EIO; + } + rc = ext2_commit_bg(fs); + if (rc < 0) { + LOG_DBG("block group write returned: %d", rc); + return -EIO; + } + rc = ext2_write_block(fs, fs->bgroup.block_bitmap); + if (rc < 0) { + LOG_DBG("block bitmap write returned: %d", rc); + return -EIO; + } + return 0; +} + +int ext2_free_inode(struct ext2_data *fs, uint32_t ino, bool directory) +{ + LOG_DBG("Free inode %d", ino); + + int rc; + uint32_t group = (ino - 1) / fs->sblock.s_inodes_per_group; + uint32_t bitmap_off = (ino - 1) % fs->sblock.s_inodes_per_group; + uint32_t set; + + rc = ext2_fetch_block_group(fs, group); + if (rc < 0) { + return rc; + } + + rc = ext2_fetch_bg_ibitmap(&fs->bgroup); + if (rc < 0) { + return rc; + } + + rc = ext2_bitmap_unset(BGROUP_INODE_BITMAP(&fs->bgroup), bitmap_off, fs->block_size); + if (rc < 0) { + return rc; + } + + rc = ext2_clear_inode(fs, ino); + if (rc < 0) { + return rc; + } + + fs->bgroup.bg_free_inodes_count += 1; + fs->sblock.s_free_inodes_count += 1; + + if (directory) { + fs->bgroup.bg_used_dirs_count -= 1; + } + + set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), fs->sblock.s_inodes_count); + + if (set != fs->sblock.s_inodes_count - fs->sblock.s_free_inodes_count) { + error_behavior(fs, "Wrong number of used inodes in superblock and bitmap"); + return -EINVAL; + } + + LOG_INF("Inode %d is free", ino); + + rc = ext2_commit_superblock(fs); + if (rc < 0) { + LOG_DBG("super block write returned: %d", rc); + return -EIO; + } + rc = ext2_commit_bg(fs); + if (rc < 0) { + LOG_DBG("block group write returned: %d", rc); + return -EIO; + } + rc = ext2_write_block(fs, fs->bgroup.inode_bitmap); + if (rc < 0) { + LOG_DBG("block bitmap write returned: %d", rc); + return -EIO; + } + rc = fs->backend_ops->sync(fs); + if (rc < 0) { + return -EIO; + } + return 0; +} diff --git a/subsys/fs/ext2/ext2_diskops.h b/subsys/fs/ext2/ext2_diskops.h new file mode 100644 index 0000000000000..027990f4d6126 --- /dev/null +++ b/subsys/fs/ext2/ext2_diskops.h @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __EXT2_DISKOPS_H__ +#define __EXT2_DISKOPS_H__ + +#include + +#include "ext2_struct.h" + +/** @brief Fetch superblock into buffer in fs structure. + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_superblock(struct ext2_data *fs); + +/** + * @brief Fetch inode into given buffer. + * + * @param fs File system data + * @param ino Inode number + * @param inode Buffer for fetched inode + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_inode(struct ext2_data *fs, uint32_t ino, struct ext2_inode *inode); + +/** + * @brief Fetch block into buffer in the inode structure. + * + * @param inode Inode structure + * @param block Number of inode block to fetch (0 - first block in that inode) + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_inode_block(struct ext2_inode *inode, uint32_t block); + +/** + * @brief Fetch block group into buffer in fs structure. + * + * If the group was already fetched then this function has no effect. + * + * @param fs File system data + * @param group Block group number + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_block_group(struct ext2_data *fs, uint32_t group); + +/* + * @brief Fetch one block of inode table into internal buffer + * + * If the block of inode table was already fetched then this function does nothing and returns + * with success. + * + * @param bg Block group structure + * @param block Number of inode table block to fetch (relative to start of the inode table) + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_bg_itable(struct ext2_bgroup *bg, uint32_t block); + +/** + * @brief Fetch inode bitmap into internal buffer + * + * If the bitmap was already fetched then this function has no effect. + * + * @param bg Block group structure + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_bg_ibitmap(struct ext2_bgroup *bg); + +/** + * @brief Fetch block bitmap into internal buffer + * + * If the bitmap was already fetched then this function has no effect. + * + * @param bg Block group structure + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_fetch_bg_bbitmap(struct ext2_bgroup *bg); + +/** + * @brief Clear the entry in the inode table of given inode + * + * This function triggers fetching of block group and inode table (where the + * inode is described). + * + * @param fs File system data + * @param inode Inode number of inode to clear + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_clear_inode(struct ext2_data *fs, uint32_t ino); + +/** + * @brief Commit changes made to inode structure + * + * The changes are committed only to the cached block. They are truly written to + * storage when sync is performed. + * + * @param inode Inode structure + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_commit_inode(struct ext2_inode *inode); + +/** + * @brief Commit changes made to inode block + * + * The changes are committed only to the cached block. They are truly written to + * storage when sync is performed. + * + * @param inode Inode structure + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_commit_inode_block(struct ext2_inode *inode); + +/** + * @brief Commit changes made to superblock structure. + * + * The changes made to program structure are copied to disk representation and written to the + * backing storage. + * + * @param fs File system data struct + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_commit_superblock(struct ext2_data *fs); + +/** + * @brief Commit changes made to block group structure. + * + * The changes made to program structure are copied to disk representation and written to the + * backing storage. + * + * @param fs File system data struct + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_commit_bg(struct ext2_data *fs); + +/* Operations that reserve or free the block or inode in the file system. They + * mark an inode or block as used in the bitmap and change free inode/block + * count in superblock and block group. + */ + +/** + * @brief Reserve a block for future use. + * + * Search for a free block. If block is found, proper fields in superblock and + * block group are updated and block is marked as used in block bitmap. + * + * @param fs File system data + * + * @retval >0 number of allocated block + * @retval <0 error + */ +int64_t ext2_alloc_block(struct ext2_data *fs); + +/** + * @brief Reserve an inode for future use. + * + * Search for free inode. If inode is found, proper fields in superblock and + * block group are updated and inode is marked as used in inode bitmap. + * + * @param fs File system data + * + * @retval >0 number of allocated inode + * @retval <0 error + */ +int32_t ext2_alloc_inode(struct ext2_data *fs); + +/** + * @brief Free the block + * + * @param fs File system data + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_free_block(struct ext2_data *fs, uint32_t block); + +/** + * @brief Free the inode + * + * @param fs File system data + * @param inode Inode number + * @param directory True if freed inode was a directory + * + * @retval 0 on success + * @retval <0 error + */ +int ext2_free_inode(struct ext2_data *fs, uint32_t ino, bool directory); + +/** + * @brief Allocate directory entry filled with data from disk directory entry. + * + * NOTE: This function never fails. + * + * Returns structure allocated on direntry_heap. + */ +struct ext2_direntry *ext2_fetch_direntry(struct ext2_disk_direntry *disk_de); + +/** + * @brief Write the data from program directory entry to disk structure. + */ +void ext2_write_direntry(struct ext2_disk_direntry *disk_de, struct ext2_direntry *de); + +uint32_t ext2_get_disk_direntry_inode(struct ext2_disk_direntry *de); +uint32_t ext2_get_disk_direntry_reclen(struct ext2_disk_direntry *de); +uint8_t ext2_get_disk_direntry_namelen(struct ext2_disk_direntry *de); +uint8_t ext2_get_disk_direntry_type(struct ext2_disk_direntry *de); + +void ext2_set_disk_direntry_inode(struct ext2_disk_direntry *de, uint32_t inode); +void ext2_set_disk_direntry_reclen(struct ext2_disk_direntry *de, uint16_t reclen); +void ext2_set_disk_direntry_namelen(struct ext2_disk_direntry *de, uint8_t namelen); +void ext2_set_disk_direntry_type(struct ext2_disk_direntry *de, uint8_t type); +void ext2_set_disk_direntry_name(struct ext2_disk_direntry *de, const char *name, size_t len); + +#endif /* __EXT2_DISKOPS_H__ */ diff --git a/subsys/fs/ext2/ext2_format.c b/subsys/fs/ext2/ext2_format.c new file mode 100644 index 0000000000000..6e35555e53f21 --- /dev/null +++ b/subsys/fs/ext2/ext2_format.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "ext2.h" +#include "ext2_impl.h" +#include "ext2_struct.h" +#include "ext2_diskops.h" + +LOG_MODULE_DECLARE(ext2, LOG_LEVEL_DBG); + +FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default); + +static void validate_config(struct ext2_cfg *cfg) +{ + if (cfg->block_size == 0) { + cfg->block_size = ext2_default.block_size; + } + + if (cfg->bytes_per_inode == 0) { + cfg->bytes_per_inode = ext2_default.bytes_per_inode; + } + + if (cfg->volume_name[0] == '\0') { + strcpy(cfg->volume_name, "ext2"); + } + + if (!cfg->set_uuid) { + /* Generate random UUID */ + sys_rand_get(cfg->uuid, 16); + + /* Set version of UUID (ver. 4 variant 1) */ + cfg->uuid[6] = (cfg->uuid[6] & 0x0f) | 0x40; + cfg->uuid[8] = (cfg->uuid[8] & 0x3f) | 0x80; + } +} + +static void set_bitmap_padding(uint8_t *bitmap, uint32_t nelems, struct ext2_cfg *cfg) +{ + uint32_t used_bytes = nelems / 8 + (nelems % 8 != 0); + + LOG_DBG("Set bitmap padding: %d bytes", nelems); + memset(bitmap, 0x00, used_bytes); + + /* Set padding in block-bitmap block */ + if (nelems % 8) { + bitmap[used_bytes - 1] = (0xff << (nelems % 8)) & 0xff; + LOG_DBG("last byte: %02x", (0xff << (nelems % 8)) & 0xff); + } + memset(bitmap + used_bytes, 0xff, cfg->block_size - used_bytes); +} + +static void set_bitmap_bits(uint8_t *bitmap, uint32_t to_set) +{ + int i = 0, bits; + uint16_t set_value; + + while (to_set > 0) { + bits = MIN(8, to_set); + set_value = (1 << bits) - 1; + bitmap[i] = (uint8_t)set_value; + to_set -= bits; + i++; + } +} + +static void default_directory_inode(struct ext2_disk_inode *in, uint32_t nblocks, + struct ext2_cfg *cfg) +{ + LOG_DBG("Set directory inode: %p", in); + in->i_mode = sys_cpu_to_le16(EXT2_DEF_DIR_MODE); + in->i_uid = 0; + in->i_size = sys_cpu_to_le32(nblocks * cfg->block_size); + in->i_atime = 0; + in->i_ctime = 0; + in->i_mtime = 0; + in->i_dtime = 0; + in->i_gid = 0; + in->i_blocks = sys_cpu_to_le32(nblocks * cfg->block_size / 512); + in->i_flags = 0; + in->i_osd1 = 0; + in->i_generation = 0; + in->i_file_acl = 0; + in->i_dir_acl = 0; + in->i_faddr = 0; + memset(in->i_block, 0, EXT2_INODE_BLOCKS * sizeof(uint32_t)); +} + +int ext2_format(struct ext2_data *fs, struct ext2_cfg *cfg) +{ + int rc, ret = 0; + + validate_config(cfg); + LOG_INF("[Config] blk_sz:%d fs_sz:%d ino_bytes:%d uuid:'%s' vol:'%s'", + cfg->block_size, cfg->fs_size, cfg->bytes_per_inode, cfg->uuid, + cfg->volume_name); + + uint32_t fs_memory = cfg->fs_size ? MIN(cfg->fs_size, fs->device_size) : fs->device_size; + + /* Calculate value that will be stored in superblock field 's_log_block_size'. That value + * tells how much we have to shift 1024 to obtain block size. + * To obtain it we calculate: log(block_size) - 11 + */ + uint8_t block_log_size = find_msb_set(cfg->block_size) - 11; + + LOG_INF("[Memory] available:%lld requested:%d", fs->device_size, fs_memory); + + if (fs_memory > fs->device_size) { + LOG_ERR("No enough space on storage device"); + return -ENOSPC; + } + + + uint32_t blocks_count = fs_memory / cfg->block_size; + uint32_t blocks_per_group = cfg->block_size * 8; + uint32_t inodes_per_block = cfg->block_size / sizeof(struct ext2_disk_inode); + uint32_t mem_per_inode = cfg->bytes_per_inode + sizeof(struct ext2_disk_inode); + + /* 24 block should be enough to fit minimal file system. */ + if (blocks_count < 24) { + LOG_ERR("Storage device too small to fit ext2 file system"); + return -ENOSPC; + } + + uint32_t sb_offset; + uint32_t first_data_block; + uint32_t occupied_blocks; + + if (cfg->block_size == 1024) { + /* Superblock is stored in 1st block */ + sb_offset = 0; + first_data_block = 1; + occupied_blocks = 2; + } else { + /* Superblock is stored in 0th block */ + sb_offset = 1024; + first_data_block = 0; + occupied_blocks = 1; + } + + /* Reserve blocks for block group and bitmaps. */ + uint32_t bg_block_num = occupied_blocks++; + uint32_t bbitmap_block_num = occupied_blocks++; + uint32_t ibitmap_block_num = occupied_blocks++; + + /* We want to have only 1 block group (that starts with first data block) */ + if (blocks_count > blocks_per_group + first_data_block) { + LOG_ERR("File systems with more than 1 block group are not supported."); + return -ENOTSUP; + } + + uint32_t mem_for_inodes = fs_memory - occupied_blocks * cfg->block_size; + uint32_t inodes_count = mem_for_inodes / mem_per_inode; + + /* Align indes_count to use last block of inode table entirely. */ + if (inodes_count % inodes_per_block) { + inodes_count += inodes_per_block - (inodes_count % inodes_per_block); + } + + uint32_t itable_blocks = inodes_count / inodes_per_block; + uint32_t used_inodes = EXT2_RESERVED_INODES; + uint32_t lost_found_inode = 1 + used_inodes++; /* We count inodes from 1. */ + + /* First unoccupied block will be the start of inode table. */ + uint32_t itable_block_num = occupied_blocks; + + occupied_blocks += itable_blocks; + + /* Two next block after inode table will be the blocks for '/' and 'lost+found' dirs */ + uint32_t root_dir_blk_num = occupied_blocks++; + uint32_t lost_found_dir_blk_num = occupied_blocks++; + + LOG_INF("root: %d l+f: %d", root_dir_blk_num, lost_found_dir_blk_num); + + /* All blocks available for writes after creating file system. */ + uint32_t free_blocks = blocks_count - occupied_blocks; + + /* Blocks that will be described in bitmaps. */ + uint32_t used_blocks = occupied_blocks - first_data_block; + + LOG_INF("[Blocks] total:%d per_grp:%d occupied:%d used:%d", + blocks_count, blocks_per_group, occupied_blocks, used_blocks); + LOG_INF("[Inodes] total:%d used:%d itable_blocks:%d", + inodes_count, used_inodes, itable_blocks); + + struct ext2_block *sb_block = ext2_get_block(fs, first_data_block); + struct ext2_block *bg_block = ext2_get_block(fs, bg_block_num); + struct ext2_block *bbitmap_block = ext2_get_block(fs, bbitmap_block_num); + struct ext2_block *ibitmap_block = ext2_get_block(fs, ibitmap_block_num); + struct ext2_block *itable_block1, *itable_block2, *root_dir_blk, *lost_found_dir_blk; + + itable_block1 = itable_block2 = root_dir_blk = lost_found_dir_blk = NULL; + + if (ibitmap_block == NULL || bbitmap_block == NULL || + bg_block == NULL || sb_block == NULL) { + ret = -ENOMEM; + goto out; + } + + struct ext2_disk_superblock *sb = + (struct ext2_disk_superblock *)((uint8_t *)sb_block->data + sb_offset); + + memset(sb, 0, 1024); + sb->s_inodes_count = sys_cpu_to_le32(inodes_count); + sb->s_blocks_count = sys_cpu_to_le32(blocks_count); + sb->s_r_blocks_count = sys_cpu_to_le32(0); + sb->s_free_blocks_count = sys_cpu_to_le32(free_blocks); + sb->s_free_inodes_count = sys_cpu_to_le32(inodes_count - used_inodes); + sb->s_first_data_block = sys_cpu_to_le32(first_data_block); + sb->s_log_block_size = sys_cpu_to_le32(block_log_size); + sb->s_log_frag_size = sys_cpu_to_le32(block_log_size); + sb->s_blocks_per_group = sys_cpu_to_le32(cfg->block_size * 8); + sb->s_frags_per_group = sys_cpu_to_le32(cfg->block_size * 8); + sb->s_inodes_per_group = sys_cpu_to_le32(inodes_count); + sb->s_mtime = sys_cpu_to_le32(0); + sb->s_wtime = sys_cpu_to_le32(0); + sb->s_mnt_count = sys_cpu_to_le32(0); + sb->s_max_mnt_count = sys_cpu_to_le32(-1); + sb->s_magic = sys_cpu_to_le32(0xEF53); + sb->s_state = sys_cpu_to_le32(EXT2_VALID_FS); + sb->s_errors = sys_cpu_to_le32(EXT2_ERRORS_RO); + sb->s_minor_rev_level = sys_cpu_to_le32(0); + sb->s_lastcheck = sys_cpu_to_le32(0); + sb->s_checkinterval = sys_cpu_to_le32(0); + sb->s_creator_os = sys_cpu_to_le32(5); /* Unknown OS */ + sb->s_rev_level = sys_cpu_to_le32(EXT2_DYNAMIC_REV); + sb->s_def_resuid = sys_cpu_to_le32(0); + sb->s_def_resgid = sys_cpu_to_le32(0); + sb->s_first_ino = sys_cpu_to_le32(11); + sb->s_inode_size = sys_cpu_to_le32(sizeof(struct ext2_disk_inode)); + sb->s_block_group_nr = sys_cpu_to_le32(0); + sb->s_feature_compat = sys_cpu_to_le32(0); + sb->s_feature_incompat = sys_cpu_to_le32(EXT2_FEATURE_INCOMPAT_FILETYPE); + sb->s_feature_ro_compat = sys_cpu_to_le32(0); + sb->s_algo_bitmap = sys_cpu_to_le32(0); + sb->s_prealloc_blocks = sys_cpu_to_le32(0); + sb->s_prealloc_dir_blocks = sys_cpu_to_le32(0); + sb->s_journal_inum = sys_cpu_to_le32(0); + sb->s_journal_dev = sys_cpu_to_le32(0); + sb->s_last_orphan = sys_cpu_to_le32(0); + + memcpy(sb->s_uuid, cfg->uuid, 16); + strcpy(sb->s_volume_name, cfg->volume_name); + + if (ext2_write_block(fs, sb_block) < 0) { + ret = -EIO; + goto out; + } + + /* Block descriptor table */ + + struct ext2_disk_bgroup *bg = (struct ext2_disk_bgroup *)bg_block->data; + + memset(bg, 0, cfg->block_size); + bg->bg_block_bitmap = sys_cpu_to_le32(bbitmap_block_num); + bg->bg_inode_bitmap = sys_cpu_to_le32(ibitmap_block_num); + bg->bg_inode_table = sys_cpu_to_le32(itable_block_num); + bg->bg_free_blocks_count = sys_cpu_to_le16(free_blocks); + bg->bg_free_inodes_count = sys_cpu_to_le16(inodes_count - used_inodes); + bg->bg_used_dirs_count = sys_cpu_to_le16(2); /* '/' and 'lost+found' */ + + if (ext2_write_block(fs, bg_block) < 0) { + ret = -EIO; + goto out; + } + + /* Block bitmap */ + uint8_t *bbitmap = bbitmap_block->data; + + /* In bitmap we describe blocks starting from s_first_data_block. */ + set_bitmap_padding(bbitmap, blocks_count - sb->s_first_data_block, cfg); + set_bitmap_bits(bbitmap, used_blocks); + if (ext2_write_block(fs, bbitmap_block) < 0) { + ret = -EIO; + goto out; + } + + /* Inode bitmap */ + uint8_t *ibitmap = ibitmap_block->data; + + set_bitmap_padding(ibitmap, inodes_count, cfg); + set_bitmap_bits(ibitmap, used_inodes); + if (ext2_write_block(fs, ibitmap_block) < 0) { + ret = -EIO; + goto out; + } + + /* Inode table */ + /* Zero inode table */ + for (int i = 0; i < itable_blocks; i++) { + struct ext2_block *blk = ext2_get_block(fs, itable_block_num + i); + + memset(blk->data, 0, cfg->block_size); + rc = ext2_write_block(fs, blk); + ext2_drop_block(blk); + if (rc < 0) { + ret = -EIO; + goto out; + } + } + + struct ext2_disk_inode *in; + int inode_offset; + + /* Set inode 2 ('/' directory) */ + itable_block1 = ext2_get_block(fs, itable_block_num); + in = (struct ext2_disk_inode *)itable_block1->data; + inode_offset = EXT2_ROOT_INODE - 1; + default_directory_inode(&in[inode_offset], 1, cfg); + + in[inode_offset].i_links_count = sys_cpu_to_le16(3); /* 2 from itself, 1 from child */ + in[inode_offset].i_block[0] = sys_cpu_to_le32(root_dir_blk_num); + if (ext2_write_block(fs, itable_block1) < 0) { + ret = -EIO; + goto out; + } + + /* Set inode for 'lost+found' directory */ + inode_offset = (lost_found_inode - 1) % inodes_per_block; /* We count inodes from 1 */ + + LOG_DBG("Inode offset: %d", inode_offset); + + if (inodes_per_block < lost_found_inode) { + /* We need to fetch new inode table block */ + uint32_t block_num = itable_block_num + lost_found_inode / inodes_per_block; + + itable_block2 = ext2_get_block(fs, block_num); + in = (struct ext2_disk_inode *)itable_block2->data; + } + + default_directory_inode(&in[inode_offset], 1, cfg); + in[inode_offset].i_links_count = sys_cpu_to_le16(2); /* 1 from itself, 1 from parent */ + in[inode_offset].i_block[0] = sys_cpu_to_le32(lost_found_dir_blk_num); + if (itable_block2) { + if (ext2_write_block(fs, itable_block2) < 0) { + ret = -EIO; + goto out; + } + } + + struct ext2_disk_direntry *disk_de; + struct ext2_direntry *de; + uint32_t de_offset; + + /* Contents of '/' directory */ + LOG_DBG("Root dir blk: %d", root_dir_blk_num); + root_dir_blk = ext2_get_block(fs, root_dir_blk_num); + if (root_dir_blk == NULL) { + ret = ENOMEM; + goto out; + } + memset(root_dir_blk->data, 0, cfg->block_size); + + de_offset = 0; + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset); + de = ext2_create_direntry(".", 1, EXT2_ROOT_INODE, EXT2_FT_DIR); + ext2_write_direntry(disk_de, de); + + de_offset += de->de_rec_len; + k_heap_free(&direntry_heap, de); + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset); + de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR); + ext2_write_direntry(disk_de, de); + + de_offset += de->de_rec_len; + k_heap_free(&direntry_heap, de); + + char *name = "lost+found"; + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(root_dir_blk->data, de_offset); + de = ext2_create_direntry(name, strlen(name), lost_found_inode, EXT2_FT_DIR); + de_offset += de->de_rec_len; + + /* This was the last entry so add padding until end of block */ + de->de_rec_len += cfg->block_size - de_offset; + + ext2_write_direntry(disk_de, de); + k_heap_free(&direntry_heap, de); + + if (ext2_write_block(fs, root_dir_blk) < 0) { + ret = -EIO; + goto out; + } + + /* Contents of 'lost+found' directory */ + LOG_DBG("Lost found dir blk: %d", lost_found_dir_blk_num); + lost_found_dir_blk = ext2_get_block(fs, lost_found_dir_blk_num); + if (lost_found_dir_blk == NULL) { + ret = ENOMEM; + goto out; + } + memset(lost_found_dir_blk->data, 0, cfg->block_size); + + de_offset = 0; + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset); + de = ext2_create_direntry(".", 1, lost_found_inode, EXT2_FT_DIR); + ext2_write_direntry(disk_de, de); + + de_offset += de->de_rec_len; + k_heap_free(&direntry_heap, de); + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(lost_found_dir_blk->data, de_offset); + de = ext2_create_direntry("..", 2, EXT2_ROOT_INODE, EXT2_FT_DIR); + de_offset += de->de_rec_len; + + /* This was the last entry so add padding until end of block */ + de->de_rec_len += cfg->block_size - de_offset; + + ext2_write_direntry(disk_de, de); + k_heap_free(&direntry_heap, de); + + if (ext2_write_block(fs, lost_found_dir_blk) < 0) { + ret = -EIO; + goto out; + } +out: + ext2_drop_block(sb_block); + ext2_drop_block(bg_block); + ext2_drop_block(bbitmap_block); + ext2_drop_block(ibitmap_block); + ext2_drop_block(itable_block1); + ext2_drop_block(itable_block2); + ext2_drop_block(root_dir_blk); + ext2_drop_block(lost_found_dir_blk); + if ((ret >= 0) && (fs->backend_ops->sync(fs)) < 0) { + ret = -EIO; + } + return ret; +} diff --git a/subsys/fs/ext2/ext2_impl.c b/subsys/fs/ext2/ext2_impl.c new file mode 100644 index 0000000000000..3a5382aeee5c6 --- /dev/null +++ b/subsys/fs/ext2/ext2_impl.c @@ -0,0 +1,1545 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "ext2.h" +#include "ext2_impl.h" +#include "ext2_struct.h" +#include "ext2_diskops.h" +#include "ext2_bitmap.h" + +LOG_MODULE_REGISTER(ext2, CONFIG_EXT2_LOG_LEVEL); + +static struct ext2_data __fs; +static bool initialized; + +#define BLOCK_MEMORY_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * CONFIG_EXT2_MAX_BLOCK_SIZE) +#define BLOCK_STRUCT_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * sizeof(struct ext2_block)) + +/* Structures for blocks slab alocator */ +struct k_mem_slab ext2_block_memory_slab, ext2_block_struct_slab; +char __aligned(sizeof(void *)) __ext2_block_memory_buffer[BLOCK_MEMORY_BUFFER_SIZE]; +char __aligned(sizeof(void *)) __ext2_block_struct_buffer[BLOCK_STRUCT_BUFFER_SIZE]; + +/* Initialize heap memory allocator */ +K_HEAP_DEFINE(direntry_heap, MAX_DIRENTRY_SIZE); +K_MEM_SLAB_DEFINE(inode_struct_slab, sizeof(struct ext2_inode), MAX_INODES, sizeof(void *)); + +/* Helper functions --------------------------------------------------------- */ + +void error_behavior(struct ext2_data *fs, const char *msg) +{ + LOG_ERR("File system corrupted: %s", msg); + + /* If file system is not initialized panic */ + if (!initialized) { + LOG_ERR("File system data not found. Panic..."); + k_panic(); + } + + switch (fs->sblock.s_errors) { + case EXT2_ERRORS_CONTINUE: + /* Do nothing */ + break; + case EXT2_ERRORS_RO: + LOG_WRN("Marking file system as read only"); + fs->flags |= EXT2_DATA_FLAGS_RO; + break; + case EXT2_ERRORS_PANIC: + LOG_ERR("Panic..."); + k_panic(); + break; + default: + LOG_ERR("Unrecognized errors behavior in superblock s_errors field. Panic..."); + k_panic(); + } +} + +/* Block operations --------------------------------------------------------- */ + +static struct ext2_block *get_block_struct(void) +{ + int ret; + struct ext2_block *b; + + ret = k_mem_slab_alloc(&ext2_block_struct_slab, (void **)&b, K_NO_WAIT); + if (ret < 0) { + LOG_ERR("get block: alloc block struct error %d", ret); + return NULL; + } + + ret = k_mem_slab_alloc(&ext2_block_memory_slab, (void **)&b->data, K_NO_WAIT); + if (ret < 0) { + LOG_ERR("get block: alloc block memory error %d", ret); + k_mem_slab_free(&ext2_block_struct_slab, (void **)&b); + return NULL; + } + return b; +} + +struct ext2_block *ext2_get_block(struct ext2_data *fs, uint32_t block) +{ + int ret; + struct ext2_block *b = get_block_struct(); + + if (!b) { + return NULL; + } + b->num = block; + b->flags = EXT2_BLOCK_ASSIGNED; + ret = fs->backend_ops->read_block(fs, b->data, block); + if (ret < 0) { + LOG_ERR("get block: read block error %d", ret); + ext2_drop_block(b); + return NULL; + } + return b; +} + +struct ext2_block *ext2_get_empty_block(struct ext2_data *fs) +{ + struct ext2_block *b = get_block_struct(); + + if (!b) { + return NULL; + } + b->num = 0; + b->flags = 0; + memset(b->data, 0, fs->block_size); + return b; +} + +int ext2_write_block(struct ext2_data *fs, struct ext2_block *b) +{ + int ret; + + if (!(b->flags & EXT2_BLOCK_ASSIGNED)) { + return -EINVAL; + } + + ret = fs->backend_ops->write_block(fs, b->data, b->num); + if (ret < 0) { + return ret; + } + return 0; +} + +void ext2_drop_block(struct ext2_block *b) +{ + if (b == NULL) { + return; + } + + if (b != NULL && b->data != NULL) { + k_mem_slab_free(&ext2_block_memory_slab, (void **)&b->data); + k_mem_slab_free(&ext2_block_struct_slab, (void **)&b); + } +} + +void ext2_init_blocks_slab(struct ext2_data *fs) +{ + memset(__ext2_block_memory_buffer, 0, BLOCK_MEMORY_BUFFER_SIZE); + memset(__ext2_block_struct_buffer, 0, BLOCK_STRUCT_BUFFER_SIZE); + + /* These calls will always succeed because sizes and memory buffers are properly aligned. */ + + k_mem_slab_init(&ext2_block_struct_slab, __ext2_block_struct_buffer, + sizeof(struct ext2_block), CONFIG_EXT2_MAX_BLOCK_COUNT); + + k_mem_slab_init(&ext2_block_memory_slab, __ext2_block_memory_buffer, fs->block_size, + CONFIG_EXT2_MAX_BLOCK_COUNT); +} + +int ext2_assign_block_num(struct ext2_data *fs, struct ext2_block *b) +{ + int64_t new_block; + + if (b->flags & EXT2_BLOCK_ASSIGNED) { + return -EINVAL; + } + + /* Allocate block in the file system. */ + new_block = ext2_alloc_block(fs); + if (new_block < 0) { + return new_block; + } + + b->num = new_block; + b->flags |= EXT2_BLOCK_ASSIGNED; + return 0; +} + + +/* FS operations ------------------------------------------------------------ */ + +int ext2_init_storage(struct ext2_data **fsp, const void *storage_dev, int flags) +{ + if (initialized) { + return -EBUSY; + } + + int ret = 0; + struct ext2_data *fs = &__fs; + int64_t dev_size, write_size; + + *fsp = fs; + fs->open_inodes = 0; + fs->flags = 0; + fs->bgroup.num = -1; + + ret = ext2_init_disk_access_backend(fs, storage_dev, flags); + if (ret < 0) { + return ret; + } + + dev_size = fs->backend_ops->get_device_size(fs); + if (dev_size < 0) { + ret = dev_size; + goto err; + } + + write_size = fs->backend_ops->get_write_size(fs); + if (write_size < 0) { + ret = write_size; + goto err; + } + + if (write_size < 1024 && 1024 % write_size != 0) { + ret = -EINVAL; + LOG_ERR("expecting sector size that divides 1024 (got: %lld)", write_size); + goto err; + } + + LOG_DBG("Device size: %lld", dev_size); + LOG_DBG("Write size: %lld", write_size); + + fs->device_size = dev_size; + fs->write_size = write_size; + + initialized = true; +err: + return ret; +} + +int ext2_verify_disk_superblock(struct ext2_disk_superblock *sb) +{ + /* Check if it is a valid Ext2 file system. */ + if (sys_le16_to_cpu(sb->s_magic) != EXT2_MAGIC_NUMBER) { + LOG_ERR("Wrong file system magic number (%x)", sb->s_magic); + return -EINVAL; + } + + /* For now we don't support file systems with frag size different from block size */ + if (sys_le32_to_cpu(sb->s_log_block_size) != sb->s_log_frag_size) { + LOG_ERR("Filesystem with frag_size != block_size is not supported"); + return -ENOTSUP; + } + + /* Support only second revision */ + if (sys_le32_to_cpu(sb->s_rev_level) != EXT2_DYNAMIC_REV) { + LOG_ERR("Filesystem with revision %d is not supported", sb->s_rev_level); + return -ENOTSUP; + } + + if (sys_le16_to_cpu(sb->s_inode_size) != EXT2_GOOD_OLD_INODE_SIZE) { + LOG_ERR("Filesystem with inode size %d is not supported", sb->s_inode_size); + return -ENOTSUP; + } + + /* Check if file system may contain errors. */ + if (sys_le16_to_cpu(sb->s_state) == EXT2_ERROR_FS) { + LOG_WRN("File system may contain errors."); + switch (sys_le16_to_cpu(sb->s_errors)) { + case EXT2_ERRORS_CONTINUE: + break; + + case EXT2_ERRORS_RO: + LOG_WRN("File system can be mounted read only"); + return -EROFS; + + case EXT2_ERRORS_PANIC: + LOG_ERR("File system can't be mounted. Panic..."); + k_panic(); + default: + LOG_WRN("Unknown option for superblock s_errors field."); + } + } + + if ((sys_le32_to_cpu(sb->s_feature_incompat) & EXT2_FEATURE_INCOMPAT_FILETYPE) == 0) { + LOG_ERR("File system without file type stored in de is not supported"); + return -ENOTSUP; + } + + if ((sys_le32_to_cpu(sb->s_feature_incompat) & ~EXT2_FEATURE_INCOMPAT_SUPPORTED) > 0) { + LOG_ERR("File system can't be mounted. Incompat features %d not supported", + (sb->s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED)); + return -ENOTSUP; + } + + if ((sys_le32_to_cpu(sb->s_feature_ro_compat) & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED) > 0) { + LOG_WRN("File system can be mounted read only. RO features %d detected.", + (sb->s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED)); + return -EROFS; + } + + LOG_DBG("ino_cnt:%d blk_cnt:%d blk_per_grp:%d ino_per_grp:%d free_ino:%d free_blk:%d " + "blk_size:%d ino_size:%d mntc:%d", + sys_le32_to_cpu(sb->s_inodes_count), + sys_le32_to_cpu(sb->s_blocks_count), + sys_le32_to_cpu(sb->s_blocks_per_group), + sys_le32_to_cpu(sb->s_inodes_per_group), + sys_le32_to_cpu(sb->s_free_inodes_count), + sys_le32_to_cpu(sb->s_free_blocks_count), + sys_le32_to_cpu(1024 << sb->s_log_block_size), + sys_le16_to_cpu(sb->s_inode_size), + sys_le16_to_cpu(sb->s_mnt_count)); + return 0; +} + +int ext2_init_fs(struct ext2_data *fs) +{ + int ret = 0; + + /* Fetch superblock */ + ret = ext2_fetch_superblock(fs); + if (ret < 0) { + return ret; + } + + if (!(fs->flags & EXT2_DATA_FLAGS_RO)) { + /* Update sblock fields set during the successful mount. */ + fs->sblock.s_state = EXT2_ERROR_FS; + fs->sblock.s_mnt_count += 1; + ret = ext2_commit_superblock(fs); + if (ret < 0) { + return ret; + } + } + + ret = ext2_fetch_block_group(fs, 0); + if (ret < 0) { + return ret; + } + ret = ext2_fetch_bg_ibitmap(&fs->bgroup); + if (ret < 0) { + return ret; + } + ret = ext2_fetch_bg_bbitmap(&fs->bgroup); + if (ret < 0) { + return ret; + } + + /* Validate superblock */ + uint32_t set; + struct ext2_superblock *sb = &fs->sblock; + uint32_t fs_blocks = sb->s_blocks_count - sb->s_first_data_block; + + set = ext2_bitmap_count_set(BGROUP_BLOCK_BITMAP(&fs->bgroup), fs_blocks); + + if (set != sb->s_blocks_count - sb->s_free_blocks_count - sb->s_first_data_block) { + error_behavior(fs, "Wrong number of used blocks in superblock and bitmap"); + return -EINVAL; + } + + set = ext2_bitmap_count_set(BGROUP_INODE_BITMAP(&fs->bgroup), sb->s_inodes_count); + + if (set != sb->s_inodes_count - sb->s_free_inodes_count) { + error_behavior(fs, "Wrong number of used inodes in superblock and bitmap"); + return -EINVAL; + } + return 0; +} + +int ext2_close_fs(struct ext2_data *fs) +{ + int ret = 0; + + /* Close all open inodes */ + for (int32_t i = 0; i < fs->open_inodes; ++i) { + if (fs->inode_pool[i] != NULL) { + ext2_inode_drop(fs->inode_pool[i]); + } + } + + /* To save file system as correct it must be writable and without errors */ + if (!(fs->flags & (EXT2_DATA_FLAGS_RO | EXT2_DATA_FLAGS_ERR))) { + fs->sblock.s_state = EXT2_VALID_FS; + ret = ext2_commit_superblock(fs); + if (ret < 0) { + return ret; + } + } + + /* free block group if it is fetched */ + ext2_drop_block(fs->bgroup.inode_table); + ext2_drop_block(fs->bgroup.inode_bitmap); + ext2_drop_block(fs->bgroup.block_bitmap); + + if (fs->backend_ops->sync(fs) < 0) { + return -EIO; + } + return 0; +} + +int ext2_close_struct(struct ext2_data *fs) +{ + memset(fs, 0, sizeof(struct ext2_data)); + initialized = false; + return 0; +} + +/* Lookup ------------------------------------------------------------------- */ + +/* Functions needed by lookup inode */ +static const char *skip_slash(const char *str); +static char *strchrnul(const char *str, const char c); +static int64_t find_dir_entry(struct ext2_inode *inode, const char *name, size_t len, + uint32_t *r_offset); + +int ext2_lookup_inode(struct ext2_data *fs, struct ext2_lookup_args *args) +{ + LOG_DBG("Looking for file %s", args->path); + + int rc, ret = 0; + struct ext2_inode *cur_dir = NULL, *next = NULL; + static char name_buf[EXT2_MAX_FILE_NAME + 1]; + + /* Start looking from root directory of file system */ + rc = ext2_inode_get(fs, EXT2_ROOT_INODE, &cur_dir); + if (rc < 0) { + ret = rc; + goto out; + } + + /* There may be slash at the beginning of path */ + const char *path = args->path; + + path = skip_slash(path); + + /* If path is empty then return root directory */ + if (path[0] == '\0') { + args->inode = cur_dir; + cur_dir = NULL; + goto out; + } + + for (;;) { + /* Get path component */ + char *end = strchrnul(path, '/'); + size_t len = end - path; + + if (len > EXT2_MAX_FILE_NAME) { + ret = -ENAMETOOLONG; + goto out; + } + + strncpy(name_buf, path, len); + name_buf[len] = '\0'; + + /* Search in current directory */ + uint32_t dir_off = 0; + /* using 64 bit value to don't lose any information on error */ + int64_t ino = find_dir_entry(cur_dir, name_buf, len, &dir_off); + + const char *next_path = skip_slash(end); + bool last_entry = next_path[0] == '\0'; + + if (!last_entry) { + /* prepare the next loop iteration */ + + if (ino < 0) { + /* next entry not found */ + ret = -ENOENT; + goto out; + } + + rc = ext2_inode_get(fs, ino, &next); + if (rc < 0) { + /* error while fetching next entry */ + ret = rc; + goto out; + } + + if (!(next->i_mode & EXT2_S_IFDIR)) { + /* path component should be directory */ + ret = -ENOTDIR; + goto out; + } + + /* Go to the next path component */ + path = next_path; + + /* Move to next directory */ + ext2_inode_drop(cur_dir); + cur_dir = next; + + next = NULL; + continue; + } + + /* Last entry */ + + if (ino < 0 && !(args->flags & LOOKUP_ARG_CREATE)) { + /* entry not found but we need it */ + ret = -ENOENT; + goto out; + } + + if (ino > 0) { + rc = ext2_inode_get(fs, ino, &next); + if (rc < 0) { + ret = rc; + goto out; + } + } + + /* Store parent directory and offset in parent directory */ + if (args->flags & (LOOKUP_ARG_CREATE | LOOKUP_ARG_STAT | LOOKUP_ARG_UNLINK)) { + /* In create it will be valid only if we have found existing file */ + args->offset = dir_off; + args->parent = cur_dir; + cur_dir = NULL; + } + + /* Store name info */ + if (args->flags & LOOKUP_ARG_CREATE) { + args->name_pos = path - args->path; + args->name_len = len; + } + + /* Store found inode */ + if (ino > 0) { + args->inode = next; + next = NULL; + } + goto out; + } + +out: + /* Always free that inodes. + * If some of them is returned from function then proper pointer is set to NULL. + */ + ext2_inode_drop(cur_dir); + ext2_inode_drop(next); + return ret; +} + +/* Return position of given char or end of string. */ +static char *strchrnul(const char *s, char c) +{ + while ((*s != c) && (*s != '\0')) { + s++; + } + return (char *) s; +} + +static const char *skip_slash(const char *s) +{ + while ((*s == '/') && (*s != '\0')) { + s++; + } + return s; +} + +/** + * @brief Find inode + * + * @note Inodes are 32 bit. When we return signed 64 bit number then we don't + * lose any information. + * + * @param r_offset If not NULL then offset in directory of that entry is written here. + * @return Inode number or negative error code + */ +static int64_t find_dir_entry(struct ext2_inode *inode, const char *name, size_t len, + uint32_t *r_offset) +{ + int rc; + uint32_t block, block_off, offset = 0; + int64_t ino = -1; + struct ext2_data *fs = inode->i_fs; + struct ext2_direntry *de; + + while (offset < inode->i_size) { + block = offset / fs->block_size; + block_off = offset % fs->block_size; + + rc = ext2_fetch_inode_block(inode, block); + if (rc < 0) { + return rc; + } + + struct ext2_disk_direntry *disk_de = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), block_off); + + de = ext2_fetch_direntry(disk_de); + if (de == NULL) { + return -EINVAL; + } + + if (len == de->de_name_len && strncmp(de->de_name, name, len) == 0) { + ino = de->de_inode; + if (r_offset) { + /* Return offset*/ + *r_offset = offset; + } + goto success; + } + /* move to the next directory entry */ + offset += de->de_rec_len; + k_heap_free(&direntry_heap, de); + } + + return -EINVAL; +success: + k_heap_free(&direntry_heap, de); + return (int64_t)ino; +} + +/* Inode operations --------------------------------------------------------- */ + +ssize_t ext2_inode_read(struct ext2_inode *inode, void *buf, uint32_t offset, size_t nbytes) +{ + int rc = 0; + ssize_t read = 0; + uint32_t block_size = inode->i_fs->block_size; + + while (read < nbytes && offset < inode->i_size) { + + uint32_t block = offset / block_size; + uint32_t block_off = offset % block_size; + + rc = ext2_fetch_inode_block(inode, block); + if (rc < 0) { + break; + } + + uint32_t left_on_blk = block_size - block_off; + uint32_t left_in_file = inode->i_size - offset; + size_t to_read = MIN(nbytes, MIN(left_on_blk, left_in_file)); + + memcpy((uint8_t *)buf + read, inode_current_block_mem(inode) + block_off, to_read); + + read += to_read; + offset += to_read; + } + + if (rc < 0) { + return rc; + } + return read; +} + +ssize_t ext2_inode_write(struct ext2_inode *inode, const void *buf, uint32_t offset, size_t nbytes) +{ + int rc = 0; + ssize_t written = 0; + uint32_t block_size = inode->i_fs->block_size; + + while (written < nbytes) { + uint32_t block = offset / block_size; + uint32_t block_off = offset % block_size; + + LOG_DBG("inode:%d Write to block %d (offset: %d-%zd/%d)", + inode->i_id, block, offset, offset + nbytes, inode->i_size); + + rc = ext2_fetch_inode_block(inode, block); + if (rc < 0) { + break; + } + + size_t to_write = MIN(nbytes, block_size - block_off); + + memcpy(inode_current_block_mem(inode) + block_off, (uint8_t *)buf + written, + to_write); + LOG_DBG("Written %zd bytes at offset %d in block i%d", to_write, block_off, block); + + rc = ext2_commit_inode_block(inode); + if (rc < 0) { + break; + } + + written += to_write; + } + + if (rc < 0) { + return rc; + } + + if (offset + written > inode->i_size) { + LOG_DBG("New inode size: %d -> %zd", inode->i_size, offset + written); + inode->i_size = offset + written; + rc = ext2_commit_inode(inode); + if (rc < 0) { + return rc; + } + } + + return written; +} + +int ext2_inode_trunc(struct ext2_inode *inode, off_t length) +{ + if (length > UINT32_MAX) { + return -ENOTSUP; + } + + int rc = 0; + uint32_t new_size = (uint32_t)length; + uint32_t old_size = inode->i_size; + const uint32_t block_size = inode->i_fs->block_size; + + LOG_DBG("Resizing inode from %d to %d", old_size, new_size); + + if (old_size == new_size) { + return 0; + } + + uint32_t used_blocks = new_size / block_size + (new_size % block_size != 0); + + if (new_size > old_size) { + if (old_size % block_size != 0) { + /* file ends inside some block */ + + LOG_DBG("Has to insert zeros to the end of block"); + + /* insert zeros to the end of last block */ + uint32_t old_block = old_size / block_size; + uint32_t start_off = old_size % block_size; + uint32_t to_write = MIN(new_size - old_size, block_size - start_off); + + rc = ext2_fetch_inode_block(inode, old_block); + if (rc < 0) { + return rc; + } + + memset(inode_current_block_mem(inode) + start_off, 0, to_write); + rc = ext2_commit_inode_block(inode); + if (rc < 0) { + return rc; + } + } + + /* There is no need to zero rest of blocks because they will be automatically + * treated as zero filled. + */ + + } else { + /* First removed block is just the number of used blocks. + * (We count blocks from zero hence its number is just number of used blocks.) + */ + uint32_t start_blk = used_blocks; + int64_t removed_blocks; + + LOG_DBG("Inode trunc from blk: %d", start_blk); + + /* Remove blocks starting with start_blk. */ + removed_blocks = ext2_inode_remove_blocks(inode, start_blk); + if (removed_blocks < 0) { + return removed_blocks; + } + + LOG_DBG("Removed blocks: %lld (%lld)", + removed_blocks, removed_blocks * (block_size / 512)); + inode->i_blocks -= removed_blocks * (block_size / 512); + } + + inode->i_size = new_size; + + LOG_DBG("New inode size: %d (blocks: %d)", inode->i_size, inode->i_blocks); + + rc = ext2_commit_inode(inode); + return rc; +} + +static int write_one_block(struct ext2_data *fs, struct ext2_block *b) +{ + int ret = 0; + + if (!(b->flags & EXT2_BLOCK_ASSIGNED)) { + ret = ext2_assign_block_num(fs, b); + if (ret < 0) { + return ret; + } + } + + ret = ext2_write_block(fs, b); + return ret; +} + +int ext2_inode_sync(struct ext2_inode *inode) +{ + int ret; + struct ext2_data *fs = inode->i_fs; + + for (int i = 0; i < 4; ++i) { + if (inode->blocks[i] == NULL) { + break; + } + ret = write_one_block(fs, inode->blocks[i]); + if (ret < 0) { + return ret; + } + ret = fs->backend_ops->sync(fs); + if (ret < 0) { + return ret; + } + } + return 0; +} + +int ext2_get_direntry(struct ext2_file *dir, struct fs_dirent *ent) +{ + if (dir->f_off >= dir->f_inode->i_size) { + /* end of directory */ + ent->name[0] = 0; + return 0; + } + + struct ext2_data *fs = dir->f_inode->i_fs; + + int rc, ret = 0; + uint32_t block = dir->f_off / fs->block_size; + uint32_t block_off = dir->f_off % fs->block_size; + uint32_t len; + + LOG_DBG("Reading dir entry from block %d at offset %d", block, block_off); + + rc = ext2_fetch_inode_block(dir->f_inode, block); + if (rc < 0) { + return rc; + } + + struct ext2_inode *inode = NULL; + struct ext2_disk_direntry *disk_de = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir->f_inode), block_off); + struct ext2_direntry *de = ext2_fetch_direntry(disk_de); + + LOG_DBG("inode=%d name_len=%d rec_len=%d", de->de_inode, de->de_name_len, de->de_rec_len); + + if (de == NULL) { + LOG_ERR("Read directory entry name too long"); + return -EINVAL; + } + + len = de->de_name_len; + if (de->de_name_len > MAX_FILE_NAME) { + LOG_WRN("Directory name won't fit in direntry"); + len = MAX_FILE_NAME; + } + memcpy(ent->name, de->de_name, len); + ent->name[len] = '\0'; + + LOG_DBG("name_len=%d name=%s %d", de->de_name_len, ent->name, EXT2_MAX_FILE_NAME); + + /* Get type of directory entry */ + ent->type = de->de_file_type & EXT2_FT_DIR ? FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE; + + /* Get size only for files. Directories have size 0. */ + size_t size = 0; + + if (ent->type == FS_DIR_ENTRY_FILE) { + rc = ext2_inode_get(fs, de->de_inode, &inode); + if (rc < 0) { + ret = rc; + goto out; + } + size = inode->i_size; + } + + ent->size = size; + + /* Update offset to point to next directory entry */ + dir->f_off += de->de_rec_len; + +out: + k_heap_free(&direntry_heap, de); + ext2_inode_drop(inode); + return ret; +} + +/* Create files and directories */ + +/* Allocate inode number and fill inode table with default values. */ +static int ext2_create_inode(struct ext2_data *fs, struct ext2_inode *parent, + struct ext2_inode *inode, int type) +{ + int rc; + int32_t ino = ext2_alloc_inode(fs); + + if (ino < 0) { + return ino; + } + + /* fill inode with correct data */ + inode->i_fs = fs; + inode->flags = 0; + inode->i_id = ino; + inode->i_size = 0; + inode->i_mode = type == FS_DIR_ENTRY_FILE ? EXT2_DEF_FILE_MODE : EXT2_DEF_DIR_MODE; + inode->i_links_count = 0; + memset(inode->i_block, 0, 15 * 4); + + if (type == FS_DIR_ENTRY_DIR) { + /* Block group current block is already fetched. We don't have to do it again. + * (It was done above in ext2_alloc_inode function.) + */ + fs->bgroup.bg_used_dirs_count += 1; + rc = ext2_commit_bg(fs); + if (rc < 0) { + return rc; + } + } + + rc = ext2_commit_inode(inode); + return rc; +} + +struct ext2_direntry *ext2_create_direntry(const char *name, uint8_t namelen, uint32_t ino, + uint8_t filetype) +{ + __ASSERT(namelen <= EXT2_MAX_FILE_NAME, "Name length to long"); + + uint32_t prog_rec_len = sizeof(struct ext2_direntry) + namelen; + struct ext2_direntry *de = k_heap_alloc(&direntry_heap, prog_rec_len, K_FOREVER); + + /* Size of future disk structure. */ + uint32_t reclen = sizeof(struct ext2_disk_direntry) + namelen; + + /* Align reclen to 4 bytes. */ + reclen = ROUND_UP(reclen, 4); + + de->de_inode = ino; + de->de_rec_len = reclen; + de->de_name_len = (uint8_t)namelen; + de->de_file_type = filetype; + memcpy(de->de_name, name, namelen); + + LOG_DBG("Initialized directory entry %p{%s(%d) %d %d %c}", + de, de->de_name, de->de_name_len, de->de_inode, de->de_rec_len, + de->de_file_type == EXT2_FT_DIR ? 'd' : 'f'); + return de; +} + +static int ext2_add_direntry(struct ext2_inode *dir, struct ext2_direntry *entry) +{ + LOG_DBG("Adding entry: {in=%d type=%d name_len=%d} to directory (in=%d)", + entry->de_inode, entry->de_file_type, entry->de_name_len, dir->i_id); + + int rc = 0; + uint32_t block_size = dir->i_fs->block_size; + uint32_t entry_size = sizeof(struct ext2_disk_direntry) + entry->de_name_len; + + if (entry_size > block_size) { + return -EINVAL; + } + + /* Find last entry */ + /* get last block and start from first entry on that block */ + int last_blk = (dir->i_size / block_size) - 1; + + rc = ext2_fetch_inode_block(dir, last_blk); + if (rc < 0) { + return rc; + } + + uint32_t offset = 0; + uint16_t reclen; + + struct ext2_disk_direntry *de = 0; + + /* loop must be executed at least once, because block_size > 0 */ + while (offset < block_size) { + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir), offset); + reclen = ext2_get_disk_direntry_reclen(de); + if (offset + reclen == block_size) { + break; + } + offset += reclen; + } + + + uint32_t occupied = sizeof(struct ext2_disk_direntry) + ext2_get_disk_direntry_namelen(de); + + /* Align to 4 bytes */ + occupied = ROUND_UP(occupied, 4); + + LOG_DBG("Occupied: %d total: %d needed: %d", occupied, reclen, entry_size); + + if (reclen - occupied >= entry_size) { + /* Entry fits into current block */ + offset += occupied; + entry->de_rec_len = block_size - offset; + ext2_set_disk_direntry_reclen(de, occupied); + } else { + LOG_DBG("Allocating new block for directory"); + + /* Have to allocate new block */ + rc = ext2_fetch_inode_block(dir, last_blk + 1); + if (rc < 0) { + return rc; + } + + /* Increase size of directory */ + dir->i_size += block_size; + rc = ext2_commit_inode(dir); + if (rc < 0) { + return rc; + } + rc = ext2_commit_inode_block(dir); + if (rc < 0) { + return rc; + } + + /* New entry will start at offset 0 */ + offset = 0; + entry->de_rec_len = block_size; + } + + LOG_DBG("Writing entry {in=%d type=%d rec_len=%d name_len=%d} to block %d of inode %d", + entry->de_inode, entry->de_file_type, entry->de_rec_len, entry->de_name_len, + inode_current_block(dir)->num, dir->i_id); + + + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(dir), offset); + ext2_write_direntry(de, entry); + + rc = ext2_commit_inode_block(dir); + return rc; +} + +int ext2_create_file(struct ext2_inode *parent, struct ext2_inode *new_inode, + struct ext2_lookup_args *args) +{ + int rc, ret = 0; + struct ext2_direntry *entry; + struct ext2_data *fs = parent->i_fs; + + rc = ext2_create_inode(fs, args->inode, new_inode, FS_DIR_ENTRY_FILE); + if (rc < 0) { + return rc; + } + + entry = ext2_create_direntry(args->path + args->name_pos, args->name_len, new_inode->i_id, + EXT2_FT_REG_FILE); + + rc = ext2_add_direntry(parent, entry); + if (rc < 0) { + ret = rc; + goto out; + } + + /* Successfully added to directory */ + new_inode->i_links_count += 1; + + rc = ext2_commit_inode(new_inode); + if (rc < 0) { + ret = rc; + } +out: + k_heap_free(&direntry_heap, entry); + return ret; +} + +int ext2_create_dir(struct ext2_inode *parent, struct ext2_inode *new_inode, + struct ext2_lookup_args *args) +{ + int rc, ret = 0; + struct ext2_direntry *entry; + struct ext2_disk_direntry *disk_de; + struct ext2_data *fs = parent->i_fs; + uint32_t block_size = parent->i_fs->block_size; + + rc = ext2_create_inode(fs, args->inode, new_inode, FS_DIR_ENTRY_DIR); + if (rc < 0) { + return rc; + } + + /* Directory must have at least one block */ + new_inode->i_size = block_size; + + entry = ext2_create_direntry(args->path + args->name_pos, args->name_len, new_inode->i_id, + EXT2_FT_DIR); + + rc = ext2_add_direntry(parent, entry); + if (rc < 0) { + ret = rc; + goto out; + } + + /* Successfully added to directory */ + new_inode->i_links_count += 1; + + k_heap_free(&direntry_heap, entry); + + /* Create "." directory entry */ + entry = ext2_create_direntry(".", 1, new_inode->i_id, EXT2_FT_DIR); + entry->de_rec_len = block_size; + + /* It has to be inserted manually */ + rc = ext2_fetch_inode_block(new_inode, 0); + if (rc < 0) { + ret = rc; + goto out; + } + + disk_de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(new_inode), 0); + ext2_write_direntry(disk_de, entry); + + new_inode->i_links_count += 1; + + k_heap_free(&direntry_heap, entry); + + /* Add ".." directory entry */ + entry = ext2_create_direntry("..", 2, parent->i_id, EXT2_FT_DIR); + + rc = ext2_add_direntry(new_inode, entry); + if (rc < 0) { + ret = rc; + goto out; + } + + /* Successfully added to directory */ + parent->i_links_count += 1; + + rc = ext2_commit_inode_block(new_inode); + if (rc < 0) { + ret = rc; + } + + rc = ext2_commit_inode_block(parent); + if (rc < 0) { + ret = rc; + } + + /* Commit inodes after increasing link counts */ + rc = ext2_commit_inode(new_inode); + if (rc < 0) { + ret = rc; + } + + rc = ext2_commit_inode(parent); + if (rc < 0) { + ret = rc; + } +out: + k_heap_free(&direntry_heap, entry); + return ret; +} + +static int ext2_del_direntry(struct ext2_inode *parent, uint32_t offset) +{ + int rc = 0; + uint32_t block_size = parent->i_fs->block_size; + + uint32_t blk = offset / block_size; + uint32_t blk_off = offset % block_size; + + rc = ext2_fetch_inode_block(parent, blk); + if (rc < 0) { + return rc; + } + + if (blk_off == 0) { + struct ext2_disk_direntry *de = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), 0); + uint16_t reclen = ext2_get_disk_direntry_reclen(de); + + if (reclen == block_size) { + /* Remove whole block */ + + uint32_t last_blk = parent->i_size / block_size - 1; + uint32_t old_blk = parent->i_block[blk]; + + /* move last block in place of removed one. Entries start only at beginning + * of the block, hence we don't have to care to move any entry. + */ + parent->i_block[blk] = parent->i_block[last_blk]; + parent->i_block[last_blk] = 0; + + /* Free removed block */ + rc = ext2_free_block(parent->i_fs, old_blk); + if (rc < 0) { + return rc; + } + + rc = ext2_commit_inode(parent); + if (rc < 0) { + return rc; + } + } else { + /* Move next entry to beginning of block */ + struct ext2_disk_direntry *next = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), reclen); + uint16_t next_reclen = ext2_get_disk_direntry_reclen(next); + + memmove(de, next, next_reclen); + ext2_set_disk_direntry_reclen(de, reclen + next_reclen); + + rc = ext2_commit_inode_block(parent); + if (rc < 0) { + return rc; + } + } + + } else { + /* Entry inside the block */ + uint32_t cur = 0; + uint16_t reclen; + + struct ext2_disk_direntry *de = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), 0); + + reclen = ext2_get_disk_direntry_reclen(de); + /* find previous entry */ + while (cur + reclen < blk_off) { + cur += reclen; + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), cur); + reclen = ext2_get_disk_direntry_reclen(de); + } + + struct ext2_disk_direntry *del_entry = + EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(parent), blk_off); + uint16_t del_reclen = ext2_get_disk_direntry_reclen(del_entry); + + ext2_set_disk_direntry_reclen(de, reclen + del_reclen); + rc = ext2_commit_inode_block(parent); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +static int remove_inode(struct ext2_inode *inode) +{ + int ret = 0; + + LOG_DBG("inode: %d", inode->i_id); + + /* Free blocks of inode */ + ret = ext2_inode_remove_blocks(inode, 0); + if (ret < 0) { + return ret; + } + + /* Free inode */ + ret = ext2_free_inode(inode->i_fs, inode->i_id, IS_DIR(inode->i_mode)); + return ret; +} + +static int can_unlink(struct ext2_inode *inode) +{ + if (!IS_DIR(inode->i_mode)) { + return 0; + } + + int rc = 0; + + rc = ext2_fetch_inode_block(inode, 0); + if (rc < 0) { + return rc; + } + + /* If directory check if it is empty */ + + uint32_t offset = 0; + struct ext2_disk_direntry *de; + + /* Get first entry */ + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), 0); + offset += ext2_get_disk_direntry_reclen(de); + + /* Get second entry */ + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(inode), offset); + offset += ext2_get_disk_direntry_reclen(de); + + uint32_t block_size = inode->i_fs->block_size; + + /* If directory has size of one block and second entry ends with block end + * then directory is empty. + */ + if (offset == block_size && inode->i_size == block_size) { + return 0; + } + + return -ENOTEMPTY; +} + +int ext2_inode_unlink(struct ext2_inode *parent, struct ext2_inode *inode, uint32_t offset) +{ + int rc; + + rc = can_unlink(inode); + if (rc < 0) { + return rc; + } + + rc = ext2_del_direntry(parent, offset); + if (rc < 0) { + return rc; + } + + if ((IS_REG_FILE(inode->i_mode) && inode->i_links_count == 1) || + (IS_DIR(inode->i_mode) && inode->i_links_count == 2)) { + + /* Only set the flag. Inode may still be open. Inode will be + * removed after dropping all references to it. + */ + inode->flags |= INODE_REMOVE; + } + + inode->i_links_count -= 1; + rc = ext2_commit_inode(inode); + if (rc < 0) { + return rc; + } + return 0; +} + +int ext2_replace_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to) +{ + LOG_DBG("Replace existing directory entry in rename"); + LOG_DBG("Inode: %d Inode to replace: %d", args_from->inode->i_id, args_to->inode->i_id); + + int rc = 0; + struct ext2_disk_direntry *de; + + uint32_t block_size = args_from->parent->i_fs->block_size; + uint32_t from_offset = args_from->offset; + uint32_t from_blk = from_offset / block_size; + uint32_t from_blk_off = from_offset % block_size; + + rc = ext2_fetch_inode_block(args_from->parent, from_blk); + if (rc < 0) { + return rc; + } + + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(args_from->parent), from_blk_off); + + /* record file type */ + uint8_t file_type = ext2_get_disk_direntry_type(de); + + /* NOTE: Replace the inode number in removed entry with inode of file that will be replaced + * with new one. Thanks to that we can use the function that unlinks directory entry to get + * rid of old directory entry and link to inode that will no longer be referenced by the + * directory entry after it is replaced with moved file. + */ + ext2_set_disk_direntry_inode(de, args_to->inode->i_id); + rc = ext2_inode_unlink(args_from->parent, args_to->inode, args_from->offset); + if (rc < 0) { + /* restore the old inode number */ + ext2_set_disk_direntry_inode(de, args_from->inode->i_id); + return rc; + } + + uint32_t to_offset = args_to->offset; + uint32_t to_blk = to_offset / block_size; + uint32_t to_blk_off = to_offset % block_size; + + rc = ext2_fetch_inode_block(args_to->parent, to_blk); + if (rc < 0) { + return rc; + } + + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(args_to->parent), to_blk_off); + + /* change inode of new entry */ + ext2_set_disk_direntry_inode(de, args_from->inode->i_id); + ext2_set_disk_direntry_type(de, file_type); + + rc = ext2_commit_inode_block(args_to->parent); + if (rc < 0) { + return rc; + } + return 0; +} + +int ext2_move_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to) +{ + int rc = 0; + uint32_t block_size = args_from->parent->i_fs->block_size; + + struct ext2_inode *fparent = args_from->parent; + struct ext2_inode *tparent = args_to->parent; + uint32_t offset = args_from->offset; + uint32_t blk = offset / block_size; + uint32_t blk_off = offset % block_size; + + /* Check if we could just modify existing entry */ + if (fparent->i_id == tparent->i_id) { + rc = ext2_fetch_inode_block(fparent, blk); + if (rc < 0) { + return rc; + } + + struct ext2_disk_direntry *de; + + de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(fparent), blk_off); + + uint16_t reclen = ext2_get_disk_direntry_reclen(de); + + /* If new name fits in old entry, then just copy it there */ + if (reclen - sizeof(struct ext2_disk_direntry) >= args_to->name_len) { + LOG_DBG("Old entry is modified to hold new name"); + ext2_set_disk_direntry_namelen(de, args_to->name_len); + ext2_set_disk_direntry_name(de, args_to->path + args_to->name_pos, + args_to->name_len); + + rc = ext2_commit_inode_block(fparent); + return rc; + } + } + + LOG_DBG("Create new directory entry in rename"); + + int ret = 0; + + rc = ext2_fetch_inode_block(fparent, blk); + if (rc < 0) { + return rc; + } + + struct ext2_disk_direntry *old_de; + struct ext2_direntry *new_de; + + old_de = EXT2_DISK_DIRENTRY_BY_OFFSET(inode_current_block_mem(fparent), blk_off); + + uint32_t inode = ext2_get_disk_direntry_inode(old_de); + uint8_t file_type = ext2_get_disk_direntry_type(old_de); + + new_de = ext2_create_direntry(args_to->path + args_to->name_pos, args_to->name_len, inode, + file_type); + + rc = ext2_add_direntry(tparent, new_de); + if (rc < 0) { + ret = rc; + goto out; + } + + rc = ext2_del_direntry(fparent, args_from->offset); + if (rc < 0) { + return rc; + } + +out: + k_heap_free(&direntry_heap, new_de); + return ret; +} + +int ext2_inode_get(struct ext2_data *fs, uint32_t ino, struct ext2_inode **ret) +{ + int rc; + struct ext2_inode *inode; + + for (int i = 0; i < fs->open_inodes; ++i) { + inode = fs->inode_pool[i]; + + if (inode->i_id == ino) { + *ret = inode; + inode->i_ref++; + return 0; + } + } + + if (fs->open_inodes >= MAX_INODES) { + return -ENOMEM; + } + + + rc = k_mem_slab_alloc(&inode_struct_slab, (void **)&inode, K_FOREVER); + if (rc < 0) { + return -ENOMEM; + } + memset(inode, 0, sizeof(struct ext2_inode)); + + if (ino != 0) { + int rc = ext2_fetch_inode(fs, ino, inode); + + if (rc < 0) { + k_mem_slab_free(&inode_struct_slab, (void **)&inode); + return rc; + } + } + + fs->inode_pool[fs->open_inodes] = inode; + fs->open_inodes++; + + inode->i_fs = fs; + inode->i_ref = 1; + *ret = inode; + return 0; +} + +int ext2_inode_drop(struct ext2_inode *inode) +{ + if (inode == NULL) { + return 0; + } + + struct ext2_data *fs = inode->i_fs; + + if (fs->open_inodes <= 0) { + LOG_WRN("All inodes should be already closed"); + return 0; + } + + inode->i_ref--; + + /* Clean inode if that was last reference */ + if (inode->i_ref == 0) { + + /* find entry */ + uint32_t offset = 0; + + while (fs->inode_pool[offset] != inode && offset < MAX_INODES) { + offset++; + } + + if (offset >= MAX_INODES) { + LOG_ERR("Inode structure at %p not in inode_pool", inode); + return -EINVAL; + } + + ext2_inode_drop_blocks(inode); + + if (inode->flags & INODE_REMOVE) { + /* This is the inode that should be removed because + * there was called unlink function on it. + */ + int rc = remove_inode(inode); + + if (rc < 0) { + return rc; + } + } + + k_mem_slab_free(&inode_struct_slab, (void **)&inode); + + /* copy last open in place of freed inode */ + uint32_t last = fs->open_inodes - 1; + + fs->inode_pool[offset] = fs->inode_pool[last]; + fs->open_inodes--; + + } + + return 0; +} + +void ext2_inode_drop_blocks(struct ext2_inode *inode) +{ + for (int i = 0; i < 4; ++i) { + ext2_drop_block(inode->blocks[i]); + } + inode->flags &= ~INODE_FETCHED_BLOCK; +} diff --git a/subsys/fs/ext2/ext2_impl.h b/subsys/fs/ext2/ext2_impl.h new file mode 100644 index 0000000000000..32e198dfb5320 --- /dev/null +++ b/subsys/fs/ext2/ext2_impl.h @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __EXT2_IMPL_H__ +#define __EXT2_IMPL_H__ + +#include +#include + +#include "ext2_struct.h" + +extern struct k_heap direntry_heap; + +void error_behavior(struct ext2_data *fs, const char *msg); + +/* Memory allocation for ext2 implementation */ +void *ext2_heap_alloc(size_t size); +void ext2_heap_free(void *ptr); + +/* Initialization of disk storage. */ +int ext2_init_disk_access_backend(struct ext2_data *fs, const void *storage_dev, int flags); + +/** + * @brief Get block from the disk. + */ +struct ext2_block *ext2_get_block(struct ext2_data *fs, uint32_t block); + +struct ext2_block *ext2_get_empty_block(struct ext2_data *fs); + +/** + * @brief Free the block structure. + */ +void ext2_drop_block(struct ext2_block *b); + +/** + * @brief Write block to the disk. + * + * NOTICE: to ensure that all writes has ended the sync of disk must be triggered + * (fs::sync function). + */ +int ext2_write_block(struct ext2_data *fs, struct ext2_block *b); + +void ext2_init_blocks_slab(struct ext2_data *fs); + +/** + * @brief Write block to the disk. + * + * NOTICE: to ensure that all writes has ended the sync of disk must be triggered + * (fs::sync function). + */ +int ext2_write_block(struct ext2_data *fs, struct ext2_block *b); + +int ext2_assign_block_num(struct ext2_data *fs, struct ext2_block *b); + +/* FS operations */ + +/** + * @brief Initialize structure with data needed to access the storage device + * + * @param fs File system data structure to initialize + * @param storage_dev Pointer to storage + * @param flags Additional flags (e.g. RO flag) + * + * @retval 0 on success + * @retval -EINVAL when superblock of ext2 was not detected + * @retval -ENOTSUP when described file system is not supported + * @retval <0 other error + */ +int ext2_init_storage(struct ext2_data **fsp, const void *storage_dev, int flags); + +/** + * @brief Verify superblock of file system + * + * Checks if file system is supported by the implementation. + * @retval 0 when superblock is valid + * @retval -EROFS when superblock is not valid but file system may be mounted read only + * @retval -EINVAL when superblock is not valid and file system cannot be mounted at all + * @retval -ENOTSUP when superblock has some field set to value that we don't support + */ +int ext2_verify_disk_superblock(struct ext2_disk_superblock *sb); + +/** + * @brief Initialize all data needed to perform operations on file system + * + * Fetches the superblock. Initializes structure fields. + */ +int ext2_init_fs(struct ext2_data *fs); + +/** + * @brief Clear the data used by file system implementation + * + */ +int ext2_close_fs(struct ext2_data *fs); + +/** + * @brief Clear the data used to communicate with storage device + * + */ +int ext2_close_struct(struct ext2_data *fs); + +/** + * @brief Create the ext2 file system + * + * This function uses functions stored in `ext2_data` structure to create new + * file system on storage device. + * + * NOTICE: fs structure must be first initialized with `ext2_init_fs` function. + * + * After this function succeeds the `ext2_clean` function must be called. + * + * @param fs File system data (must be initialized before) + * + * @retval 0 on success + * @retval -ENOSPC when storage device is too small for ext2 file system + * @retval -ENOTSUP when storage device is too big (file systems with more than + * 8192 blocks are not supported) + */ +int ext2_format(struct ext2_data *fs, struct ext2_cfg *cfg); + +/* Lookup flags */ +#define LOOKUP_ARG_OPEN BIT(0) +#define LOOKUP_ARG_CREATE BIT(1) +#define LOOKUP_ARG_STAT BIT(2) +#define LOOKUP_ARG_UNLINK BIT(3) + +/* Structure for lookup arguments and results. + * + * Required fields (must be filled when lookup function is invoked): + * - path + * - flags + * + * Fields that hold the result: + * - inode + * - parent + * - offset + * - name_pos + * - name_len + * + * Some of these fields have a meaning only for a specific function. + * (E.g. during stat only the fields parent and offset are used) + * + * Field is marked with these labels when lookup is used from other function: + * OP -- open + * CR -- create + * ST -- stat + * UN -- unlink + */ +struct ext2_lookup_args { + const char *path; /* path of inode */ + struct ext2_inode *inode; /* (OP, CR, ST, UN) found inode */ + struct ext2_inode *parent; /* (CR, ST, UN) parent of found inode */ + uint32_t offset; /* (CR, ST, UN) offset of entry in directory */ + uint32_t name_pos; /* (CR) position of name in input path */ + uint32_t name_len; /* (CR) length of name */ + uint8_t flags; /* indicates from which function lookup is invoked */ +}; + +/** + * @brief Look for an inode. + * + * @param fs File system data + * @param args All needed arguments for lookup + * + * @retval 0 on success + * @retval -ENOENT inode or path component not found + * @retval -ENOTDIR path component not a directory + * @retval <0 other error + */ +int ext2_lookup_inode(struct ext2_data *fs, struct ext2_lookup_args *args); + +/* Inode operations */ + +/** + * @brief Read from inode at given offset + * + * @param inode Inode + * @param buf Buffer to hold read data + * @param offset Offset in inode + * @param nbytes Number of bytes to read + * + * @retval >=0 number of bytes read on success + * @retval <0 error code + */ +ssize_t ext2_inode_read(struct ext2_inode *inode, void *buf, uint32_t offset, + size_t nbytes); + +/** + * @brief Write to inode at given offset + * + * @param inode Inode + * @param buf Buffer with data to write + * @param offset Offset in inode + * @param nbytes Number of bytes to write + * + * @retval >=0 number of bytes read on success + * @retval <0 error code + */ +ssize_t ext2_inode_write(struct ext2_inode *inode, const void *buf, + uint32_t offset, size_t nbytes); + +/** + * @brief Truncate the inode + * + * @param inode Inode + * @param size New size for inode + * + * @retval 0 on success + * @retval -ENOTSUP when requested size is too big + * @retval <0 other error + */ +int ext2_inode_trunc(struct ext2_inode *inode, off_t size); + +/** + * @brief Sync currently fetched blocks + * + * @param inode Inode + * + */ +int ext2_inode_sync(struct ext2_inode *inode); + +/* Directory operations */ + +/** + * @brief Get directory entry + * + * Reads directory entry that is at offset specified in `ext2_file` structure. + * + * @param dir Read directory + * @param ent Directory entry to fill in + * + * @retval 0 on success + * @retval <0 on error + */ +int ext2_get_direntry(struct ext2_file *dir, struct fs_dirent *ent); + +/** + * @brief Create a directory entry with given attributes + * + * Automatically calculates and sets de_rec_len field. + * + * NOTE: if you need to adjust the size (e.g. when this entry is the last one in the block) + * then just update the size after this function returns. + * + * @param name Name of direntry + * @param namelen Length of name + * @param ino Inode associated with that entry + * @param filetype File type of that entry + * + * @returns structure allocated on direntry_heap filled with given data + */ +struct ext2_direntry *ext2_create_direntry(const char *name, uint8_t namelen, uint32_t ino, + uint8_t filetype); + +/** + * @brief Create a file + * + * @param parent Parent directory + * @param inode Pointer to inode structure that will be filled with new inode + * @param args Lookup arguments that describe file to create + * + * @retval 0 on success + * @retval -ENOSPC there is not enough memory on storage device to create a file + * @retval <0 on error + */ +int ext2_create_file(struct ext2_inode *parent, struct ext2_inode *inode, + struct ext2_lookup_args *args); + + +/** + * @brief Create a directory + * + * @param parent Parent directory + * @param inode Pointer to inode structure that will be filled with new inode + * @param args Lookup arguments that describe directory to create + * + * @retval 0 on success + * @retval -ENOSPC there is not enough memory on storage device to create a file + * @retval <0 on error + */ +int ext2_create_dir(struct ext2_inode *parent, struct ext2_inode *inode, + struct ext2_lookup_args *args); + +/** + * @brief Unlink the directory entry at given offset in parent directory + * + * @param parent Parent directory + * @param inode File to unlink + * @param offset Offset of unlinked file in the parent directory + * + * @retval 0 on success + * @retval -ENOTEMPTY when directory to unlink is not empty + * @retval <0 other error + */ +int ext2_inode_unlink(struct ext2_inode *parent, struct ext2_inode *inode, + uint32_t offset); + +/** + * @brief Move a file + * + * Invoked when rename destination entry doesn't exist. + * + * @param args_from Describe source file + * @param args_to Describe destination + * + * @retval 0 on success + * @retval <0 on error + */ +int ext2_move_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to); + +/** + * @brief Replace the file with another + * + * Invoked when rename destination entry does exist + * + * @param args_from Describe source file + * @param args_to Describe destination file + * + * @retval 0 on success + * @retval <0 on error + */ +int ext2_replace_file(struct ext2_lookup_args *args_from, struct ext2_lookup_args *args_to); + +/* Inode pool operations */ + +/** + * @brief Get the inode + * + * Retrieves inode structure and stores it in the inode pool. The inode is + * filled with data of requested inode. + * + * @param fs File system data + * @param ino Inode number + * @param ret Pointer to place where to store new inode pointer + * + * @retval 0 on success + * @retval -ENOMEM when there is no memory to hold the requested inode + * @retval <0 on error + */ +int ext2_inode_get(struct ext2_data *fs, uint32_t ino, struct ext2_inode **ret); + +/** + * @brief Remove reference to the inode structure + * + * When removed reference is the last reference to that inode then it is freed. + * + * @param inode Dropped inode + * + * @retval 0 on success + * @retval -EINVAL the dropped inode is not stored in the inode pool + * @retval <0 on error + */ +int ext2_inode_drop(struct ext2_inode *inode); + +/* Drop blocks fetched in inode structure. */ +void ext2_inode_drop_blocks(struct ext2_inode *inode); + +/** + * @brief Remove all blocks starting with some block + * + * @param first First block to remove + * + * @retval >=0 number of removed blocks + * @retval <0 error code + */ +int64_t ext2_inode_remove_blocks(struct ext2_inode *inode, uint32_t first); + +#endif /* __EXT2_IMPL_H__ */ diff --git a/subsys/fs/ext2/ext2_ops.c b/subsys/fs/ext2/ext2_ops.c new file mode 100644 index 0000000000000..f3777e0e06bb2 --- /dev/null +++ b/subsys/fs/ext2/ext2_ops.c @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "../fs_impl.h" +#include "ext2.h" +#include "ext2_impl.h" +#include "ext2_struct.h" + +LOG_MODULE_DECLARE(ext2); + +K_MEM_SLAB_DEFINE(file_struct_slab, sizeof(struct ext2_file), CONFIG_MAX_FILES, sizeof(void *)); + +/* File operations */ + +static int ext2_open(struct fs_file_t *filp, const char *fs_path, fs_mode_t flags) +{ + int rc, ret = 0; + struct ext2_file *file; + struct ext2_data *fs = filp->mp->fs_data; + + if (fs->open_files >= CONFIG_MAX_FILES) { + LOG_DBG("Too many open files"); + return -EMFILE; + } + + LOG_DBG("Open mode: Rd:%d Wr:%d App:%d Creat:%d", + (flags & FS_O_READ) != 0, + (flags & FS_O_WRITE) != 0, + (flags & FS_O_APPEND) != 0, + (flags & FS_O_CREATE) != 0); + + const char *path = fs_impl_strip_prefix(fs_path, filp->mp); + struct ext2_lookup_args args = { + .path = path, + .inode = NULL, + .flags = LOOKUP_ARG_OPEN, + }; + + if (flags & FS_O_CREATE) { + args.flags |= LOOKUP_ARG_CREATE; + args.parent = NULL; + } + + rc = ext2_lookup_inode(fs, &args); + if (rc < 0) { + return rc; + } + + /* Inodes allocated by lookup. Must be freed in manually. */ + struct ext2_inode *found_inode = args.inode; + + /* Not NULL iff FS_O_CREATE and found_inode == NULL */ + struct ext2_inode *parent = args.parent; + + /* File has to be created */ + if (flags & FS_O_CREATE && found_inode == NULL) { + LOG_DBG("Returned from lookup & create: '%s':%d creating file: %d", + path + args.name_pos, args.name_len, found_inode == NULL); + + struct ext2_inode *new_inode; + + rc = ext2_inode_get(fs, 0, &new_inode); + if (rc < 0) { + ret = rc; + goto out; + } + + rc = ext2_create_file(parent, new_inode, &args); + if (rc < 0) { + ext2_inode_drop(new_inode); + ret = rc; + goto out; + } + + found_inode = new_inode; + } + + if ((found_inode->i_mode & EXT2_S_IFMT) != EXT2_S_IFREG) { + ret = -EINVAL; + goto out; + } + + rc = k_mem_slab_alloc(&file_struct_slab, (void **)&file, K_FOREVER); + if (rc < 0) { + ret = -ENOMEM; + goto out; + } + + file->f_inode = found_inode; + file->f_off = 0; + file->f_flags = flags & (FS_O_RDWR | FS_O_APPEND); + + filp->filep = file; + + ext2_inode_drop(parent); + return 0; + +out: + ext2_inode_drop(found_inode); + ext2_inode_drop(parent); + return ret; +} + +static int ext2_close(struct fs_file_t *filp) +{ + int rc; + struct ext2_file *f = filp->filep; + + rc = ext2_inode_sync(f->f_inode); + if (rc < 0) { + goto out; + } + + rc = ext2_inode_drop(f->f_inode); + if (rc < 0) { + goto out; + } + + k_mem_slab_free(&file_struct_slab, (void **)&f); + filp->filep = NULL; +out: + return rc; +} + +static ssize_t ext2_read(struct fs_file_t *filp, void *dest, size_t nbytes) +{ + struct ext2_file *f = filp->filep; + + if ((f->f_flags & FS_O_READ) == 0) { + return -EACCES; + } + + ssize_t r = ext2_inode_read(f->f_inode, dest, f->f_off, nbytes); + + if (r < 0) { + return r; + } + f->f_off += r; + return r; +} + +static ssize_t ext2_write(struct fs_file_t *filp, const void *src, size_t nbytes) +{ + struct ext2_file *f = filp->filep; + + if ((f->f_flags & FS_O_WRITE) == 0) { + return -EACCES; + } + + if (f->f_flags & FS_O_APPEND) { + f->f_off = f->f_inode->i_size; + } + + ssize_t r = ext2_inode_write(f->f_inode, src, f->f_off, nbytes); + + if (r < 0) { + return r; + } + + f->f_off += r; + return r; +} + +static int ext2_lseek(struct fs_file_t *filp, off_t off, int whence) +{ + struct ext2_file *f = filp->filep; + + uint32_t new_off = 0; + + switch (whence) { + case FS_SEEK_SET: + new_off = off; + break; + + case FS_SEEK_CUR: + new_off = f->f_off + off; + break; + + case FS_SEEK_END: + new_off = f->f_inode->i_size + off; + break; + + default: + return -EINVAL; + } + + /* New offset not inside the file. */ + if (new_off < 0 || new_off > f->f_inode->i_size) { + return -EINVAL; + } + f->f_off = new_off; + return 0; +} + +static off_t ext2_tell(struct fs_file_t *filp) +{ + struct ext2_file *f = filp->filep; + + return f->f_off; +} + +static int ext2_truncate(struct fs_file_t *filp, off_t length) +{ + struct ext2_file *f = filp->filep; + + if ((f->f_flags & FS_O_WRITE) == 0) { + return -EACCES; + } + + int rc = ext2_inode_trunc(f->f_inode, length); + + if (rc < 0) { + return rc; + } + return 0; +} + +static int ext2_sync(struct fs_file_t *filp) +{ + struct ext2_file *f = filp->filep; + + int rc = ext2_inode_sync(f->f_inode); + + if (rc >= 0) { + return 0; + } + return rc; +} + +/* Directory operations */ + +static int ext2_mkdir(struct fs_mount_t *mountp, const char *name) +{ + int rc, ret = 0; + struct ext2_data *fs = mountp->fs_data; + + const char *path = fs_impl_strip_prefix(name, mountp); + struct ext2_lookup_args args = { + args.path = path, + args.inode = NULL, + args.parent = NULL, + }; + + args.flags = LOOKUP_ARG_CREATE; + + rc = ext2_lookup_inode(fs, &args); + if (rc < 0) { + return rc; + } + + struct ext2_inode *found_inode = args.inode; + struct ext2_inode *parent = args.parent; + + LOG_DBG("Returned from lookup & create: '%s':%d res: %d", + path + args.name_pos, args.name_len, found_inode == NULL); + + if (found_inode != NULL) { + ret = -EEXIST; + goto out; + } + + rc = ext2_inode_get(fs, 0, &found_inode); + if (rc < 0) { + ret = rc; + goto out; + } + + rc = ext2_create_dir(parent, found_inode, &args); + if (rc < 0) { + ret = rc; + } + +out: + ext2_inode_drop(parent); + ext2_inode_drop(found_inode); + return ret; +} + +static int ext2_opendir(struct fs_dir_t *dirp, const char *fs_path) +{ + int rc, ret = 0; + struct ext2_file *dir; + const char *path = fs_impl_strip_prefix(fs_path, dirp->mp); + struct ext2_data *fs = dirp->mp->fs_data; + struct ext2_lookup_args args = { + .path = path, + .inode = NULL, + .flags = LOOKUP_ARG_OPEN, + }; + + rc = ext2_lookup_inode(fs, &args); + if (rc < 0) { + return rc; + } + + struct ext2_inode *found_inode = args.inode; + + if (!(found_inode->i_mode & EXT2_S_IFDIR)) { + ret = -ENOTDIR; + goto out; + } + + rc = k_mem_slab_alloc(&file_struct_slab, (void **)&dir, K_FOREVER); + if (rc < 0) { + ret = -ENOMEM; + goto out; + } + + + if (!dir) { + ret = -ENOMEM; + goto out; + } + + dir->f_inode = found_inode; + dir->f_off = 0; + + dirp->dirp = dir; + return 0; + +out: + ext2_inode_drop(found_inode); + return ret; +} + +static int ext2_readdir(struct fs_dir_t *dirp, struct fs_dirent *entry) +{ + struct ext2_file *dir = dirp->dirp; + int rc = ext2_get_direntry(dir, entry); + + if (rc < 0) { + return rc; + } + + return 0; +} + +static int ext2_closedir(struct fs_dir_t *dirp) +{ + struct ext2_file *dir = dirp->dirp; + + ext2_inode_drop(dir->f_inode); + k_mem_slab_free(&file_struct_slab, (void **)&dir); + return 0; +} + +/* File system level operations */ + +#ifdef CONFIG_FILE_SYSTEM_MKFS +FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default_cfg); +#endif + +/* Superblock is used only once. Because ext2 may have only one instance at the time we could + * statically allocate this strusture. + */ +static struct ext2_disk_superblock superblock; + +static int ext2_mount(struct fs_mount_t *mountp) +{ + int ret = 0; + struct ext2_data *fs = NULL; +#ifdef CONFIG_FILE_SYSTEM_MKFS + bool do_format = false; + bool possible_format = (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0 && + (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 0; +#endif + + ret = ext2_init_storage(&fs, mountp->storage_dev, mountp->flags); + if (ret < 0) { + goto err; + } + + fs->flags = 0; + if (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) { + fs->flags |= EXT2_DATA_FLAGS_RO; + } + + ret = fs->backend_ops->read_superblock(fs, &superblock); + if (ret < 0) { + goto err; + } + + ret = ext2_verify_disk_superblock(&superblock); + if (ret == 0) { + fs->block_size = 1024 << superblock.s_log_block_size; + + } else if (ret == -EROFS) { + fs->block_size = 1024 << superblock.s_log_block_size; + fs->flags |= EXT2_DATA_FLAGS_RO; + +#ifdef CONFIG_FILE_SYSTEM_MKFS + } else if (ret == -EINVAL && possible_format) { + do_format = true; + fs->block_size = ext2_default_cfg.block_size; +#endif + + } else { + goto err; + } + + if (fs->block_size % fs->write_size != 0) { + LOG_ERR("Blocks size isn't multiple of sector size. (bsz: %d, ssz: %d)", + fs->block_size, fs->write_size); + ret = -ENOTSUP; + goto err; + } + + ext2_init_blocks_slab(fs); + +#ifdef CONFIG_FILE_SYSTEM_MKFS + if (do_format) { + LOG_INF("Formatting the storage device"); + + ret = ext2_format(fs, &ext2_default_cfg); + if (ret < 0) { + goto err; + } + /* We don't need to verify superblock here again. Format has succeeded hence + * superblock must be valid. + */ + } +#endif + + ret = ext2_init_fs(fs); + if (ret < 0) { + goto err; + } + + mountp->fs_data = fs; + return 0; + +err: + ext2_close_struct(fs); + return ret; +} + +#if defined(CONFIG_FILE_SYSTEM_MKFS) + +static int ext2_mkfs(uintptr_t dev_id, void *vcfg, int flags) +{ + int ret = 0; + struct ext2_data *fs; + struct ext2_cfg *cfg = vcfg; + + if (cfg == NULL) { + cfg = &ext2_default_cfg; + } + + ret = ext2_init_storage(&fs, (const void *)dev_id, flags); + if (ret < 0) { + LOG_ERR("Initialization of %ld device failed (%d)", dev_id, ret); + goto out; + } + + fs->block_size = cfg->block_size; + + ext2_init_blocks_slab(fs); + + LOG_INF("Formatting the storage device"); + ret = ext2_format(fs, cfg); + if (ret < 0) { + LOG_ERR("Format of %ld device failed (%d)", dev_id, ret); + } + +out: + ext2_close_struct(fs); + return ret; +} + +#endif /* CONFIG_FILE_SYSTEM_MKFS */ + +static int ext2_unmount(struct fs_mount_t *mountp) +{ + int ret; + struct ext2_data *fs = mountp->fs_data; + + ret = ext2_close_fs(fs); + if (ret < 0) { + return ret; + } + + ret = ext2_close_struct(fs); + if (ret < 0) { + return ret; + } + mountp->fs_data = NULL; + return 0; +} + +static int ext2_unlink(struct fs_mount_t *mountp, const char *name) +{ + int rc, ret = 0; + struct ext2_data *fs = mountp->fs_data; + + const char *path = fs_impl_strip_prefix(name, mountp); + struct ext2_lookup_args args = { + args.path = path, + args.inode = NULL, + args.parent = NULL, + }; + + args.flags = LOOKUP_ARG_UNLINK; + + rc = ext2_lookup_inode(fs, &args); + if (rc < 0) { + return rc; + } + + ret = ext2_inode_unlink(args.parent, args.inode, args.offset); + + rc = ext2_inode_drop(args.parent); + if (rc < 0) { + LOG_WRN("Parent inode not dropped correctly in unlink (%d)", rc); + } + rc = ext2_inode_drop(args.inode); + if (rc < 0) { + LOG_WRN("Unlinked inode not dropped correctly in unlink (%d)", rc); + } + return ret; +} + +static int ext2_rename(struct fs_mount_t *mountp, const char *from, const char *to) +{ + int rc, ret = 0; + struct ext2_data *fs = mountp->fs_data; + + LOG_DBG("Rename: %s -> %s", from, to); + + const char *path_from = fs_impl_strip_prefix(from, mountp); + const char *path_to = fs_impl_strip_prefix(to, mountp); + + struct ext2_lookup_args args_from = { + .path = path_from, + .inode = NULL, + .parent = NULL, + .flags = LOOKUP_ARG_UNLINK, + }; + + struct ext2_lookup_args args_to = { + .path = path_to, + .inode = NULL, + .parent = NULL, + .flags = LOOKUP_ARG_CREATE, + }; + + rc = ext2_lookup_inode(fs, &args_from); + if (rc < 0) { + return rc; + } + + rc = ext2_lookup_inode(fs, &args_to); + if (rc < 0) { + return rc; + } + + if (args_to.inode != NULL) { + /* Replace existing directory entry with new one. */ + ret = ext2_replace_file(&args_from, &args_to); + } else { + /* Moving to new location */ + ret = ext2_move_file(&args_from, &args_to); + } + + ext2_inode_drop(args_from.inode); + ext2_inode_drop(args_from.parent); + ext2_inode_drop(args_to.inode); + ext2_inode_drop(args_to.parent); + return ret; +} + +static int ext2_stat(struct fs_mount_t *mountp, const char *path, struct fs_dirent *entry) +{ + int rc; + struct ext2_data *fs = mountp->fs_data; + + path = fs_impl_strip_prefix(path, mountp); + + struct ext2_lookup_args args = { + .path = path, + .parent = NULL, + .flags = LOOKUP_ARG_STAT, + }; + + rc = ext2_lookup_inode(fs, &args); + if (rc < 0) { + return rc; + } + + uint32_t offset = args.offset; + struct ext2_inode *parent = args.parent; + struct ext2_file dir = {.f_inode = parent, .f_off = offset}; + + rc = ext2_get_direntry(&dir, entry); + + ext2_inode_drop(parent); + ext2_inode_drop(args.inode); + return rc; +} + +static int ext2_statvfs(struct fs_mount_t *mountp, const char *path, struct fs_statvfs *stat) +{ + ARG_UNUSED(path); + struct ext2_data *fs = mountp->fs_data; + + stat->f_bsize = fs->block_size; + stat->f_frsize = fs->block_size; + stat->f_blocks = fs->sblock.s_blocks_count; + stat->f_bfree = fs->sblock.s_free_blocks_count; + + return 0; +} + +/* File system interface */ + +static const struct fs_file_system_t ext2_fs = { + .open = ext2_open, + .close = ext2_close, + .read = ext2_read, + .write = ext2_write, + .lseek = ext2_lseek, + .tell = ext2_tell, + .truncate = ext2_truncate, + .sync = ext2_sync, + .mkdir = ext2_mkdir, + .opendir = ext2_opendir, + .readdir = ext2_readdir, + .closedir = ext2_closedir, + .mount = ext2_mount, + .unmount = ext2_unmount, + .unlink = ext2_unlink, + .rename = ext2_rename, + .stat = ext2_stat, + .statvfs = ext2_statvfs, +#if defined(CONFIG_FILE_SYSTEM_MKFS) + .mkfs = ext2_mkfs, +#endif +}; + +static int ext2_init(void) +{ + int rc = fs_register(FS_EXT2, &ext2_fs); + + if (rc < 0) { + LOG_WRN("Ext2 register error (%d)\n", rc); + } else { + LOG_DBG("Ext2 fs registered\n"); + } + + return rc; +} + +SYS_INIT(ext2_init, POST_KERNEL, 99); diff --git a/subsys/fs/ext2/ext2_struct.h b/subsys/fs/ext2/ext2_struct.h new file mode 100644 index 0000000000000..f2805752140b1 --- /dev/null +++ b/subsys/fs/ext2/ext2_struct.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __EXT2_STRUCT_H__ +#define __EXT2_STRUCT_H__ + +#include +#include "ext2.h" + +/* Disk structures ---------------------------------------------------------- */ + +struct ext2_disk_superblock { + uint32_t s_inodes_count; + uint32_t s_blocks_count; + uint32_t s_r_blocks_count; + uint32_t s_free_blocks_count; + uint32_t s_free_inodes_count; + uint32_t s_first_data_block; + uint32_t s_log_block_size; + uint32_t s_log_frag_size; + uint32_t s_blocks_per_group; + uint32_t s_frags_per_group; + uint32_t s_inodes_per_group; + uint32_t s_mtime; + uint32_t s_wtime; + uint16_t s_mnt_count; + uint16_t s_max_mnt_count; + uint16_t s_magic; + uint16_t s_state; + uint16_t s_errors; + uint16_t s_minor_rev_level; + uint32_t s_lastcheck; + uint32_t s_checkinterval; + uint32_t s_creator_os; + uint32_t s_rev_level; + uint16_t s_def_resuid; + uint16_t s_def_resgid; + uint32_t s_first_ino; + uint16_t s_inode_size; + uint16_t s_block_group_nr; + uint32_t s_feature_compat; + uint32_t s_feature_incompat; + uint32_t s_feature_ro_compat; + uint8_t s_uuid[16]; + uint8_t s_volume_name[16]; + uint8_t s_last_mounted[64]; + uint32_t s_algo_bitmap; + uint8_t s_prealloc_blocks; + uint8_t s_prealloc_dir_blocks; + uint8_t s_align[2]; + uint8_t s_journal_uuid[16]; + uint32_t s_journal_inum; + uint32_t s_journal_dev; + uint32_t s_last_orphan; + uint8_t s_padding[788]; +} __packed; + +struct ext2_disk_bgroup { + uint32_t bg_block_bitmap; + uint32_t bg_inode_bitmap; + uint32_t bg_inode_table; + uint16_t bg_free_blocks_count; + uint16_t bg_free_inodes_count; + uint16_t bg_used_dirs_count; + uint16_t bg_pad; + uint8_t bg_reserved[12]; +} __packed; + +struct ext2_disk_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_ctime; + uint32_t i_mtime; + uint32_t i_dtime; + uint16_t i_gid; + uint16_t i_links_count; + uint32_t i_blocks; + uint32_t i_flags; + uint32_t i_osd1; + uint32_t i_block[15]; + uint32_t i_generation; + uint32_t i_file_acl; + uint32_t i_dir_acl; + uint32_t i_faddr; + uint8_t i_osd2[12]; +} __packed; + +struct ext2_disk_direntry { + uint32_t de_inode; + uint16_t de_rec_len; + uint8_t de_name_len; + uint8_t de_file_type; + char de_name[]; +} __packed; + +/* Program structures ------------------------------------------------------- */ + +struct ext2_superblock { + uint32_t s_inodes_count; + uint32_t s_blocks_count; + uint32_t s_free_blocks_count; + uint32_t s_free_inodes_count; + uint32_t s_first_data_block; + uint32_t s_log_block_size; + uint32_t s_log_frag_size; + uint32_t s_blocks_per_group; + uint32_t s_frags_per_group; + uint32_t s_inodes_per_group; + uint16_t s_mnt_count; + uint16_t s_max_mnt_count; + uint16_t s_magic; + uint16_t s_state; + uint16_t s_errors; + uint32_t s_creator_os; + uint32_t s_rev_level; + uint32_t s_first_ino; + uint16_t s_inode_size; + uint16_t s_block_group_nr; + uint32_t s_feature_compat; + uint32_t s_feature_incompat; + uint32_t s_feature_ro_compat; +}; + +#define EXT2_BLOCK_NUM_SIZE (sizeof(uint32_t)) +#define EXT2_DISK_DIRENTRY_BY_OFFSET(addr, offset) \ + ((struct ext2_disk_direntry *)(((uint8_t *)(addr)) + (offset))) + +#define EXT2_BLOCK_ASSIGNED BIT(0) + +struct ext2_block { + uint32_t num; + uint8_t flags; + uint8_t *data; +} __aligned(sizeof(void *)); + +#define BGROUP_INODE_TABLE(bg) ((struct ext2_disk_inode *)(bg)->inode_table->data) +#define BGROUP_INODE_BITMAP(bg) ((uint8_t *)(bg)->inode_bitmap->data) +#define BGROUP_BLOCK_BITMAP(bg) ((uint8_t *)(bg)->block_bitmap->data) + +struct ext2_bgroup { + struct ext2_data *fs; /* pointer to file system data */ + + struct ext2_block *inode_table; /* fetched block of inode table */ + struct ext2_block *inode_bitmap; /* inode bitmap */ + struct ext2_block *block_bitmap; /* block bitmap */ + + int32_t num; /* number of described block group */ + uint32_t inode_table_block; /* number of fetched block (relative) */ + + uint32_t bg_block_bitmap; + uint32_t bg_inode_bitmap; + uint32_t bg_inode_table; + uint16_t bg_free_blocks_count; + uint16_t bg_free_inodes_count; + uint16_t bg_used_dirs_count; +}; + +/* Flags for inode */ +#define INODE_FETCHED_BLOCK BIT(0) +#define INODE_REMOVE BIT(1) + +struct ext2_inode { + struct ext2_data *i_fs; /* pointer to file system data */ + uint8_t i_ref; /* reference count */ + + uint8_t flags; + uint32_t i_id; /* inode number */ + uint16_t i_mode; /* mode */ + uint16_t i_links_count; /* link count */ + uint32_t i_size; /* size */ + uint32_t i_blocks; /* number of reserved blocks (of size 512B) */ + uint32_t i_block[15]; /* numbers of blocks */ + + int block_lvl; /* level of current block */ + uint32_t block_num; /* relative number of fetched block */ + uint32_t offsets[4]; /* offsets describing path to fetched block */ + struct ext2_block *blocks[4]; /* fetched blocks for each level */ +}; + +static inline struct ext2_block *inode_current_block(struct ext2_inode *inode) +{ + return inode->blocks[inode->block_lvl]; +} + +static inline uint8_t *inode_current_block_mem(struct ext2_inode *inode) +{ + return (uint8_t *)inode_current_block(inode)->data; +} + +struct ext2_direntry { + uint32_t de_inode; + uint16_t de_rec_len; + uint8_t de_name_len; + uint8_t de_file_type; + char de_name[]; +}; + +/* Max size of directory entry that could be allocated from heap. */ +#define MAX_DIRENTRY_SIZE (sizeof(struct ext2_direntry) + UINT8_MAX) + +/* Structure common for files and directories representation */ +struct ext2_file { + struct ext2_inode *f_inode; + uint32_t f_off; + uint8_t f_flags; +}; + +#define EXT2_DATA_FLAGS_RO BIT(0) +#define EXT2_DATA_FLAGS_ERR BIT(1) + +struct ext2_data; + +struct ext2_backend_ops { + int64_t (*get_device_size)(struct ext2_data *fs); + int64_t (*get_write_size)(struct ext2_data *fs); + int (*read_block)(struct ext2_data *fs, void *buf, uint32_t num); + int (*write_block)(struct ext2_data *fs, const void *buf, uint32_t num); + int (*read_superblock)(struct ext2_data *fs, struct ext2_disk_superblock *sb); + int (*sync)(struct ext2_data *fs); +}; + +#define MAX_INODES (CONFIG_MAX_FILES + 2) + +struct ext2_data { + struct ext2_superblock sblock; /* superblock */ + struct ext2_bgroup bgroup; /* block group */ + + int32_t open_inodes; + int32_t open_files; + struct ext2_inode *inode_pool[MAX_INODES]; + + uint32_t sblock_offset; + uint32_t block_size; /* fs block size */ + uint32_t write_size; /* dev minimal write size */ + uint64_t device_size; + struct k_thread sync_thr; + + void *backend; /* pointer to implementation specific resource */ + const struct ext2_backend_ops *backend_ops; + uint8_t flags; +}; + +#endif /* __EXT2_STRUCT_H__ */ diff --git a/tests/subsys/fs/common/test_fs_basic.c b/tests/subsys/fs/common/test_fs_basic.c new file mode 100644 index 0000000000000..60b51ac48ac8a --- /dev/null +++ b/tests/subsys/fs/common/test_fs_basic.c @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "test_fs_util.h" + +#define HELLO "hello" +#define GOODBYE "goodbye" + +/* Mount point for the tests should be provided by test runner. + * File system should be mounted before tests are started. + */ +extern struct fs_mount_t *fs_basic_test_mp; + +static int create_write_hello(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("creating and writing file\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + HELLO, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "open hello failed"); + + struct fs_dirent stat; + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat new hello failed"); + + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, + "stat new hello not file"); + zassert_equal(strcmp(stat.name, HELLO), 0, + "stat new hello not hello"); + zassert_equal(stat.size, 0, + "stat new hello not empty"); + + zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE), + TESTFS_BUFFER_SIZE, + "write constant failed"); + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat written hello failed"); + + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, + "stat written hello not file"); + zassert_equal(strcmp(stat.name, HELLO), 0, + "stat written hello not hello"); + + /* Anomalous behavior requiring upstream response */ + if (mp->type == FS_LITTLEFS) { + /* VARIATION POINT: littlefs does not update the file size of + * an open file. See upstream issue #250. + */ + zassert_equal(stat.size, 0, + "stat written hello bad size"); + } + + zassert_equal(fs_close(&file), 0, + "close hello failed"); + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat closed hello failed"); + + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, + "stat closed hello not file"); + zassert_equal(strcmp(stat.name, HELLO), 0, + "stat closed hello not hello"); + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat closed hello badsize"); + + return TC_PASS; +} + +static int verify_hello(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("opening and verifying file\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + HELLO, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "verify hello open failed"); + + zassert_equal(fs_tell(&file), 0U, + "verify hello open tell failed"); + + zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE), + TESTFS_BUFFER_SIZE, + "verify hello at start failed"); + + zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, + "verify hello read tell failed"); + + zassert_equal(fs_close(&file), 0, + "verify close hello failed"); + + return TC_PASS; +} + +static int seek_within_hello(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("seek and tell in file\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + HELLO, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "verify hello open failed"); + + zassert_equal(fs_tell(&file), 0U, + "verify hello open tell failed"); + + struct fs_dirent stat; + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat old hello failed"); + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat old hello bad size"); + + off_t pos = stat.size / 4; + + zassert_equal(fs_seek(&file, pos, FS_SEEK_SET), + 0, + "verify hello seek near mid failed"); + + zassert_equal(fs_tell(&file), pos, + "verify hello tell near mid failed"); + + zassert_equal(testfs_verify_incrementing(&file, pos, TESTFS_BUFFER_SIZE), + TESTFS_BUFFER_SIZE - pos, + "verify hello at middle failed"); + + zassert_equal(fs_tell(&file), stat.size, + "verify hello read middle tell failed"); + + zassert_equal(fs_seek(&file, -stat.size, FS_SEEK_CUR), + 0, + "verify hello seek back from cur failed"); + + zassert_equal(fs_tell(&file), 0U, + "verify hello tell back from cur failed"); + + zassert_equal(fs_seek(&file, -pos, FS_SEEK_END), + 0, + "verify hello seek from end failed"); + + zassert_equal(fs_tell(&file), stat.size - pos, + "verify hello tell from end failed"); + + zassert_equal(testfs_verify_incrementing(&file, stat.size - pos, + TESTFS_BUFFER_SIZE), + pos, + "verify hello at post middle failed"); + + zassert_equal(fs_close(&file), 0, + "verify close hello failed"); + + return TC_PASS; +} + +static int truncate_hello(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("truncate in file\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + HELLO, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "verify hello open failed"); + + struct fs_dirent stat; + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat old hello failed"); + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat old hello bad size"); + + off_t pos = 3 * stat.size / 4; + + zassert_equal(fs_tell(&file), 0U, + "truncate initial tell failed"); + + zassert_equal(fs_truncate(&file, pos), + 0, + "truncate 3/4 failed"); + + zassert_equal(fs_tell(&file), 0U, + "truncate post tell failed"); + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat open 3/4 failed"); + + /* Anomalous behavior requiring upstream response */ + if (mp->type == FS_LITTLEFS) { + /* VARIATION POINT: littlefs does not update the file size of + * an open file. See upstream issue #250. + */ + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat open 3/4 bad size"); + } + + zassert_equal(testfs_verify_incrementing(&file, 0, 64), + 48, + "post truncate content unexpected"); + + zassert_equal(fs_close(&file), 0, + "post truncate close failed"); + + /* After close size is correct. */ + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat closed truncated failed"); + zassert_equal(stat.size, pos, + "stat closed truncated bad size"); + + return TC_PASS; +} + +static int unlink_hello(const struct fs_mount_t *mp) +{ + struct testfs_path path; + + TC_PRINT("unlink hello\n"); + + testfs_path_init(&path, mp, + HELLO, + TESTFS_PATH_END); + + struct fs_dirent stat; + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat existing hello failed"); + zassert_equal(fs_unlink(path.path), + 0, + "unlink hello failed"); + zassert_equal(fs_stat(path.path, &stat), + -ENOENT, + "stat existing hello failed"); + + return TC_PASS; +} + +static int sync_goodbye(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("sync goodbye\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + GOODBYE, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "sync goodbye failed"); + + struct fs_dirent stat; + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat existing hello failed"); + zassert_equal(stat.size, 0, + "stat new goodbye not empty"); + + zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE), + TESTFS_BUFFER_SIZE, + "write goodbye failed"); + + zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, + "tell goodbye failed"); + + if (true && mp->type == FS_LITTLEFS) { + /* Upstream issue #250 */ + zassert_equal(stat.size, 0, + "stat new goodbye not empty"); + } + + zassert_equal(fs_sync(&file), 0, + "sync goodbye failed"); + + zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, + "tell synced moved"); + + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat existing hello failed"); + printk("sync size %u\n", (uint32_t)stat.size); + + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat synced goodbye not correct"); + + zassert_equal(fs_close(&file), 0, + "post sync close failed"); + + /* After close size is correct. */ + zassert_equal(fs_stat(path.path, &stat), + 0, + "stat sync failed"); + zassert_equal(stat.size, TESTFS_BUFFER_SIZE, + "stat sync bad size"); + + return TC_PASS; +} + +static int verify_goodbye(const struct fs_mount_t *mp) +{ + struct testfs_path path; + struct fs_file_t file; + + fs_file_t_init(&file); + TC_PRINT("verify goodbye\n"); + + zassert_equal(fs_open(&file, + testfs_path_init(&path, mp, + GOODBYE, + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "verify goodbye failed"); + + zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE), + TESTFS_BUFFER_SIZE, + "write goodbye failed"); + + zassert_equal(fs_close(&file), 0, + "post sync close failed"); + + return TC_PASS; +} + +void test_fs_basic(void) +{ + + zassert_equal(fs_mount(fs_basic_test_mp), 0, + "mount failed"); + + zassert_equal(create_write_hello(fs_basic_test_mp), TC_PASS, + "write hello failed"); + + zassert_equal(verify_hello(fs_basic_test_mp), TC_PASS, + "verify hello failed"); + + zassert_equal(seek_within_hello(fs_basic_test_mp), TC_PASS, + "seek within hello failed"); + + zassert_equal(truncate_hello(fs_basic_test_mp), TC_PASS, + "truncate hello failed"); + + zassert_equal(unlink_hello(fs_basic_test_mp), TC_PASS, + "unlink hello failed"); + + zassert_equal(sync_goodbye(fs_basic_test_mp), TC_PASS, + "sync goodbye failed"); + + TC_PRINT("unmounting %s\n", fs_basic_test_mp->mnt_point); + zassert_equal(fs_unmount(fs_basic_test_mp), 0, + "unmount small failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + TC_PRINT("checking double unmount diagnoses\n"); + + zassert_equal(fs_unmount(fs_basic_test_mp), -EINVAL, + "unmount unmounted failed"); + + zassert_equal(fs_mount(fs_basic_test_mp), 0, + "mount failed"); + + zassert_equal(verify_goodbye(fs_basic_test_mp), TC_PASS, + "verify goodbye failed"); + + zassert_equal(fs_unmount(fs_basic_test_mp), 0, + "unmount2 small failed"); +} diff --git a/tests/subsys/fs/common/test_fs_dirops.c b/tests/subsys/fs/common/test_fs_dirops.c new file mode 100644 index 0000000000000..3bd0cf2a48ffd --- /dev/null +++ b/tests/subsys/fs/common/test_fs_dirops.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "test_fs_util.h" + +/* Mount point for the tests should be provided by test runner. + * File system should be mounted before tests are started. + */ +extern struct fs_mount_t *fs_dirops_test_mp; + +static struct testfs_bcmd test_hierarchy[] = { + TESTFS_BCMD_FILE("f1", 1, 1), + TESTFS_BCMD_FILE("f2", 2, 100), + TESTFS_BCMD_ENTER_DIR("d1"), + TESTFS_BCMD_FILE("d1f1", 11, 23), + TESTFS_BCMD_FILE("d1f2", 12, 612), + TESTFS_BCMD_EXIT_DIR(), + TESTFS_BCMD_FILE("f3", 3, 10000), + TESTFS_BCMD_END(), +}; + +static int check_mkdir(struct fs_mount_t *mp) +{ + struct testfs_path dpath; + + TC_PRINT("checking dir create unlink\n"); + zassert_equal(testfs_path_init(&dpath, mp, + "dir", + TESTFS_PATH_END), + dpath.path, + "root init failed"); + + zassert_equal(fs_mkdir(dpath.path), + 0, + "mkdir failed"); + + struct fs_file_t file; + struct testfs_path fpath; + + fs_file_t_init(&file); + zassert_equal(fs_open(&file, + testfs_path_extend(testfs_path_copy(&fpath, + &dpath), + "file", + TESTFS_PATH_END), + FS_O_CREATE | FS_O_RDWR), + 0, + "creat in dir failed"); + zassert_equal(fs_close(&file), 0, + "close file failed"); + + struct fs_dirent stat; + + zassert_equal(fs_stat(fpath.path, &stat), 0, + "stat file failed"); + + zassert_equal(fs_unlink(dpath.path), + -ENOTEMPTY, + "unlink bad failure"); + + zassert_equal(fs_unlink(fpath.path), + 0, + "unlink file failed"); + + zassert_equal(fs_unlink(dpath.path), + 0, + "unlink dir failed"); + + return TC_PASS; +} + +static int build_layout(struct fs_mount_t *mp, + const struct testfs_bcmd *cp) +{ + struct testfs_path path; + struct fs_statvfs stat; + + TC_PRINT("building layout on %s\n", mp->mnt_point); + + zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, + "statvfs failed"); + + TC_PRINT("before: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", + stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); + + zassert_equal(testfs_path_init(&path, mp, TESTFS_PATH_END), + path.path, + "root init failed"); + + zassert_equal(testfs_build(&path, cp), + 0, + "Build_layout failed"); + + zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, + "statvfs failed"); + + TC_PRINT("after: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", + stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); + + return TC_PASS; +} + +static int check_layout(struct fs_mount_t *mp, + struct testfs_bcmd *layout) +{ + struct testfs_path path; + struct testfs_bcmd *end_layout = testfs_bcmd_end(layout); + + TC_PRINT("checking layout\n"); + + zassert_equal(testfs_path_init(&path, mp, TESTFS_PATH_END), + path.path, + "root init failed"); + + int rc = testfs_bcmd_verify_layout(&path, layout, end_layout); + + zassert_true(rc >= 0, "layout check failed"); + + zassert_equal(rc, 0, + "layout found foreign"); + + struct testfs_bcmd *cp = layout; + + while (!TESTFS_BCMD_IS_END(cp)) { + if (cp->name != NULL) { + TC_PRINT("verifying %s%s %u\n", + cp->name, + (cp->type == FS_DIR_ENTRY_DIR) ? "/" : "", + cp->size); + zassert_true(cp->matched, + "Unmatched layout entry"); + } + ++cp; + } + + return TC_PASS; +} + +static int check_rename(struct fs_mount_t *mp) +{ + struct testfs_path root; + struct testfs_path from_path; + const char *from = from_path.path; + struct testfs_path to_path; + const char *to = to_path.path; + struct fs_dirent stat; + struct testfs_bcmd from_bcmd[] = { + TESTFS_BCMD_FILE("f1f", 1, 8), /* from f1f to f1t */ + TESTFS_BCMD_FILE("f2f", 2, 8), /* from f2f to f2t */ + TESTFS_BCMD_FILE("f2t", 3, 8), /* target for f2f clobber, replaced */ + TESTFS_BCMD_FILE("f3f", 4, 8), /* from f3f to d1f/d1f2t */ + TESTFS_BCMD_FILE("f4f", 5, 8), /* from f4f to d2t, reject */ + TESTFS_BCMD_ENTER_DIR("d1f"), /* from d1f to d1t */ + TESTFS_BCMD_FILE("d1f1", 5, 16), + TESTFS_BCMD_EXIT_DIR(), + TESTFS_BCMD_ENTER_DIR("d2t"), /* target for d1f, unchanged */ + TESTFS_BCMD_FILE("d2f1", 6, 16), + TESTFS_BCMD_EXIT_DIR(), + TESTFS_BCMD_END(), + }; + struct testfs_bcmd *from_end_bcmd = testfs_bcmd_end(from_bcmd); + struct testfs_bcmd to_bcmd[] = { + TESTFS_BCMD_FILE("f1t", 1, 8), /* from f1f to f1t */ + TESTFS_BCMD_FILE("f2t", 2, 8), /* from f2f to f2t */ + TESTFS_BCMD_FILE("f4f", 5, 8), /* from f4f to d2t, reject */ + TESTFS_BCMD_ENTER_DIR("d1t"), /* from d1f to d1t */ + TESTFS_BCMD_FILE("d1f1", 5, 16), + TESTFS_BCMD_FILE("d1f2t", 4, 8), /* from f3f to d1f/d1f2t */ + TESTFS_BCMD_EXIT_DIR(), + TESTFS_BCMD_ENTER_DIR("d2t"), /* target for d1f, unchanged */ + TESTFS_BCMD_FILE("d2f1", 6, 16), + TESTFS_BCMD_EXIT_DIR(), + TESTFS_BCMD_END(), + }; + struct testfs_bcmd *to_end_bcmd = testfs_bcmd_end(to_bcmd); + + zassert_equal(testfs_path_init(&root, mp, + "rename", + TESTFS_PATH_END), + root.path, + "root init failed"); + + zassert_equal(fs_mkdir(root.path), + 0, + "rename mkdir failed"); + zassert_equal(testfs_build(&root, from_bcmd), + 0, + "rename build failed"); + + zassert_equal(testfs_bcmd_verify_layout(&root, from_bcmd, from_end_bcmd), + 0, + "layout check failed"); + + testfs_path_extend(testfs_path_copy(&from_path, &root), + "nofile", + TESTFS_PATH_END); + testfs_path_extend(testfs_path_copy(&to_path, &root), + "f1t", + TESTFS_PATH_END); + TC_PRINT("%s => %s -ENOENT\n", from, to); + zassert_equal(fs_rename(from, to), + -ENOENT, + "rename noent failed"); + + /* f1f => f1t : ok */ + testfs_path_extend(testfs_path_copy(&from_path, &root), + "f1f", + TESTFS_PATH_END); + TC_PRINT("%s => %s ok\n", from, to); + zassert_equal(fs_rename(from, to), + 0, + "rename noent failed"); + + /* f2f => f2t : clobbers */ + testfs_path_extend(testfs_path_copy(&from_path, &root), + "f2f", + TESTFS_PATH_END); + testfs_path_extend(testfs_path_copy(&to_path, &root), + "f2t", + TESTFS_PATH_END); + TC_PRINT("%s => %s clobber ok\n", from, to); + zassert_equal(fs_rename(from, to), + 0, + "rename clobber failed"); + zassert_equal(fs_stat(from, &stat), + -ENOENT, + "rename clobber left from"); + + /* f3f => d1f/d1f2t : moves */ + testfs_path_extend(testfs_path_copy(&from_path, &root), + "f3f", + TESTFS_PATH_END); + testfs_path_extend(testfs_path_copy(&to_path, &root), + "d1f", "d1f2t", + TESTFS_PATH_END); + TC_PRINT("%s => %s move ok\n", from, to); + zassert_equal(fs_rename(from, to), + 0, + "rename to subdir failed"); + zassert_equal(fs_stat(from, &stat), + -ENOENT, + "rename to subdir left from"); + + /* d1f => d2t : reject */ + testfs_path_extend(testfs_path_copy(&from_path, &root), + "d1f", + TESTFS_PATH_END); + testfs_path_extend(testfs_path_copy(&to_path, &root), + "d2t", + TESTFS_PATH_END); + TC_PRINT("%s => %s -ENOTEMPTY\n", from, to); + zassert_equal(fs_rename(from, to), + -ENOTEMPTY, + "rename to existing dir failed"); + + /* d1f => d1t : rename */ + testfs_path_extend(testfs_path_copy(&from_path, &root), + "d1f", + TESTFS_PATH_END); + testfs_path_extend(testfs_path_copy(&to_path, &root), + "d1t", + TESTFS_PATH_END); + TC_PRINT("%s => %s ok\n", from, to); + zassert_equal(fs_rename(from, to), + 0, + "rename to new dir failed"); + zassert_equal(fs_stat(from, &stat), + -ENOENT, + "rename to new dir left from"); + + zassert_equal(testfs_bcmd_verify_layout(&root, to_bcmd, to_end_bcmd), + 0, + "layout verification failed"); + + struct testfs_bcmd *cp = to_bcmd; + + while (cp != to_end_bcmd) { + if (cp->name) { + zassert_true(cp->matched, "foreign file retained"); + } + ++cp; + } + + return TC_PASS; +} + +void test_fs_dirops(void) +{ + zassert_equal(fs_mount(fs_dirops_test_mp), 0, + "mount failed"); + + zassert_equal(check_mkdir(fs_dirops_test_mp), TC_PASS, + "check mkdir failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(build_layout(fs_dirops_test_mp, test_hierarchy), TC_PASS, + "build test hierarchy failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(check_layout(fs_dirops_test_mp, test_hierarchy), TC_PASS, + "check test hierarchy failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(check_rename(fs_dirops_test_mp), TC_PASS, + "check rename failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(fs_unmount(fs_dirops_test_mp), 0, + "unmount small failed"); +} diff --git a/tests/subsys/fs/common/test_fs_mount_flags.c b/tests/subsys/fs/common/test_fs_mount_flags.c new file mode 100644 index 0000000000000..009687778cd5f --- /dev/null +++ b/tests/subsys/fs/common/test_fs_mount_flags.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* Mount point for test should be provided by test runner */ +extern struct fs_mount_t *mount_flags_mp; +extern const char *mount_flags_mnt_point_str; + +void test_fs_mount_flags(void) +{ + int ret = 0; + struct fs_file_t fs; + struct fs_mount_t *mp = mount_flags_mp; + + fs_file_t_init(&fs); + + /* Test FS_MOUNT_FLAG_NO_FORMAT flag */ + mp->flags |= FS_MOUNT_FLAG_NO_FORMAT; + ret = fs_mount(mp); + TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_NO_FORMAT set\n"); + zassert_false(ret == 0, "Expected failure", ret); + + /* Test FS_MOUNT_FLAG_READ_ONLY on non-formatted volume*/ + mp->flags = FS_MOUNT_FLAG_READ_ONLY; + ret = fs_mount(mp); + TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_READ_ONLY set\n"); + zassert_false(ret == 0, "Expected failure", ret); + + /* Format volume and add some files/dirs to check read-only flag */ + mp->flags = 0; + ret = fs_mount(mp); + TC_PRINT("Mount again to format volume\n"); + zassert_equal(ret, 0, "Expected success", ret); + TC_PRINT("Create some file\n"); + ret = fs_open(&fs, "/sml/some", FS_O_CREATE); + zassert_equal(ret, 0, "Expected success", ret); + fs_close(&fs); + TC_PRINT("Create other directory\n"); + ret = fs_mkdir("/sml/other"); + zassert_equal(ret, 0, "Expected success", ret); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Expected success", ret); + + /* Check fs operation on volume mounted with FS_MOUNT_FLAG_READ_ONLY */ + mp->flags = FS_MOUNT_FLAG_READ_ONLY; + TC_PRINT("Mount as read-only\n"); + ret = fs_mount(mp); + zassert_equal(ret, 0, "Expected success", ret); + + /* Attempt creating new file */ + ret = fs_open(&fs, "/sml/nosome", FS_O_CREATE); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_mkdir("/sml/another"); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_rename("/sml/some", "/sml/nosome"); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_unlink("/sml/some"); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_open(&fs, "/sml/other", FS_O_CREATE); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_open(&fs, "/sml/some", FS_O_RDWR); + zassert_equal(ret, -EROFS, "Expected EROFS", ret); + ret = fs_open(&fs, "/sml/some", FS_O_READ); + zassert_equal(ret, 0, "Expected success", ret); + fs_close(&fs); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Expected success", ret); +} diff --git a/tests/subsys/fs/littlefs/src/testfs_util.c b/tests/subsys/fs/common/test_fs_util.c similarity index 99% rename from tests/subsys/fs/littlefs/src/testfs_util.c rename to tests/subsys/fs/common/test_fs_util.c index e95a392d5ebf5..e682203e4d73f 100644 --- a/tests/subsys/fs/littlefs/src/testfs_util.c +++ b/tests/subsys/fs/common/test_fs_util.c @@ -8,7 +8,7 @@ #include #include #include -#include "testfs_util.h" +#include "test_fs_util.h" static const char *path_vextend(struct testfs_path *pp, va_list ap) diff --git a/tests/subsys/fs/littlefs/src/testfs_util.h b/tests/subsys/fs/common/test_fs_util.h similarity index 100% rename from tests/subsys/fs/littlefs/src/testfs_util.h rename to tests/subsys/fs/common/test_fs_util.h diff --git a/tests/subsys/fs/ext2/CMakeLists.txt b/tests/subsys/fs/ext2/CMakeLists.txt new file mode 100644 index 0000000000000..5185c3c840351 --- /dev/null +++ b/tests/subsys/fs/ext2/CMakeLists.txt @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ext2tests) + +FILE(GLOB app_sources src/*.c) +FILE(GLOB common_sources + ../common/test_fs_util.c + ../common/test_fs_mkfs.c + ../common/test_fs_basic.c + ../common/test_fs_dirops.c + ../common/test_fs_open_flags.c + ../common/test_fs_mount_flags.c +) +target_sources(app PRIVATE + ${app_sources} + ${common_sources} +) diff --git a/tests/subsys/fs/ext2/Kconfig b/tests/subsys/fs/ext2/Kconfig new file mode 100644 index 0000000000000..3645a3c34fc31 --- /dev/null +++ b/tests/subsys/fs/ext2/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2023 Antmicro + +mainmenu "Ext2 tests" + +source "Kconfig.zephyr" + +config APP_TEST_BIG + bool "Make tests with larger file systems" diff --git a/tests/subsys/fs/ext2/boards/hifive_unmatched.overlay b/tests/subsys/fs/ext2/boards/hifive_unmatched.overlay new file mode 100644 index 0000000000000..730370e1b5a8b --- /dev/null +++ b/tests/subsys/fs/ext2/boards/hifive_unmatched.overlay @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&spi2 { + status = "okay"; + + sdhc0: sdhc@0 { + compatible = "zephyr,sdhc-spi-slot"; + reg = <0>; + status = "okay"; + mmc { + compatible = "zephyr,sdmmc-disk"; + status = "okay"; + }; + spi-max-frequency = <20000000>; + }; +}; diff --git a/tests/subsys/fs/ext2/boards/native_posix.overlay b/tests/subsys/fs/ext2/boards/native_posix.overlay new file mode 100644 index 0000000000000..5b40394859052 --- /dev/null +++ b/tests/subsys/fs/ext2/boards/native_posix.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flashcontroller0 { + reg = <0x00000000 DT_SIZE_M(128)>; +}; + +&flash0 { + reg = <0x00000000 DT_SIZE_M(128)>; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage: partition@0 { + reg = <0x00000000 0x08000000>; + }; + }; +}; + +/ { + storage_disk { + compatible = "zephyr,flash-disk"; + partition = <&storage>; + disk-name = "NAND"; + cache-size = <4096>; + }; +}; diff --git a/tests/subsys/fs/ext2/boards/native_posix_64.overlay b/tests/subsys/fs/ext2/boards/native_posix_64.overlay new file mode 100644 index 0000000000000..5b40394859052 --- /dev/null +++ b/tests/subsys/fs/ext2/boards/native_posix_64.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flashcontroller0 { + reg = <0x00000000 DT_SIZE_M(128)>; +}; + +&flash0 { + reg = <0x00000000 DT_SIZE_M(128)>; + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + storage: partition@0 { + reg = <0x00000000 0x08000000>; + }; + }; +}; + +/ { + storage_disk { + compatible = "zephyr,flash-disk"; + partition = <&storage>; + disk-name = "NAND"; + cache-size = <4096>; + }; +}; diff --git a/tests/subsys/fs/ext2/prj.conf b/tests/subsys/fs/ext2/prj.conf new file mode 100644 index 0000000000000..8661fc9969e19 --- /dev/null +++ b/tests/subsys/fs/ext2/prj.conf @@ -0,0 +1,13 @@ +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_EXT2=y +CONFIG_FILE_SYSTEM_MKFS=y + +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVER_RAM=y +CONFIG_DISK_RAM_VOLUME_SIZE=200 + +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_STACK_SIZE=4096 diff --git a/tests/subsys/fs/ext2/prj_big.conf b/tests/subsys/fs/ext2/prj_big.conf new file mode 100644 index 0000000000000..1a108557b47ca --- /dev/null +++ b/tests/subsys/fs/ext2/prj_big.conf @@ -0,0 +1,17 @@ +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_EXT2=y +CONFIG_FILE_SYSTEM_MKFS=y + +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVER_RAM=y +CONFIG_DISK_RAM_VOLUME_SIZE=9000 + +CONFIG_EXT2_MAX_BLOCK_COUNT=20 + +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_STACK_SIZE=4096 + +CONFIG_APP_TEST_BIG=y diff --git a/tests/subsys/fs/ext2/prj_flash.conf b/tests/subsys/fs/ext2/prj_flash.conf new file mode 100644 index 0000000000000..741f45771af67 --- /dev/null +++ b/tests/subsys/fs/ext2/prj_flash.conf @@ -0,0 +1,12 @@ +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_EXT2=y +CONFIG_FILE_SYSTEM_MKFS=y + +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVER_FLASH=y + +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_STACK_SIZE=4096 diff --git a/tests/subsys/fs/ext2/prj_sdcard.conf b/tests/subsys/fs/ext2/prj_sdcard.conf new file mode 100644 index 0000000000000..6089edf67c0eb --- /dev/null +++ b/tests/subsys/fs/ext2/prj_sdcard.conf @@ -0,0 +1,16 @@ +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_EXT2=y +CONFIG_FILE_SYSTEM_MKFS=y + +CONFIG_SPI=y + +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVER_SDMMC=y + +CONFIG_EXT2_DISK_STARTING_SECTOR=34 + +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_STACK_SIZE=4096 diff --git a/tests/subsys/fs/ext2/src/main.c b/tests/subsys/fs/ext2/src/main.c new file mode 100644 index 0000000000000..d58a904270ee0 --- /dev/null +++ b/tests/subsys/fs/ext2/src/main.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "utils.h" + +#ifdef CONFIG_DISK_DRIVER_RAM + #define STORAGE_DEVICE "RAM" +#elif CONFIG_DISK_DRIVER_FLASH + #define STORAGE_DEVICE "NAND" +#elif CONFIG_DISK_DRIVER_SDMMC + #define STORAGE_DEVICE "SDMMC" +#endif + +/* All tests must use this structure to mount file system. After each test this structure is cleaned + * to allow for running next tests unaffected by previous one. + */ +struct fs_mount_t testfs_mnt = { + .type = FS_EXT2, + .mnt_point = "/sml", + .storage_dev = STORAGE_DEVICE, + .flags = 0, +}; + +static void before_test(void *f) +{ + ARG_UNUSED(f); + + zassert_equal(wipe_partition((uintptr_t)testfs_mnt.storage_dev), TC_PASS, + "Failed to clean partition"); + testfs_mnt.flags = 0; +} + +static void after_test(void *f) +{ + ARG_UNUSED(f); + + /* Unmount file system */ + fs_unmount(&testfs_mnt); +} + +ZTEST_SUITE(ext2tests, NULL, NULL, before_test, after_test, NULL); diff --git a/tests/subsys/fs/ext2/src/test_basic.c b/tests/subsys/fs/ext2/src/test_basic.c new file mode 100644 index 0000000000000..b2d4ba5d0856f --- /dev/null +++ b/tests/subsys/fs/ext2/src/test_basic.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "utils.h" + +void test_fs_basic(void); + +/* Expected by test_fs_basic() */ +struct fs_mount_t *fs_basic_test_mp = &testfs_mnt; + +ZTEST(ext2tests, test_basic) +{ + /* Common basic tests. + * (File system is mounted and unmounted during that test.) + */ + test_fs_basic(); +} diff --git a/tests/subsys/fs/ext2/src/test_dirops.c b/tests/subsys/fs/ext2/src/test_dirops.c new file mode 100644 index 0000000000000..f5acbfd766e9f --- /dev/null +++ b/tests/subsys/fs/ext2/src/test_dirops.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "utils.h" + +void test_fs_dirops(void); + +/* Mount structure needed by test_fs_basic tests. */ +struct fs_mount_t *fs_dirops_test_mp = &testfs_mnt; + +ZTEST(ext2tests, test_dirops) +{ + struct fs_mount_t *mp = &testfs_mnt; + struct fs_dirent de; + + zassert_equal(fs_mount(mp), 0, "Mount failed"); + + /* 'lost+found' directory is created automatically. test_fs_dirops expects empty root + * directory hence we have to remove it before starting test. + */ + if (fs_stat("/sml/lost+found", &de) == 0) { + zassert_equal(fs_unlink("/sml/lost+found"), 0, "unlink failed"); + } + zassert_equal(fs_unmount(mp), 0, "Unount failed"); + + /* Common dirops tests. + * (File system is mounted and unmounted during that test.) + */ + test_fs_dirops(); +} diff --git a/tests/subsys/fs/ext2/src/testfs_dirops.c b/tests/subsys/fs/ext2/src/testfs_dirops.c new file mode 100644 index 0000000000000..f55cc46bce147 --- /dev/null +++ b/tests/subsys/fs/ext2/src/testfs_dirops.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "utils.h" + +ZTEST(ext2tests, test_dirops_basic) +{ + struct fs_mount_t *mp = &testfs_mnt; + + zassert_equal(fs_mount(mp), 0, "Mount failed"); + + struct fs_file_t file; + struct fs_dirent stat; + + fs_file_t_init(&file); + + /* Create some directories */ + zassert_equal(fs_mkdir("/sml/dir1"), 0, "Create dir1 failed"); + zassert_equal(fs_mkdir("/sml/dir2"), 0, "Create dir2 failed"); + + /* Create some files */ + zassert_equal(fs_open(&file, "/sml/file1", FS_O_CREATE), 0, "Create file1 failed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + zassert_equal(fs_open(&file, "/sml/dir1/file2", FS_O_CREATE), 0, "Create file2 failed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + zassert_equal(fs_open(&file, "/sml/dir2/file3", FS_O_CREATE), 0, "Create file3 failed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + + /* Check if directories will open as files */ + zassert_equal(fs_open(&file, "/sml/dir1", 0), -EINVAL, "Should return error"); + zassert_equal(fs_open(&file, "/sml/dir2", 0), -EINVAL, "Should return error"); + + /* Check directories with stat */ + zassert_equal(fs_stat("/sml/dir1", &stat), 0, "Stat dir1 failed"); + zassert_equal(stat.type, FS_DIR_ENTRY_DIR, "Wrong type"); + zassert_equal(stat.size, 0, "Wrong directory size"); + zassert_mem_equal(stat.name, "dir1", 5, "Wrong directory name"); + + zassert_equal(fs_stat("/sml/dir2", &stat), 0, "Stat dir2 failed"); + zassert_equal(stat.type, FS_DIR_ENTRY_DIR, "Wrong type"); + zassert_equal(stat.size, 0, "Wrong directory size"); + zassert_mem_equal(stat.name, "dir2", 5, "Wrong directory name"); + + /* Check if files will open correctly */ + zassert_equal(fs_open(&file, "/sml/file1", 0), 0, "Open file1 should succeed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + zassert_equal(fs_open(&file, "/sml/dir1/file2", 0), 0, "Open file2 should succeed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + zassert_equal(fs_open(&file, "/sml/dir2/file3", 0), 0, "Open file3 should succeed"); + zassert_equal(fs_close(&file), 0, "Close file error"); + + /* Check files with stat */ + zassert_equal(fs_stat("/sml/file1", &stat), 0, "Stat file1 failed"); + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, "Wrong type"); + zassert_equal(stat.size, 0, "Wrong file size"); + zassert_mem_equal(stat.name, "file1", 6, "Wrong file name"); + + zassert_equal(fs_stat("/sml/dir1/file2", &stat), 0, "Stat file1 failed"); + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, "Wrong type"); + zassert_equal(stat.size, 0, "Wrong file size"); + zassert_mem_equal(stat.name, "file2", 6, "Wrong file name"); + + zassert_equal(fs_stat("/sml/dir2/file3", &stat), 0, "Stat file1 failed"); + zassert_equal(stat.type, FS_DIR_ENTRY_FILE, "Wrong type"); + zassert_equal(stat.size, 0, "Wrong file size"); + zassert_mem_equal(stat.name, "file3", 6, "Wrong file name"); + + /* Check for some nonexisting files */ + zassert_equal(fs_open(&file, "/sml/file2", 0), -ENOENT, "Should not exist"); + zassert_equal(fs_open(&file, "/sml/file3", 0), -ENOENT, "Should not exist"); + zassert_equal(fs_open(&file, "/sml/dir1/file1", 0), -ENOENT, "Should not exist"); + zassert_equal(fs_open(&file, "/sml/dir1/file3", 0), -ENOENT, "Should not exist"); + zassert_equal(fs_open(&file, "/sml/dir2/file1", 0), -ENOENT, "Should not exist"); + zassert_equal(fs_open(&file, "/sml/dir2/file2", 0), -ENOENT, "Should not exist"); + + zassert_equal(fs_unmount(mp), 0, "Unmount failed"); +} diff --git a/tests/subsys/fs/ext2/src/testfs_ext_specific.c b/tests/subsys/fs/ext2/src/testfs_ext_specific.c new file mode 100644 index 0000000000000..63367d7272e00 --- /dev/null +++ b/tests/subsys/fs/ext2/src/testfs_ext_specific.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "utils.h" +#include "../../common/test_fs_util.h" + +uint32_t calculate_blocks(uint32_t freeb, uint32_t B) +{ + uint32_t blocks = 0; + + if (freeb <= 12) { + return freeb; + } + + blocks += 12; + freeb -= 12 + 1; /* direct blocks + top block of first level table */ + + if (freeb <= B) { + return blocks + freeb; + } + + blocks += B; + freeb -= B + 1; /* 1st level blocks + top block of second level table */ + + if (freeb <= B * (B + 1)) { + uint32_t n = freeb / (B + 1); + uint32_t r = freeb % (B + 1); + uint32_t partial = r > 0 ? 1 : 0; + + return blocks + n * B + r - partial; + } + return blocks; + /* TODO: revisit this and extend when 3rd level blocks will be possible */ +} + +void writing_test(struct ext2_cfg *config) +{ + int64_t ret = 0; + struct fs_file_t file; + struct fs_statvfs sbuf; + struct fs_dirent entry; + struct fs_mount_t *mp = &testfs_mnt; + static const char *file_path = "/sml/file"; + + ret = fs_mkfs(FS_EXT2, (uintptr_t)mp->storage_dev, config, 0); + zassert_equal(ret, 0, "Failed to mkfs with 2K blocks"); + + mp->flags = FS_MOUNT_FLAG_NO_FORMAT; + ret = fs_mount(mp); + zassert_equal(ret, 0, "Mount failed (ret=%d)", ret); + + fs_file_t_init(&file); + ret = fs_open(&file, file_path, FS_O_RDWR | FS_O_CREATE); + zassert_equal(ret, 0, "File open failed (ret=%d)", ret); + + ret = fs_statvfs(mp->mnt_point, &sbuf); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + + + /* Calculate how many numbers will be written (use all available memory) */ + uint32_t freeb = sbuf.f_bfree; + uint32_t bsize = sbuf.f_bsize; + uint32_t available_blocks = calculate_blocks(freeb, bsize / sizeof(uint32_t)); + + uint32_t bytes_to_write = bsize * available_blocks; + + TC_PRINT("Available blocks: %d\nBlock size: %d\nBytes_to_write: %d\n", + available_blocks, bsize, bytes_to_write); + + ret = testfs_write_incrementing(&file, 0, available_blocks * bsize); + zassert_equal(ret, bytes_to_write, "Different number of bytes written %ld (expected %ld)", + ret, bytes_to_write); + + ret = fs_close(&file); + zassert_equal(ret, 0, "File close failed (ret=%d)", ret); + + + /* Check file size */ + ret = fs_stat(file_path, &entry); + zassert_equal(ret, 0, "File stat failed (ret=%d)", ret); + zassert_equal(entry.size, bytes_to_write, + "Wrong file size %d (expected %d)", entry.size, + bytes_to_write); + + fs_file_t_init(&file); + ret = fs_open(&file, file_path, FS_O_READ); + zassert_equal(ret, 0, "File open failed (ret=%d)", ret); + + + ret = testfs_verify_incrementing(&file, 0, available_blocks * bsize); + zassert_equal(ret, bytes_to_write, "Different number of bytes read %ld (expected %ld)", + ret, bytes_to_write); + + ret = fs_close(&file); + zassert_equal(ret, 0, "File close failed (ret=%d)", ret); + + uint32_t new_size = bytes_to_write; + + while (new_size > 1) { + new_size = new_size / 8 * 3; + + TC_PRINT("Truncating to %d\n", new_size); + + ret = fs_open(&file, file_path, FS_O_RDWR); + zassert_equal(ret, 0, "File open failed (ret=%d)", ret); + + ret = fs_truncate(&file, new_size); + zassert_equal(ret, 0, "File truncate failed (ret=%d)", ret); + + ret = fs_stat(file_path, &entry); + zassert_equal(ret, 0, "File stat failed (ret=%d)", ret); + zassert_equal(entry.size, new_size, + "Wrong file size %d (expected %d)", entry.size, new_size); + + ret = fs_seek(&file, 0, FS_SEEK_SET); + zassert_equal(ret, 0, "File seek failed (ret=%d)", ret); + + ret = testfs_verify_incrementing(&file, 0, new_size); + zassert_equal(ret, new_size, "Different number of bytes read %ld (expected %ld)", + ret, new_size); + + ret = fs_close(&file); + zassert_equal(ret, 0, "File close failed (ret=%d)", ret); + } + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Unmount failed (ret=%d)", ret); +} + +ZTEST(ext2tests, test_write_big_file) +{ + writing_test(NULL); +} + +#if defined(CONFIG_APP_TEST_BIG) +ZTEST(ext2tests, test_write_big_file_2K) +{ + struct ext2_cfg config = { + .block_size = 2048, + .fs_size = 0x2000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + writing_test(&config); +} + +ZTEST(ext2tests, test_write_big_file_4K) +{ + struct ext2_cfg config = { + .block_size = 4096, + .fs_size = 0x8000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + writing_test(&config); +} +#endif diff --git a/tests/subsys/fs/ext2/src/testfs_mount.c b/tests/subsys/fs/ext2/src/testfs_mount.c new file mode 100644 index 0000000000000..0b396bb669f7a --- /dev/null +++ b/tests/subsys/fs/ext2/src/testfs_mount.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "utils.h" + +ZTEST(ext2tests, test_mount_only) +{ + struct fs_mount_t *mp = &testfs_mnt; + int ret = 0; + + /* Test FS_MOUNT_FLAG_NO_FORMAT flag */ + mp->flags |= FS_MOUNT_FLAG_NO_FORMAT; + ret = fs_mount(mp); + TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_NO_FORMAT set\n"); + zassert_false(ret == 0, "Expected failure (ret=%d)", ret); + + /* Test FS_MOUNT_FLAG_READ_ONLY on non-formatted volume*/ + mp->flags = FS_MOUNT_FLAG_READ_ONLY; + ret = fs_mount(mp); + TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_READ_ONLY set\n"); + zassert_false(ret == 0, "Expected failure (ret=%d)", ret); + + /* Format volume and add some files/dirs to check read-only flag */ + mp->flags = 0; + ret = fs_mount(mp); + TC_PRINT("Mount again to format volume\n"); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + TC_PRINT("Create some file\n"); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + + /* Check fs operation on volume mounted with FS_MOUNT_FLAG_READ_ONLY */ + mp->flags = FS_MOUNT_FLAG_READ_ONLY; + TC_PRINT("Mount as read-only\n"); + ret = fs_mount(mp); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); +} + +ZTEST(ext2tests, test_statvfs) +{ + int ret = 0; + struct fs_statvfs sbuf; + struct fs_mount_t *mp = &testfs_mnt; + size_t partition_size = MIN(0x800000, get_partition_size((uintptr_t)mp->storage_dev)); + + mp->flags = 0; + ret = fs_mount(mp); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + + ret = fs_statvfs(mp->mnt_point, &sbuf); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); + + TC_PRINT("Mounted file system: bsize:%lu frsize:%lu blocks:%lu, bfree:%lu\n", + sbuf.f_bsize, sbuf.f_frsize, sbuf.f_blocks, sbuf.f_bfree); + + zassert_equal(sbuf.f_bsize, 1024, + "Wrong block size %lu (expected %lu)", sbuf.f_bsize, 1024); + zassert_equal(sbuf.f_frsize, 1024, + "Wrong frag size %lu (expected %lu)", sbuf.f_frsize, 1024); + zassert_equal(sbuf.f_blocks, partition_size / 1024, + "Wrong block count %lu (expected %lu)", + sbuf.f_blocks, partition_size / 1024); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Expected success (ret=%d)", ret); +} + +/* Tests from common directory */ + +void test_fs_mkfs_simple(void); + +/* Global variables expected by tests */ +struct fs_mount_t *fs_mkfs_mp = &testfs_mnt; +const int fs_mkfs_type = FS_EXT2; +uintptr_t fs_mkfs_dev_id; +int fs_mkfs_flags; + +ZTEST(ext2tests, test_mkfs_simple) +{ + struct fs_mount_t *mp = &testfs_mnt; + + fs_mkfs_dev_id = (uintptr_t) mp->storage_dev; + fs_mkfs_flags = 0; + test_fs_mkfs_simple(); +} + +void mkfs_custom_config(struct ext2_cfg *cfg) +{ + int ret = 0; + struct fs_statvfs sbuf; + struct fs_mount_t *mp = &testfs_mnt; + size_t partition_size = MIN(cfg->fs_size, get_partition_size((uintptr_t)mp->storage_dev)); + + ret = fs_mkfs(FS_EXT2, (uintptr_t)mp->storage_dev, cfg, 0); + zassert_equal(ret, 0, "Failed to mkfs with 2K blocks"); + + mp->flags = FS_MOUNT_FLAG_NO_FORMAT; + ret = fs_mount(mp); + zassert_equal(ret, 0, "Mount failed (ret=%d)", ret); + + ret = fs_statvfs(mp->mnt_point, &sbuf); + zassert_equal(ret, 0, "Statvfs failed (ret=%d)", ret); + + TC_PRINT("Mounted file system: bsize:%lu frsize:%lu blocks:%lu, bfree:%lu\n", + sbuf.f_bsize, sbuf.f_frsize, sbuf.f_blocks, sbuf.f_bfree); + + zassert_equal(sbuf.f_bsize, cfg->block_size, + "Wrong block size %lu (expected %lu)", sbuf.f_bsize, cfg->block_size); + zassert_equal(sbuf.f_frsize, cfg->block_size, + "Wrong frag size %lu (expected %lu)", sbuf.f_frsize, cfg->block_size); + zassert_equal(sbuf.f_blocks, partition_size / cfg->block_size, + "Wrong block count %lu (expected %lu)", + sbuf.f_blocks, partition_size / cfg->block_size); + + ret = fs_unmount(mp); + zassert_equal(ret, 0, "Unmount failed (ret=%d)", ret); +} + +#if defined(CONFIG_APP_TEST_BIG) +ZTEST(ext2tests, test_mkfs_custom_2K) +{ + struct ext2_cfg config = { + .block_size = 2048, + .fs_size = 0x2000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + mkfs_custom_config(&config); +} + +ZTEST(ext2tests, test_mkfs_custom_4K) +{ + struct ext2_cfg config = { + .block_size = 4096, + .fs_size = 0x8000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + mkfs_custom_config(&config); +} +#endif diff --git a/tests/subsys/fs/ext2/src/testfs_mount_flags.c b/tests/subsys/fs/ext2/src/testfs_mount_flags.c new file mode 100644 index 0000000000000..a420d49167375 --- /dev/null +++ b/tests/subsys/fs/ext2/src/testfs_mount_flags.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "utils.h" + +void test_fs_mount_flags(void); + +/* Global variable expected by tests */ +struct fs_mount_t *mount_flags_mp = &testfs_mnt; + +ZTEST(ext2tests, test_mount_flags) +{ + test_fs_mount_flags(); +} diff --git a/tests/subsys/fs/ext2/src/testfs_open_flags.c b/tests/subsys/fs/ext2/src/testfs_open_flags.c new file mode 100644 index 0000000000000..bf4273968ae7d --- /dev/null +++ b/tests/subsys/fs/ext2/src/testfs_open_flags.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "utils.h" + +void test_fs_open_flags(void); + +/* Expected by test_fs_open_flags() */ +const char *test_fs_open_flags_file_path = "/sml/open_flags_file"; + +ZTEST(ext2tests, test_open_flags) +{ + struct fs_mount_t *mp = &testfs_mnt; + + zassert_equal(fs_mount(mp), 0, "Failed to mount partition"); + + test_fs_open_flags(); + + zassert_equal(fs_unmount(mp), 0, "Failed to unmount partition"); +} + +ZTEST(ext2tests, test_open_flags_2K) +{ + int ret = 0; + struct fs_mount_t *mp = &testfs_mnt; + struct ext2_cfg ext2_config = { + .block_size = 2048, + .fs_size = 0x2000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + ret = fs_mkfs(FS_EXT2, (uintptr_t)mp->storage_dev, &ext2_config, 0); + zassert_equal(ret, 0, "Failed to mkfs with 2K blocks"); + + mp->flags = FS_MOUNT_FLAG_NO_FORMAT; + zassert_equal(fs_mount(mp), 0, "Failed to mount partition"); + + test_fs_open_flags(); + + zassert_equal(fs_unmount(mp), 0, "Failed to unmount partition"); +} + +#if defined(CONFIG_APP_TEST_BIG) +ZTEST(ext2tests, test_open_flags_4K) +{ + int ret = 0; + struct fs_mount_t *mp = &testfs_mnt; + struct ext2_cfg ext2_config = { + .block_size = 4096, + .fs_size = 0x8000000, + .bytes_per_inode = 0, + .volume_name[0] = 0, + .set_uuid = false, + }; + + ret = fs_mkfs(FS_EXT2, (uintptr_t)mp->storage_dev, &ext2_config, 0); + zassert_equal(ret, 0, "Failed to mkfs with 2K blocks"); + + mp->flags = FS_MOUNT_FLAG_NO_FORMAT; + zassert_equal(fs_mount(mp), 0, "Failed to mount partition"); + + test_fs_open_flags(); + + zassert_equal(fs_unmount(mp), 0, "Failed to unmount partition"); +} +#endif diff --git a/tests/subsys/fs/ext2/src/utils.c b/tests/subsys/fs/ext2/src/utils.c new file mode 100644 index 0000000000000..9a6afd89022c2 --- /dev/null +++ b/tests/subsys/fs/ext2/src/utils.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "utils.h" + +static int sectors_info(const char *disk, uint32_t *ss, uint32_t *sc) +{ + int rc; + + rc = disk_access_ioctl(disk, DISK_IOCTL_GET_SECTOR_COUNT, sc); + if (rc < 0) { + TC_PRINT("Disk access (sector count) error: %d", rc); + return rc; + } + + rc = disk_access_ioctl(disk, DISK_IOCTL_GET_SECTOR_SIZE, ss); + if (rc < 0) { + TC_PRINT("Disk access (sector size) error: %d", rc); + return rc; + } + + return 0; +} + +int wipe_partition(uintptr_t id) +{ + int rc; + const char *name = (const char *)id; + uint32_t sector_count, sector_size; + + TC_PRINT("Wiping %s\n", name); + + rc = disk_access_init(name); + if (rc < 0) { + return rc; + } + + rc = sectors_info(name, §or_size, §or_count); + if (rc < 0) { + return rc; + } + + uint8_t zeros[sector_size]; + + memset(zeros, 0, sector_size); + + /* Superblock is located at offset 1024B and has size 1024B */ + uint32_t start_sector = CONFIG_EXT2_DISK_STARTING_SECTOR + 1024 / sector_size; + uint32_t num_sectors = 1024 / sector_size + (1024 % sector_size != 0); + + for (uint32_t i = 0; i < num_sectors; i++) { + rc = disk_access_write(name, zeros, start_sector + i, 1); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +size_t get_partition_size(uintptr_t id) +{ + const char *name = (const char *)id; + uint32_t sector_count, sector_size; + + sectors_info(name, §or_size, §or_count); + + return sector_size * sector_count; +} diff --git a/tests/subsys/fs/ext2/src/utils.h b/tests/subsys/fs/ext2/src/utils.h new file mode 100644 index 0000000000000..3f1577d1753d2 --- /dev/null +++ b/tests/subsys/fs/ext2/src/utils.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023 Antmicro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __TESTS_FS_EXT2_UTILS_H__ +#define __TESTS_FS_EXT2_UTILS_H__ + +extern struct fs_mount_t testfs_mnt; + +int wipe_partition(uintptr_t id); +size_t get_partition_size(uintptr_t id); + +#endif /* __TESTS_FS_EXT2_UTILS_H__ */ diff --git a/tests/subsys/fs/ext2/testcase.yaml b/tests/subsys/fs/ext2/testcase.yaml new file mode 100644 index 0000000000000..14654c83d0aaa --- /dev/null +++ b/tests/subsys/fs/ext2/testcase.yaml @@ -0,0 +1,17 @@ +common: + tags: filesystem +tests: + filesystem.ext2.default: + platform_allow: native_posix native_posix_64 hifive_unmatched bl5340_dvk_cpuapp + + filesystem.ext2.big: + platform_allow: native_posix native_posix_64 + extra_args: CONF_FILE=prj_big.conf + + filesystem.ext2.sdcard: + platform_allow: hifive_unmatched bl5340_dvk_cpuapp + extra_args: CONF_FILE=prj_sdcard.conf + + filesystem.ext2.flash: + platform_allow: native_posix native_posix_64 + extra_args: CONF_FILE=prj_flash.conf diff --git a/tests/subsys/fs/littlefs/CMakeLists.txt b/tests/subsys/fs/littlefs/CMakeLists.txt index 26fdcb35c6d68..5be4e69837535 100644 --- a/tests/subsys/fs/littlefs/CMakeLists.txt +++ b/tests/subsys/fs/littlefs/CMakeLists.txt @@ -14,5 +14,10 @@ target_compile_definitions(app PRIVATE ) target_sources(app PRIVATE ${app_sources} + ../common/test_fs_util.c ../common/test_fs_open_flags.c - ../common/test_fs_mkfs.c) + ../common/test_fs_dirops.c + ../common/test_fs_basic.c + ../common/test_fs_mount_flags.c + ../common/test_fs_mkfs.c +) diff --git a/tests/subsys/fs/littlefs/src/main.c b/tests/subsys/fs/littlefs/src/main.c index f64047eabebaa..86af2e005bfbf 100644 --- a/tests/subsys/fs/littlefs/src/main.c +++ b/tests/subsys/fs/littlefs/src/main.c @@ -4,12 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* Tests of functions in testfs_util */ - #include #include #include "testfs_tests.h" #include "testfs_lfs.h" - ZTEST_SUITE(littlefs, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/fs/littlefs/src/test_lfs_basic.c b/tests/subsys/fs/littlefs/src/test_lfs_basic.c index c15c31ce090c5..9c4e05449677a 100644 --- a/tests/subsys/fs/littlefs/src/test_lfs_basic.c +++ b/tests/subsys/fs/littlefs/src/test_lfs_basic.c @@ -24,9 +24,6 @@ #include -#define HELLO "hello" -#define GOODBYE "goodbye" - static int mount(struct fs_mount_t *mp) { TC_PRINT("mounting %s\n", mp->mnt_point); @@ -72,355 +69,6 @@ static int clean_statvfs(const struct fs_mount_t *mp) return TC_PASS; } -static int create_write_hello(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("creating and writing file\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - HELLO, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "open hello failed"); - - struct fs_dirent stat; - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat new hello failed"); - - zassert_equal(stat.type, FS_DIR_ENTRY_FILE, - "stat new hello not file"); - zassert_equal(strcmp(stat.name, HELLO), 0, - "stat new hello not hello"); - zassert_equal(stat.size, 0, - "stat new hello not empty"); - - zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE), - TESTFS_BUFFER_SIZE, - "write constant failed"); - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat written hello failed"); - - zassert_equal(stat.type, FS_DIR_ENTRY_FILE, - "stat written hello not file"); - zassert_equal(strcmp(stat.name, HELLO), 0, - "stat written hello not hello"); - - /* Anomalous behavior requiring upstream response */ - if (true) { - /* VARIATION POINT: littlefs does not update the file size of - * an open file. See upstream issue #250. - */ - zassert_equal(stat.size, 0, - "stat written hello bad size"); - } - - zassert_equal(fs_close(&file), 0, - "close hello failed"); - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat closed hello failed"); - - zassert_equal(stat.type, FS_DIR_ENTRY_FILE, - "stat closed hello not file"); - zassert_equal(strcmp(stat.name, HELLO), 0, - "stat closed hello not hello"); - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat closed hello badsize"); - - return TC_PASS; -} - -static int verify_hello(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("opening and verifying file\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - HELLO, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "verify hello open failed"); - - zassert_equal(fs_tell(&file), 0U, - "verify hello open tell failed"); - - zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE), - TESTFS_BUFFER_SIZE, - "verify hello at start failed"); - - zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, - "verify hello read tell failed"); - - zassert_equal(fs_close(&file), 0, - "verify close hello failed"); - - return TC_PASS; -} - -static int seek_within_hello(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("seek and tell in file\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - HELLO, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "verify hello open failed"); - - zassert_equal(fs_tell(&file), 0U, - "verify hello open tell failed"); - - struct fs_dirent stat; - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat old hello failed"); - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat old hello bad size"); - - off_t pos = stat.size / 4; - - zassert_equal(fs_seek(&file, pos, FS_SEEK_SET), - 0, - "verify hello seek near mid failed"); - - zassert_equal(fs_tell(&file), pos, - "verify hello tell near mid failed"); - - zassert_equal(testfs_verify_incrementing(&file, pos, TESTFS_BUFFER_SIZE), - TESTFS_BUFFER_SIZE - pos, - "verify hello at middle failed"); - - zassert_equal(fs_tell(&file), stat.size, - "verify hello read middle tell failed"); - - zassert_equal(fs_seek(&file, -stat.size, FS_SEEK_CUR), - 0, - "verify hello seek back from cur failed"); - - zassert_equal(fs_tell(&file), 0U, - "verify hello tell back from cur failed"); - - zassert_equal(fs_seek(&file, -pos, FS_SEEK_END), - 0, - "verify hello seek from end failed"); - - zassert_equal(fs_tell(&file), stat.size - pos, - "verify hello tell from end failed"); - - zassert_equal(testfs_verify_incrementing(&file, stat.size - pos, - TESTFS_BUFFER_SIZE), - pos, - "verify hello at post middle failed"); - - zassert_equal(fs_close(&file), 0, - "verify close hello failed"); - - return TC_PASS; -} - -static int truncate_hello(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("truncate in file\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - HELLO, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "verify hello open failed"); - - struct fs_dirent stat; - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat old hello failed"); - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat old hello bad size"); - - off_t pos = 3 * stat.size / 4; - - zassert_equal(fs_tell(&file), 0U, - "truncate initial tell failed"); - - zassert_equal(fs_truncate(&file, pos), - 0, - "truncate 3/4 failed"); - - zassert_equal(fs_tell(&file), 0U, - "truncate post tell failed"); - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat open 3/4 failed"); - - /* Anomalous behavior requiring upstream response */ - if (true) { - /* VARIATION POINT: littlefs does not update the file size of - * an open file. See upstream issue #250. - */ - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat open 3/4 bad size"); - } - - zassert_equal(testfs_verify_incrementing(&file, 0, 64), - 48, - "post truncate content unexpected"); - - zassert_equal(fs_close(&file), 0, - "post truncate close failed"); - - /* After close size is correct. */ - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat closed truncated failed"); - zassert_equal(stat.size, pos, - "stat closed truncated bad size"); - - return TC_PASS; -} - -static int unlink_hello(const struct fs_mount_t *mp) -{ - struct testfs_path path; - - TC_PRINT("unlink hello\n"); - - testfs_path_init(&path, mp, - HELLO, - TESTFS_PATH_END); - - struct fs_dirent stat; - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat existing hello failed"); - zassert_equal(fs_unlink(path.path), - 0, - "unlink hello failed"); - zassert_equal(fs_stat(path.path, &stat), - -ENOENT, - "stat existing hello failed"); - - return TC_PASS; -} - -static int sync_goodbye(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("sync goodbye\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - GOODBYE, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "sync goodbye failed"); - - struct fs_dirent stat; - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat existing hello failed"); - zassert_equal(stat.size, 0, - "stat new goodbye not empty"); - - zassert_equal(testfs_write_incrementing(&file, 0, TESTFS_BUFFER_SIZE), - TESTFS_BUFFER_SIZE, - "write goodbye failed"); - - zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, - "tell goodbye failed"); - - if (true) { - /* Upstream issue #250 */ - zassert_equal(stat.size, 0, - "stat new goodbye not empty"); - } - - zassert_equal(fs_sync(&file), 0, - "sync goodbye failed"); - - zassert_equal(fs_tell(&file), TESTFS_BUFFER_SIZE, - "tell synced moved"); - - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat existing hello failed"); - printk("sync size %u\n", (uint32_t)stat.size); - - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat synced goodbye not correct"); - - zassert_equal(fs_close(&file), 0, - "post sync close failed"); - - /* After close size is correct. */ - zassert_equal(fs_stat(path.path, &stat), - 0, - "stat sync failed"); - zassert_equal(stat.size, TESTFS_BUFFER_SIZE, - "stat sync bad size"); - - return TC_PASS; -} - -static int verify_goodbye(const struct fs_mount_t *mp) -{ - struct testfs_path path; - struct fs_file_t file; - - fs_file_t_init(&file); - TC_PRINT("verify goodbye\n"); - - zassert_equal(fs_open(&file, - testfs_path_init(&path, mp, - GOODBYE, - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "verify goodbye failed"); - - zassert_equal(testfs_verify_incrementing(&file, 0, TESTFS_BUFFER_SIZE), - TESTFS_BUFFER_SIZE, - "write goodbye failed"); - - zassert_equal(fs_close(&file), 0, - "post sync close failed"); - - return TC_PASS; -} - static int check_medium(void) { struct fs_mount_t *mp = &testfs_medium_mnt; @@ -585,6 +233,11 @@ static int num_dirs(struct fs_mount_t *mp) return TC_PASS; } +void test_fs_basic(void); + +/* Mount structure needed by test_fs_basic tests. */ +struct fs_mount_t *fs_basic_test_mp = &testfs_small_mnt; + ZTEST(littlefs, test_lfs_basic) { struct fs_mount_t *mp = &testfs_small_mnt; @@ -592,30 +245,19 @@ ZTEST(littlefs, test_lfs_basic) zassert_equal(clear_partition(mp), TC_PASS, "clear partition failed"); + /* Common basic tests. + * (File system is mounted and unmounted during that test.) + */ + test_fs_basic(); + + /* LittleFS specific tests */ + zassert_equal(mount(mp), TC_PASS, "clean mount failed"); zassert_equal(clean_statvfs(mp), TC_PASS, "clean statvfs failed"); - zassert_equal(create_write_hello(mp), TC_PASS, - "write hello failed"); - - zassert_equal(verify_hello(mp), TC_PASS, - "verify hello failed"); - - zassert_equal(seek_within_hello(mp), TC_PASS, - "seek within hello failed"); - - zassert_equal(truncate_hello(mp), TC_PASS, - "truncate hello failed"); - - zassert_equal(unlink_hello(mp), TC_PASS, - "unlink hello failed"); - - zassert_equal(sync_goodbye(mp), TC_PASS, - "sync goodbye failed"); - zassert_equal(num_files(mp), TC_PASS, "num_files failed"); @@ -626,20 +268,6 @@ ZTEST(littlefs, test_lfs_basic) zassert_equal(fs_unmount(mp), 0, "unmount small failed"); - k_sleep(K_MSEC(100)); /* flush log messages */ - TC_PRINT("checking double unmount diagnoses\n"); - zassert_equal(fs_unmount(mp), -EINVAL, - "unmount unmounted failed"); - - zassert_equal(mount(mp), TC_PASS, - "remount failed"); - - zassert_equal(verify_goodbye(mp), TC_PASS, - "verify goodbye failed"); - - zassert_equal(fs_unmount(mp), 0, - "unmount2 small failed"); - if (IS_ENABLED(CONFIG_APP_TEST_CUSTOM)) { zassert_equal(check_medium(), TC_PASS, "check medium failed"); diff --git a/tests/subsys/fs/littlefs/src/test_lfs_dirops.c b/tests/subsys/fs/littlefs/src/test_lfs_dirops.c index 81e67ea88093a..39c7789967e2c 100644 --- a/tests/subsys/fs/littlefs/src/test_lfs_dirops.c +++ b/tests/subsys/fs/littlefs/src/test_lfs_dirops.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* Director littlefs operations: +/* Directory littlefs operations: * * mkdir * * opendir * * readdir @@ -20,318 +20,21 @@ #include -static struct testfs_bcmd test_hierarchy[] = { - TESTFS_BCMD_FILE("f1", 1, 1), - TESTFS_BCMD_FILE("f2", 2, 100), - TESTFS_BCMD_ENTER_DIR("d1"), - TESTFS_BCMD_FILE("d1f1", 11, 23), - TESTFS_BCMD_FILE("d1f2", 12, 612), - TESTFS_BCMD_EXIT_DIR(), - TESTFS_BCMD_FILE("f3", 3, 10000), - TESTFS_BCMD_END(), -}; +void test_fs_dirops(void); -static int clean_mount(struct fs_mount_t *mp) -{ - TC_PRINT("checking clean mount\n"); - - zassert_equal(testfs_lfs_wipe_partition(mp), - TC_PASS, - "failed to wipe partition"); - zassert_equal(fs_mount(mp), 0, - "mount small failed"); - - return TC_PASS; -} - -static int check_mkdir(struct fs_mount_t *mp) -{ - struct testfs_path dpath; - - TC_PRINT("checking dir create unlink\n"); - zassert_equal(testfs_path_init(&dpath, mp, - "dir", - TESTFS_PATH_END), - dpath.path, - "root init failed"); - - zassert_equal(fs_mkdir(dpath.path), - 0, - "mkdir failed"); - - struct fs_file_t file; - struct testfs_path fpath; - - fs_file_t_init(&file); - zassert_equal(fs_open(&file, - testfs_path_extend(testfs_path_copy(&fpath, - &dpath), - "file", - TESTFS_PATH_END), - FS_O_CREATE | FS_O_RDWR), - 0, - "creat in dir failed"); - zassert_equal(fs_close(&file), 0, - "close file failed"); - - struct fs_dirent stat; - - zassert_equal(fs_stat(fpath.path, &stat), 0, - "stat file failed"); - - zassert_equal(fs_unlink(dpath.path), - -ENOTEMPTY, - "unlink bad failure"); - - zassert_equal(fs_unlink(fpath.path), - 0, - "unlink file failed"); - - zassert_equal(fs_unlink(dpath.path), - 0, - "unlink dir failed"); - - return TC_PASS; -} - -static int build_layout(struct fs_mount_t *mp, - const struct testfs_bcmd *cp) -{ - struct testfs_path path; - struct fs_statvfs stat; - - TC_PRINT("building layout on %s\n", mp->mnt_point); - - zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, - "statvfs failed"); - - TC_PRINT("before: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", - stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); - - zassert_equal(testfs_path_init(&path, mp, TESTFS_PATH_END), - path.path, - "root init failed"); - - zassert_equal(testfs_build(&path, cp), - 0, - "build_layout failed"); - - zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, - "statvfs failed"); - - TC_PRINT("after: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", - stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); - - return TC_PASS; -} - -static int check_layout(struct fs_mount_t *mp, - struct testfs_bcmd *layout) -{ - struct testfs_path path; - struct testfs_bcmd *end_layout = testfs_bcmd_end(layout); - - TC_PRINT("checking layout\n"); - - zassert_equal(testfs_path_init(&path, mp, TESTFS_PATH_END), - path.path, - "root init failed"); - - int rc = testfs_bcmd_verify_layout(&path, layout, end_layout); - - zassert_true(rc >= 0, "layout check failed"); - - zassert_equal(rc, 0, - "layout found foreign"); - - struct testfs_bcmd *cp = layout; - - while (!TESTFS_BCMD_IS_END(cp)) { - if (cp->name != NULL) { - TC_PRINT("verifying %s%s %u\n", - cp->name, - (cp->type == FS_DIR_ENTRY_DIR) ? "/" : "", - cp->size); - zassert_true(cp->matched, - "Unmatched layout entry"); - } - ++cp; - } - - return TC_PASS; -} - -static int check_rename(struct fs_mount_t *mp) -{ - struct testfs_path root; - struct testfs_path from_path; - const char *from = from_path.path; - struct testfs_path to_path; - const char *to = to_path.path; - struct fs_dirent stat; - struct testfs_bcmd from_bcmd[] = { - TESTFS_BCMD_FILE("f1f", 1, 8), /* from f1f to f1t */ - TESTFS_BCMD_FILE("f2f", 2, 8), /* from f2f to f2t */ - TESTFS_BCMD_FILE("f2t", 3, 8), /* target for f2f clobber, replaced */ - TESTFS_BCMD_FILE("f3f", 4, 8), /* from f3f to d1f/d1f2t */ - TESTFS_BCMD_FILE("f4f", 5, 8), /* from f4f to d2t, reject */ - TESTFS_BCMD_ENTER_DIR("d1f"), /* from d1f to d1t */ - TESTFS_BCMD_FILE("d1f1", 5, 16), - TESTFS_BCMD_EXIT_DIR(), - TESTFS_BCMD_ENTER_DIR("d2t"), /* target for d1f, unchanged */ - TESTFS_BCMD_FILE("d2f1", 6, 16), - TESTFS_BCMD_EXIT_DIR(), - TESTFS_BCMD_END(), - }; - struct testfs_bcmd *from_end_bcmd = testfs_bcmd_end(from_bcmd); - struct testfs_bcmd to_bcmd[] = { - TESTFS_BCMD_FILE("f1t", 1, 8), /* from f1f to f1t */ - TESTFS_BCMD_FILE("f2t", 2, 8), /* from f2f to f2t */ - TESTFS_BCMD_FILE("f4f", 5, 8), /* from f4f to d2t, reject */ - TESTFS_BCMD_ENTER_DIR("d1t"), /* from d1f to d1t */ - TESTFS_BCMD_FILE("d1f1", 5, 16), - TESTFS_BCMD_FILE("d1f2t", 4, 8), /* from f3f to d1f/d1f2t */ - TESTFS_BCMD_EXIT_DIR(), - TESTFS_BCMD_ENTER_DIR("d2t"), /* target for d1f, unchanged */ - TESTFS_BCMD_FILE("d2f1", 6, 16), - TESTFS_BCMD_EXIT_DIR(), - TESTFS_BCMD_END(), - }; - struct testfs_bcmd *to_end_bcmd = testfs_bcmd_end(to_bcmd); - - zassert_equal(testfs_path_init(&root, mp, - "rename", - TESTFS_PATH_END), - root.path, - "root init failed"); - - zassert_equal(fs_mkdir(root.path), - 0, - "rename mkdir failed"); - zassert_equal(testfs_build(&root, from_bcmd), - 0, - "rename build failed"); - - zassert_equal(testfs_bcmd_verify_layout(&root, from_bcmd, from_end_bcmd), - 0, - "layout check failed"); - - testfs_path_extend(testfs_path_copy(&from_path, &root), - "nofile", - TESTFS_PATH_END); - testfs_path_extend(testfs_path_copy(&to_path, &root), - "f1t", - TESTFS_PATH_END); - TC_PRINT("%s => %s -ENOENT\n", from, to); - zassert_equal(fs_rename(from, to), - -ENOENT, - "rename noent failed"); - - /* f1f => f1t : ok */ - testfs_path_extend(testfs_path_copy(&from_path, &root), - "f1f", - TESTFS_PATH_END); - TC_PRINT("%s => %s ok\n", from, to); - zassert_equal(fs_rename(from, to), - 0, - "rename noent failed"); - - /* f2f => f2t : clobbers */ - testfs_path_extend(testfs_path_copy(&from_path, &root), - "f2f", - TESTFS_PATH_END); - testfs_path_extend(testfs_path_copy(&to_path, &root), - "f2t", - TESTFS_PATH_END); - TC_PRINT("%s => %s clobber ok\n", from, to); - zassert_equal(fs_rename(from, to), - 0, - "rename clobber failed"); - zassert_equal(fs_stat(from, &stat), - -ENOENT, - "rename clobber left from"); - - /* f3f => d1f/d1f2t : moves */ - testfs_path_extend(testfs_path_copy(&from_path, &root), - "f3f", - TESTFS_PATH_END); - testfs_path_extend(testfs_path_copy(&to_path, &root), - "d1f", "d1f2t", - TESTFS_PATH_END); - TC_PRINT("%s => %s move ok\n", from, to); - zassert_equal(fs_rename(from, to), - 0, - "rename to subdir failed"); - zassert_equal(fs_stat(from, &stat), - -ENOENT, - "rename to subdir left from"); - - /* d1f => d2t : reject */ - testfs_path_extend(testfs_path_copy(&from_path, &root), - "d1f", - TESTFS_PATH_END); - testfs_path_extend(testfs_path_copy(&to_path, &root), - "d2t", - TESTFS_PATH_END); - TC_PRINT("%s => %s -ENOTEMPTY\n", from, to); - zassert_equal(fs_rename(from, to), - -ENOTEMPTY, - "rename to existing dir failed"); - - /* d1f => d1t : rename */ - testfs_path_extend(testfs_path_copy(&from_path, &root), - "d1f", - TESTFS_PATH_END); - testfs_path_extend(testfs_path_copy(&to_path, &root), - "d1t", - TESTFS_PATH_END); - TC_PRINT("%s => %s ok\n", from, to); - zassert_equal(fs_rename(from, to), - 0, - "rename to new dir failed"); - zassert_equal(fs_stat(from, &stat), - -ENOENT, - "rename to new dir left from"); - - zassert_equal(testfs_bcmd_verify_layout(&root, to_bcmd, to_end_bcmd), - 0, - "layout verification failed"); - - struct testfs_bcmd *cp = to_bcmd; - - while (cp != to_end_bcmd) { - if (cp->name) { - zassert_true(cp->matched, "foreign file retained"); - } - ++cp; - } - - return TC_PASS; -} +/* Mount structure needed by test_fs_basic tests. */ +struct fs_mount_t *fs_dirops_test_mp = &testfs_small_mnt; ZTEST(littlefs, test_lfs_dirops) { struct fs_mount_t *mp = &testfs_small_mnt; - zassert_equal(clean_mount(mp), TC_PASS, - "clean mount failed"); - - zassert_equal(check_mkdir(mp), TC_PASS, - "check mkdir failed"); - - k_sleep(K_MSEC(100)); /* flush log messages */ - zassert_equal(build_layout(mp, test_hierarchy), TC_PASS, - "build test hierarchy failed"); - - k_sleep(K_MSEC(100)); /* flush log messages */ - zassert_equal(check_layout(mp, test_hierarchy), TC_PASS, - "check test hierarchy failed"); - - k_sleep(K_MSEC(100)); /* flush log messages */ - zassert_equal(check_rename(mp), TC_PASS, - "check rename failed"); + zassert_equal(testfs_lfs_wipe_partition(mp), + TC_PASS, + "failed to wipe partition"); - k_sleep(K_MSEC(100)); /* flush log messages */ - zassert_equal(fs_unmount(mp), 0, - "unmount small failed"); + /* Common dirops tests. + * (File system is mounted and unmounted during that test.) + */ + test_fs_dirops(); } diff --git a/tests/subsys/fs/littlefs/src/test_util.c b/tests/subsys/fs/littlefs/src/test_util.c index 289deeb8161ab..491ad3ff87317 100644 --- a/tests/subsys/fs/littlefs/src/test_util.c +++ b/tests/subsys/fs/littlefs/src/test_util.c @@ -9,7 +9,7 @@ #include #include #include "testfs_tests.h" -#include "testfs_util.h" +#include "../../common/test_fs_util.h" #define MNT "/mnt" #define ELT1 "a" diff --git a/tests/subsys/fs/littlefs/src/testfs_lfs.h b/tests/subsys/fs/littlefs/src/testfs_lfs.h index eee0d952a2741..22ed59fcc3587 100644 --- a/tests/subsys/fs/littlefs/src/testfs_lfs.h +++ b/tests/subsys/fs/littlefs/src/testfs_lfs.h @@ -8,7 +8,7 @@ #define _ZEPHYR_TESTS_SUBSYS_FS_LITTLEFS_TESTFS_LFS_H_ #include -#include "testfs_util.h" +#include "../../common/test_fs_util.h" #define TESTFS_MNT_POINT_SMALL "/sml" #define TESTFS_MNT_POINT_MEDIUM "/med" diff --git a/tests/subsys/fs/littlefs/src/testfs_mount_flags.c b/tests/subsys/fs/littlefs/src/testfs_mount_flags.c index e00a0ae62c157..2ac034a8ed319 100644 --- a/tests/subsys/fs/littlefs/src/testfs_mount_flags.c +++ b/tests/subsys/fs/littlefs/src/testfs_mount_flags.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2022 Antmicro * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +10,12 @@ #include "testfs_tests.h" #include "testfs_lfs.h" +void test_fs_mount_flags(void); +/* Using smallest partition for this tests as they do not write + * a lot of data, basically they just check flags. + */ +struct fs_mount_t *mount_flags_mp = &testfs_small_mnt; + static void cleanup(struct fs_mount_t *mp) { TC_PRINT("Clean %s\n", mp->mnt_point); @@ -17,68 +24,9 @@ static void cleanup(struct fs_mount_t *mp) "Failed to clean partition"); } -ZTEST(littlefs, test_fs_mount_flags) +ZTEST(littlefs, test_fs_mount_flags_lfs) { - /* Using smallest partition for this tests as they do not write - * a lot of data, basically they just check flags. - */ - struct fs_mount_t *mp = &testfs_small_mnt; - int ret = 0; - struct fs_file_t fs; - - fs_file_t_init(&fs); - cleanup(mp); - - /* Test FS_MOUNT_FLAG_NO_FORMAT flag */ - mp->flags |= FS_MOUNT_FLAG_NO_FORMAT; - ret = fs_mount(mp); - TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_NO_FORMAT set\n"); - zassert_false(ret == 0, "Expected failure", ret); - - /* Test FS_MOUNT_FLAG_READ_ONLY on non-formatted volume*/ - mp->flags = FS_MOUNT_FLAG_READ_ONLY; - ret = fs_mount(mp); - TC_PRINT("Mount unformatted with FS_MOUNT_FLAG_READ_ONLY set\n"); - zassert_false(ret == 0, "Expected failure", ret); - - /* Format volume and add some files/dirs to check read-only flag */ - mp->flags = 0; - ret = fs_mount(mp); - TC_PRINT("Mount again to format volume\n"); - zassert_equal(ret, 0, "Expected success", ret); - TC_PRINT("Create some file\n"); - ret = fs_open(&fs, "/sml/some", FS_O_CREATE); - zassert_equal(ret, 0, "Expected success", ret); - fs_close(&fs); - TC_PRINT("Create other directory\n"); - ret = fs_mkdir("/sml/other"); - zassert_equal(ret, 0, "Expected success", ret); - - ret = fs_unmount(mp); - zassert_equal(ret, 0, "Expected success", ret); - - /* Check fs operation on volume mounted with FS_MOUNT_FLAG_READ_ONLY */ - mp->flags = FS_MOUNT_FLAG_READ_ONLY; - TC_PRINT("Mount as read-only\n"); - ret = fs_mount(mp); - zassert_equal(ret, 0, "Expected success", ret); + cleanup(mount_flags_mp); - /* Attempt creating new file */ - ret = fs_open(&fs, "/sml/nosome", FS_O_CREATE); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_mkdir("/sml/another"); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_rename("/sml/some", "/sml/nosome"); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_unlink("/sml/some"); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_open(&fs, "/sml/other", FS_O_CREATE); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_open(&fs, "/sml/some", FS_O_RDWR); - zassert_equal(ret, -EROFS, "Expected EROFS", ret); - ret = fs_open(&fs, "/sml/some", FS_O_READ); - zassert_equal(ret, 0, "Expected success", ret); - fs_close(&fs); - ret = fs_unmount(mp); - zassert_equal(ret, 0, "Expected success", ret); + test_fs_mount_flags(); }