├── .bumpversion.cfg ├── .github ├── FUNDING.yml └── workflows │ ├── esp_upload_component.yml │ └── pr.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── Makefile ├── README.md ├── component.mk ├── example ├── CMakeLists.txt ├── Makefile ├── README.md ├── flash_data │ └── example.txt ├── main │ ├── CMakeLists.txt │ ├── component.mk │ └── demo_esp_littlefs.c ├── partitions_demo_esp_littlefs.csv └── sdkconfig.defaults ├── idf_component.yml ├── image-building-requirements.txt ├── include └── esp_littlefs.h ├── library.json ├── partition_table_unit_test_app.csv ├── project_include.cmake ├── sdkconfig.defaults ├── src ├── esp_littlefs.c ├── lfs_config.c ├── lfs_config.h ├── littlefs_api.h ├── littlefs_esp_part.c └── littlefs_sdmmc.c ├── test ├── CMakeLists.txt ├── component.mk ├── test_benchmark.c ├── test_dir.c ├── test_littlefs.c ├── test_littlefs_common.c ├── test_littlefs_common.h ├── test_littlefs_static_partition.c └── testfs.bin └── testdir ├── pangram.txt ├── test1.txt ├── test2.txt └── test_folder └── lorem_ipsum.txt /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.20.1 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:README.md] 7 | search = littlefs=={current_version} 8 | replace = littlefs=={new_version} 9 | 10 | [bumpversion:file:idf_component.yml] 11 | search = "{current_version}" 12 | replace = "{new_version}" 13 | 14 | [bumpversion:file:library.json] 15 | search = "{current_version}" 16 | replace = "{new_version}" 17 | 18 | [bumpversion:file(number):include/esp_littlefs.h] 19 | search = ESP_LITTLEFS_VERSION_NUMBER "{current_version}" 20 | replace = ESP_LITTLEFS_VERSION_NUMBER "{new_version}" 21 | 22 | [bumpversion:file(major):include/esp_littlefs.h] 23 | parse = (?P\d+) 24 | serialize = {major} 25 | search = ESP_LITTLEFS_VERSION_MAJOR {current_version} 26 | replace = ESP_LITTLEFS_VERSION_MAJOR {new_version} 27 | 28 | [bumpversion:file(minor):include/esp_littlefs.h] 29 | parse = (?P\d+) 30 | serialize = {minor} 31 | search = ESP_LITTLEFS_VERSION_MINOR {current_version} 32 | replace = ESP_LITTLEFS_VERSION_MINOR {new_version} 33 | 34 | [bumpversion:file(patch):include/esp_littlefs.h] 35 | parse = (?P\d+) 36 | serialize = {patch} 37 | search = ESP_LITTLEFS_VERSION_PATCH {current_version} 38 | replace = ESP_LITTLEFS_VERSION_PATCH {new_version} 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [BrianPugh] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/esp_upload_component.yml: -------------------------------------------------------------------------------- 1 | name: Push LittleFS to Espressif Component Service 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | upload_components: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | with: 14 | submodules: recursive 15 | - name: Upload component to component service 16 | uses: espressif/upload-components-ci-action@v1 17 | with: 18 | name: "LittleFS" 19 | namespace: "joltwallet" 20 | api_token: ${{ secrets.ESP_IDF_COMPONENT_API_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Build firmware 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | - cron: "0 1 * * 6" # Every Saturday at 1AM 12 | 13 | jobs: 14 | build-component: 15 | timeout-minutes: 10 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | idf_ver: ["release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "latest"] 20 | idf_target: ["esp32"] 21 | include: 22 | - idf_ver: "release-v5.2" 23 | idf_target: esp32s2 24 | - idf_ver: "release-v5.2" 25 | idf_target: esp32c3 26 | - idf_ver: "release-v5.2" 27 | idf_target: esp32s3 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | submodules: recursive 33 | 34 | - name: Install cppcheck 35 | run: | 36 | sudo apt-get update 37 | sudo apt-get install -y cppcheck 38 | 39 | - name: Run cppcheck 40 | run: | 41 | cppcheck src/*.c --enable=warning --error-exitcode=1 --force 42 | 43 | - name: esp-idf build library 44 | uses: espressif/esp-idf-ci-action@main 45 | with: 46 | esp_idf_version: ${{ matrix.idf_ver }} 47 | target: ${{ matrix.idf_target }} 48 | path: "example/" 49 | command: apt-get update && apt-get install -y python3-venv && idf.py --version && idf.py build 50 | 51 | - name: esp-idf build tests 52 | uses: espressif/esp-idf-ci-action@main 53 | with: 54 | esp_idf_version: ${{ matrix.idf_ver }} 55 | command: '/bin/bash -c " ln -s /app /opt/esp/idf/tools/unit-test-app/components/littlefs; cd /opt/esp/idf/tools/unit-test-app; idf.py -T littlefs build"' 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | sdkconfig 3 | sdkconfig.old 4 | 5 | example/build/ 6 | example/sdkconfig 7 | example/sdkconfig.old 8 | example/dependencies.lock 9 | 10 | *.DS_Store 11 | */.cache 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "main/littlefs"] 2 | path = src/littlefs 3 | url = https://github.com/littlefs-project/littlefs.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | file(GLOB SOURCES src/littlefs/*.c) 4 | list(APPEND SOURCES src/esp_littlefs.c src/littlefs_esp_part.c src/lfs_config.c) 5 | 6 | if(IDF_TARGET STREQUAL "esp8266") 7 | # ESP8266 configuration here 8 | else() 9 | # non-ESP8266 configuration 10 | list(APPEND pub_requires sdmmc) 11 | 12 | if(CONFIG_LITTLEFS_SDMMC_SUPPORT) 13 | list(APPEND SOURCES src/littlefs_sdmmc.c) 14 | endif() 15 | endif() 16 | 17 | list(APPEND pub_requires esp_partition) 18 | list(APPEND priv_requires esptool_py spi_flash vfs) 19 | 20 | idf_component_register( 21 | SRCS ${SOURCES} 22 | INCLUDE_DIRS include 23 | PRIV_INCLUDE_DIRS src 24 | REQUIRES ${pub_requires} 25 | PRIV_REQUIRES ${priv_requires} 26 | ) 27 | 28 | set_source_files_properties( 29 | ${SOURCES} 30 | PROPERTIES COMPILE_FLAGS "-DLFS_CONFIG=lfs_config.h" 31 | ) 32 | 33 | if(CONFIG_LITTLEFS_FCNTL_GET_PATH) 34 | target_compile_definitions(${COMPONENT_LIB} PUBLIC -DF_GETPATH=${CONFIG_LITTLEFS_FCNTL_F_GETPATH_VALUE}) 35 | endif() 36 | 37 | if(CONFIG_LITTLEFS_MULTIVERSION) 38 | target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_MULTIVERSION) 39 | endif() 40 | 41 | if(CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE) 42 | target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_NO_MALLOC) 43 | endif() 44 | 45 | if(NOT CONFIG_LITTLEFS_ASSERTS) 46 | target_compile_definitions(${COMPONENT_LIB} PUBLIC -DLFS_NO_ASSERT) 47 | endif() 48 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "LittleFS" 2 | 3 | config LITTLEFS_SDMMC_SUPPORT 4 | bool "SDMMC support (requires ESP-IDF v5+)" 5 | default n 6 | help 7 | Toggle SD card support 8 | This requires IDF v5+ as older ESP-IDF do not support SD card erase. 9 | 10 | config LITTLEFS_MAX_PARTITIONS 11 | int "Maximum Number of Partitions" 12 | default 3 13 | range 1 10 14 | help 15 | Define maximum number of partitions that can be mounted. 16 | 17 | config LITTLEFS_PAGE_SIZE 18 | int "LITTLEFS logical page size" 19 | default 256 20 | range 256 1024 21 | help 22 | Logical page size of LITTLEFS partition, in bytes. Must be multiple 23 | of flash page size (which is usually 256 bytes). 24 | Larger page sizes reduce overhead when storing large files, and 25 | improve filesystem performance when reading large files. 26 | Smaller page sizes reduce overhead when storing small (< page size) 27 | files. 28 | 29 | config LITTLEFS_OBJ_NAME_LEN 30 | int "Maximum object name length including NULL terminator." 31 | default 64 32 | range 16 1022 33 | help 34 | Includes NULL-terminator. If flashing a prebuilt filesystem image, 35 | rebuild the filesystem image if this value changes. 36 | mklittlefs, the tool that generates the image will automatically be rebuilt. 37 | If downloading a pre-built release of mklittlefs, it was most-likely 38 | built with LFS_NAME_MAX=32 and should not be used. 39 | 40 | config LITTLEFS_READ_SIZE 41 | int "Minimum size of a block read." 42 | default 128 43 | help 44 | Minimum size of a block read. All read operations will be a 45 | multiple of this value. 46 | 47 | config LITTLEFS_WRITE_SIZE 48 | int "Minimum size of a block write." 49 | default 128 50 | help 51 | Minimum size of a block program. All write operations will be a 52 | multiple of this value. 53 | 54 | config LITTLEFS_LOOKAHEAD_SIZE 55 | int "Look ahead size." 56 | default 128 57 | help 58 | Look ahead size. Must be a multiple of 8. 59 | 60 | config LITTLEFS_CACHE_SIZE 61 | int "Cache Size" 62 | default 512 63 | help 64 | Size of block caches. Each cache buffers a portion of a block in RAM. 65 | The littlefs needs a read cache, a program cache, and one additional 66 | cache per file. Larger caches can improve performance by storing more 67 | data and reducing the number of disk accesses. Must be a multiple of 68 | the read and program sizes, and a factor of the block size (4096). 69 | 70 | config LITTLEFS_BLOCK_CYCLES 71 | int "LittleFS wear-leveling block cycles" 72 | default 512 73 | range -1 1024 74 | help 75 | Number of erase cycles before littlefs evicts metadata logs and moves 76 | the metadata to another block. Suggested values are in the 77 | range 100-1000, with large values having better performance at the cost 78 | of less consistent wear distribution. 79 | Set to -1 to disable block-level wear-leveling. 80 | 81 | config LITTLEFS_USE_MTIME 82 | bool "Save file modification time" 83 | default "y" 84 | help 85 | Saves timestamp on modification. Uses an additional 4bytes. 86 | 87 | config LITTLEFS_USE_ONLY_HASH 88 | bool "Don't store filepath in the file descriptor" 89 | default "n" 90 | help 91 | Records the filepath only as a 32-bit hash in the file descriptor instead 92 | of the entire filepath. Saves approximately `sizeof(filepath)` bytes 93 | per file descriptor. 94 | If enabled, functionality (like fstat) that requires the file path 95 | from the file descriptor will not work. 96 | In rare cases, may cause unlinking or renaming issues (unlikely) if 97 | there's a hash collision between an open filepath and a filepath 98 | to be modified. 99 | 100 | config LITTLEFS_HUMAN_READABLE 101 | bool "Make errno human-readable" 102 | default "n" 103 | help 104 | Converts LittleFS error codes into human readable strings. 105 | May increase binary size depending on logging level. 106 | 107 | choice LITTLEFS_MTIME 108 | prompt "mtime attribute options" 109 | depends on LITTLEFS_USE_MTIME 110 | default LITTLEFS_MTIME_USE_SECONDS 111 | help 112 | Save an additional 4-byte attribute. Options listed below. 113 | 114 | config LITTLEFS_MTIME_USE_SECONDS 115 | bool "Use Seconds" 116 | help 117 | Saves timestamp on modification. 118 | 119 | config LITTLEFS_MTIME_USE_NONCE 120 | bool "Use Nonce" 121 | help 122 | Saves nonce on modification; intended for detecting filechanges 123 | on systems without access to a RTC. 124 | 125 | A file who's nonce is the same as it was at a previous time has 126 | high probability of not having been modified. 127 | 128 | Upon file modification, the nonce is incremented by one. Upon file 129 | creation, a random nonce is assigned. 130 | 131 | There is a very slim chance that a file will have the same nonce if 132 | it is deleted and created again (approx 1 in 4 billion). 133 | 134 | endchoice 135 | 136 | config LITTLEFS_SPIFFS_COMPAT 137 | bool "Improve SPIFFS drop-in compatability" 138 | default "n" 139 | help 140 | Enabling this feature allows for greater drop-in compatability 141 | when replacing SPIFFS. Since SPIFFS doesn't have folders, and 142 | folders are just considered as part of a file name, enabling this 143 | will automatically create folders as necessary to create a file 144 | instead of throwing an error. Similarly, upon the deletion of the 145 | last file in a folder, the folder will be deleted. It is recommended 146 | to only enable this flag as a stop-gap solution. 147 | 148 | config LITTLEFS_FLUSH_FILE_EVERY_WRITE 149 | bool "Flush file to flash after each write operation" 150 | default "n" 151 | help 152 | Enabling this feature extends SPIFFS capability. 153 | In SPIFFS data is written immediately to the flash storage when fflush() function called. 154 | In LittleFS flush() does not write data to the flash, and fsync() call needed after. 155 | With this feature fflush() will write data to the storage. 156 | 157 | config LITTLEFS_OPEN_DIR 158 | bool "Support opening directory" 159 | default "n" 160 | depends on !LITTLEFS_USE_ONLY_HASH && LITTLEFS_SPIFFS_COMPAT 161 | help 162 | Support opening directory by following APIs: 163 | 164 | int fd = open("my_directory", O_DIRECTORY); 165 | 166 | config LITTLEFS_FCNTL_GET_PATH 167 | bool "Support get file or directory path" 168 | default "n" 169 | depends on !LITTLEFS_USE_ONLY_HASH 170 | help 171 | Support getting directory by following APIs: 172 | 173 | char buffer[MAXPATHLEN]; 174 | 175 | int fd = open("my_file", flags); 176 | fcntl(fd, F_GETPATH, buffer); 177 | 178 | config LITTLEFS_FCNTL_F_GETPATH_VALUE 179 | int "Value of command F_GETPATH" 180 | default 20 181 | depends on LITTLEFS_FCNTL_GET_PATH 182 | help 183 | ESP-IDF's header file "fcntl.h" doesn't support macro "F_GETPATH", 184 | so we should define this macro here. 185 | 186 | config LITTLEFS_MULTIVERSION 187 | bool "Support selecting the LittleFS minor version to write to disk" 188 | default "n" 189 | help 190 | LittleFS 2.6 bumps the on-disk minor version of littlefs from lfs2.0 -> lfs2.1. 191 | 192 | This change is backwards-compatible, but after the first write with the new version, 193 | the image on disk will no longer be mountable by older versions of littlefs. 194 | 195 | Enabling LITTLEFS_MULTIVERSION allows to select the On-disk version 196 | to use when writing in the form of 16-bit major version 197 | + 16-bit minor version. This limiting metadata to what is supported by 198 | older minor versions. Note that some features will be lost. Defaults to 199 | to the most recent minor version when zero. 200 | 201 | choice LITTLEFS_DISK_VERSION 202 | prompt "LITTLEFS_DISK_VERSION" 203 | depends on LITTLEFS_MULTIVERSION 204 | default LITTLEFS_DISK_VERSION_MOST_RECENT 205 | help 206 | See LITTLEFS_MULTIVERSION for details. 207 | 208 | config LITTLEFS_DISK_VERSION_MOST_RECENT 209 | bool "Write the most recent LittleFS version" 210 | 211 | config LITTLEFS_DISK_VERSION_2_1 212 | bool "Write LittleFS 2.1" 213 | 214 | config LITTLEFS_DISK_VERSION_2_0 215 | bool "Write LittleFS 2.0 (no forward-looking erase-state CRCs)" 216 | 217 | endchoice 218 | 219 | choice LITTLEFS_MALLOC_STRATEGY 220 | prompt "Buffer allocation strategy" 221 | default LITTLEFS_MALLOC_STRATEGY_DEFAULT 222 | help 223 | Maps lfs_malloc to ESP-IDF capabilities-based memory allocator or 224 | disables dynamic allocation in favour of user-provided static buffers. 225 | 226 | config LITTLEFS_MALLOC_STRATEGY_DISABLE 227 | bool "Static buffers only" 228 | help 229 | Disallow dynamic allocation, static buffers must be provided by the calling application. 230 | 231 | config LITTLEFS_MALLOC_STRATEGY_DEFAULT 232 | bool "Default heap selection" 233 | help 234 | Uses an automatic allocation strategy. On systems with heap in SPIRAM, if 235 | the allocation size does not exceed SPIRAM_MALLOC_ALWAYSINTERNAL then internal 236 | heap allocation if preferred, otherwise allocation will be attempted from SPIRAM 237 | heap. 238 | 239 | config LITTLEFS_MALLOC_STRATEGY_INTERNAL 240 | bool "Internal heap" 241 | help 242 | Uses ESP-IDF heap_caps_malloc to allocate from internal heap. 243 | 244 | config LITTLEFS_MALLOC_STRATEGY_SPIRAM 245 | bool "SPIRAM heap" 246 | depends on SPIRAM_USE_MALLOC || SPIRAM_USE_CAPS_ALLOC 247 | help 248 | Uses ESP-IDF heap_caps_malloc to allocate from SPIRAM heap. 249 | 250 | endchoice 251 | 252 | config LITTLEFS_ASSERTS 253 | bool "Enable asserts" 254 | default "y" 255 | help 256 | Selects whether littlefs performs runtime assert checks. 257 | 258 | config LITTLEFS_MMAP_PARTITION 259 | bool "Memory map LITTLEFS partitions" 260 | default "n" 261 | help 262 | Use esp_partition_mmap to map the partitions to memory, which can provide a significant 263 | performance boost in some cases. Make sure the chip you're using has enough available address 264 | space to map the partition (for the ESP32 there is 4MB available). 265 | 266 | endmenu 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Brian Pugh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := littlefs 2 | 3 | EXTRA_COMPONENT_DIRS := \ 4 | $(abspath .) \ 5 | $(abspath unit_tester) \ 6 | $(IDF_PATH)/tools/unit-test-app/components/ 7 | 8 | CFLAGS += \ 9 | -Werror 10 | 11 | include $(IDF_PATH)/make/project.mk 12 | 13 | .PHONY: tests 14 | 15 | tests-build: 16 | $(MAKE) \ 17 | TEST_COMPONENTS='src' 18 | 19 | tests: 20 | $(MAKE) \ 21 | TEST_COMPONENTS='src' \ 22 | flash monitor; 23 | 24 | tests-enc: 25 | $(MAKE) \ 26 | TEST_COMPONENTS='src' \ 27 | encrypted-flash monitor; 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LittleFS for ESP-IDF. 2 | 3 | # What is LittleFS? 4 | 5 | [LittleFS](https://github.com/ARMmbed/littlefs) is a small fail-safe filesystem 6 | for microcontrollers. We ported LittleFS to esp-idf (specifically, the ESP32) 7 | because SPIFFS was too slow, and FAT was too fragile. 8 | 9 | # How to Use 10 | 11 | ## ESP-IDF 12 | 13 | There are two ways to add this component to your project 14 | 15 | 1. As a ESP-IDF managed component: In your project directory run 16 | 17 | ``` 18 | idf.py add-dependency joltwallet/littlefs==1.20.1 19 | ``` 20 | 21 | 2. As a submodule: In your project, add this as a submodule to your `components/` directory. 22 | 23 | ``` 24 | git submodule add https://github.com/joltwallet/esp_littlefs.git 25 | git submodule update --init --recursive 26 | ``` 27 | 28 | The library can be configured via `idf.py menuconfig` under `Component config->LittleFS`. 29 | 30 | #### Example 31 | User @wreyford has kindly provided a [demo repo](https://github.com/wreyford/demo_esp_littlefs) showing the use of `esp_littlefs`. A modified copy exists in the `example/` directory. 32 | 33 | ## PlatformIO 34 | Add to the following line to your project's `platformio.ini` file: 35 | 36 | ``` 37 | lib_deps = https://github.com/joltwallet/esp_littlefs.git 38 | ``` 39 | 40 | Example `platformio.ini` file: 41 | 42 | ``` 43 | [env] 44 | platform = espressif32 45 | framework = espidf 46 | monitor_speed = 115200 47 | 48 | [common] 49 | lib_deps = https://github.com/joltwallet/esp_littlefs.git 50 | 51 | [env:nodemcu-32s] 52 | board = nodemcu-32s 53 | board_build.filesystem = littlefs 54 | board_build.partitions = min_littlefs.csv 55 | lib_deps = ${common.lib_deps} 56 | ``` 57 | 58 | Example `min_littlefs.cvs` flash partition layout: 59 | ``` 60 | # Name, Type, SubType, Offset, Size, Flags 61 | nvs, data, nvs, 0x9000, 0x5000, 62 | otadata, data, ota, 0xe000, 0x2000, 63 | app0, app, ota_0, 0x10000, 0x1E0000, 64 | app1, app, ota_1, 0x1F0000,0x1E0000, 65 | littlefs, data, littlefs, 0x3D0000,0x20000, 66 | coredump, data, coredump, 0x3F0000,0x10000, 67 | ``` 68 | 69 | [Currently, it is required](https://github.com/platformio/platform-espressif32/issues/479) to modify `CMakeList.txt`. Add the following 2 lines to the your project's `CMakeList.txt`: 70 | 71 | ``` 72 | get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME) 73 | list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs") 74 | ``` 75 | 76 | Example `CMakeList.txt`: 77 | 78 | ``` 79 | cmake_minimum_required(VERSION 3.16.0) 80 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 81 | 82 | get_filename_component(configName "${CMAKE_BINARY_DIR}" NAME) 83 | list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_SOURCE_DIR}/.pio/libdeps/${configName}/esp_littlefs") 84 | 85 | project(my_project_name_here) 86 | ``` 87 | 88 | To configure LittleFS from PlatformIO, run the following command: 89 | 90 | ```console 91 | $ pio run -t menuconfig 92 | ``` 93 | An entry `Component config->LittleFS` should be available for configuration. If not, check your `CMakeList.txt` configuration. 94 | 95 | 96 | # Documentation 97 | 98 | See the official [ESP-IDF SPIFFS documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html), basically all the functionality is the 99 | same; just replace `spiffs` with `littlefs` in all function calls. 100 | 101 | Also see the comments in `include/esp_littlefs.h` 102 | 103 | Slight differences between this configuration and SPIFFS's configuration is in the `esp_vfs_littlefs_conf_t`: 104 | 105 | 1. `max_files` field doesn't exist since we removed the file limit, thanks to @X-Ryl669 106 | 2. `grow_on_mount` will expand an existing filesystem to fill the partition. Defaults to `false`. 107 | * LittleFS filesystems can only grow, they cannot shrink. 108 | 109 | ### Filesystem Image Creation 110 | 111 | At compile time, a filesystem image can be created and flashed to the device by adding the following to your project's `CMakeLists.txt` file: 112 | 113 | ``` 114 | littlefs_create_partition_image(partition_name path_to_folder_containing_files FLASH_IN_PROJECT) 115 | ``` 116 | 117 | If `FLASH_IN_PROJECT` is not specified, the image will still be generated, but you will have to flash it manually using `esptool.py`, `parttool.py`, or a custom build system target. 118 | 119 | For example, if your partition table looks like: 120 | 121 | ``` 122 | # Name, Type, SubType, Offset, Size, Flags 123 | nvs, data, nvs, 0x9000, 0x6000, 124 | phy_init, data, phy, 0xf000, 0x1000, 125 | factory, app, factory, 0x10000, 1M, 126 | graphics, data, spiffs, , 0xF0000, 127 | ``` 128 | 129 | change it to: 130 | 131 | ``` 132 | # Name, Type, SubType, Offset, Size, Flags 133 | nvs, data, nvs, 0x9000, 0x6000, 134 | phy_init, data, phy, 0xf000, 0x1000, 135 | factory, app, factory, 0x10000, 1M, 136 | graphics, data, littlefs, , 0xF0000, 137 | ``` 138 | 139 | 140 | and your project has a folder called `device_graphics/`, your call should be: 141 | 142 | ``` 143 | littlefs_create_partition_image(graphics device_graphics FLASH_IN_PROJECT) 144 | ``` 145 | 146 | 147 | # Performance 148 | 149 | Here are some naive benchmarks to give a vague indicator on performance. 150 | Tests were performed with the following configuration: 151 | 152 | * ESP-IDF: v4.4 153 | * Target: ESP32 154 | * CPU Clock: 160MHz 155 | * Flash SPI Freq: 80MHz 156 | * Flash SPI Mode: QIO 157 | 158 | In these tests, FAT has a cache size of 4096, and SPIFFS has a cahce size of 256 bytes. 159 | 160 | #### Formatting a 512KB partition 161 | 162 | ``` 163 | FAT: 549,494 us 164 | SPIFFS: 10,715,425 us 165 | LittleFS: 110,997 us 166 | ``` 167 | 168 | #### Writing 5 88KB files 169 | 170 | ``` 171 | FAT: 7,124,812 us 172 | SPIFFS*: 99,138,905 us 173 | LittleFS (cache=128): 8,261,920 us 174 | LittleFS (cache=512 default): 6,356,247 us 175 | LittleFS (cache=4096): 6,026,592 us 176 | *Only wrote 374,784 bytes instead of the benchmark 440,000, so this value is extrapolated 177 | ``` 178 | 179 | In the above test, SPIFFS drastically slows down as the filesystem fills up. Below 180 | is the specific breakdown of file write times for SPIFFS. Not sure what happens 181 | on the last file write. 182 | 183 | 184 | ``` 185 | SPIFFS: 186 | 187 | 88000 bytes written in 2190635 us 188 | 88000 bytes written in 2190321 us 189 | 88000 bytes written in 5133605 us 190 | 88000 bytes written in 16570667 us 191 | 22784 bytes written in 73053677 us 192 | ``` 193 | 194 | #### Reading 5 88KB files 195 | 196 | ``` 197 | FAT: 5,685,230 us 198 | SPIFFS*: 5,162,289 us 199 | LittleFS (cache=128): 6,284,142 us 200 | LittleFS (cache=512 default): 5,874,931 us 201 | LittleFS (cache=4096): 5,731,385 us 202 | *Only read 374,784 bytes instead of the benchmark 440,000, so this value is extrapolated 203 | ``` 204 | 205 | #### Deleting 5 88KB files 206 | 207 | ``` 208 | FAT: 680,358 us 209 | SPIFFS*: 1,653,500 us 210 | LittleFS (cache=128): 86,090 us 211 | LittleFS (cache=512 default): 53,705 us 212 | LittleFS (cache=4096): 27,709 us 213 | *The 5th file was smaller, did not extrapolate value. 214 | ``` 215 | 216 | 217 | # Tips, Tricks, and Gotchas 218 | 219 | * LittleFS operates on blocks, and blocks have a size of 4096 bytes on the ESP32. 220 | 221 | * A freshly formatted LittleFS will have 2 blocks in use, making it seem like 8KB are in use. 222 | 223 | * The esp32 has [flash concurrency constraints](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_flash/spi_flash_concurrency.html#concurrency-constraints-for-flash-on-spi1). 224 | When using UART (either for data transfer or generic logging) at the same time, you *MUST* enable the following option in KConfig: 225 | `menuconfig > Component config > Driver config > UART > UART ISR in IRAM`. 226 | 227 | # Running Unit Tests 228 | 229 | To flash the unit-tester app and the unit-tests, clone or symbolicly link this 230 | component to `$IDF_PATH/tools/unit-test-app/components/littlefs`. Make sure the 231 | folder name is `littlefs`, not `esp_littlefs`. Then, run the following: 232 | 233 | ``` 234 | cd $IDF_PATH/tools/unit-test-app 235 | idf.py menuconfig # See notes 236 | idf.py -T littlefs -p YOUR_PORT_HERE flash monitor 237 | ``` 238 | 239 | In `menuconfig`: 240 | 241 | * Set the partition table to `components/littlefs/partition_table_unit_test_app.csv` 242 | 243 | * Double check your crystal frequency `ESP32_XTAL_FREQ_SEL`; my board doesn't work with autodetect. 244 | 245 | To test on an encrypted partition, add the `encrypted` flag to the `flash_test` partition 246 | in `partition_table_unit_test_app.csv`. I.e. 247 | 248 | ``` 249 | flash_test, data, spiffs, , 512K, encrypted 250 | ``` 251 | 252 | Also make sure that `CONFIG_SECURE_FLASH_ENC_ENABLED=y` in `menuconfig`. 253 | 254 | The unit tester can then be flashed via the command: 255 | 256 | ``` 257 | idf.py -T littlefs -p YOUR_PORT_HERE encrypted-flash monitor 258 | ``` 259 | 260 | # Breaking Changes 261 | 262 | * July 22, 2020 - Changed attribute type for file timestamp from `0` to `0x74` ('t' ascii value). 263 | * May 3, 2023 - All logging tags have been changed to a unified `esp_littlefs`. 264 | 265 | # Acknowledgement 266 | 267 | This code base was heavily modeled after the SPIFFS esp-idf component. 268 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Component Makefile 3 | # 4 | 5 | COMPONENT_SRCDIRS := src src/littlefs 6 | 7 | COMPONENT_ADD_INCLUDEDIRS := include 8 | 9 | COMPONENT_PRIV_INCLUDEDIRS := src 10 | 11 | COMPONENT_SUBMODULES := src/littlefs 12 | 13 | CFLAGS += \ 14 | -DLFS_CONFIG=lfs_config.h 15 | 16 | ifdef CONFIG_LITTLEFS_FCNTL_GET_PATH 17 | CFLAGS += \ 18 | -DF_GETPATH=$(CONFIG_LITTLEFS_FCNTL_F_GETPATH_VALUE) 19 | endif 20 | 21 | ifdef CONFIG_LITTLEFS_MULTIVERSION 22 | CFLAGS += \ 23 | -DLFS_MULTIVERSION 24 | endif 25 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | # Add the root of this git repo to the component search path. 6 | set(EXTRA_COMPONENT_DIRS "../") 7 | 8 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 9 | project(demo_esp_littlefs) 10 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := demo_esp_littlefs 7 | 8 | EXTRA_COMPONENT_DIRS := $(realpath ..) 9 | 10 | include $(IDF_PATH)/make/project.mk 11 | 12 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This example is based on [wreyford's](https://github.com/wreyford/demo_esp_littlefs) demo project. 2 | 3 | Modifications were made so that this example project could be built as a part of CI. 4 | -------------------------------------------------------------------------------- /example/flash_data/example.txt: -------------------------------------------------------------------------------- 1 | Example text to compile into a LittleFS disk image to be flashed to the ESP device. 2 | -------------------------------------------------------------------------------- /example/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "demo_esp_littlefs.c" 2 | INCLUDE_DIRS "." 3 | ) 4 | 5 | # Note: you must have a partition named the first argument (here it's "littlefs") 6 | # in your partition table csv file. 7 | littlefs_create_partition_image(littlefs ../flash_data FLASH_IN_PROJECT) 8 | -------------------------------------------------------------------------------- /example/main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /example/main/demo_esp_littlefs.c: -------------------------------------------------------------------------------- 1 | /* Demo ESP LittleFS Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | #include "esp_err.h" 10 | #include "esp_log.h" 11 | #include "esp_system.h" 12 | #include "freertos/FreeRTOS.h" 13 | #include "freertos/task.h" 14 | #include "sdkconfig.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "esp_idf_version.h" 20 | #include "esp_flash.h" 21 | #include "esp_chip_info.h" 22 | #include "spi_flash_mmap.h" 23 | 24 | 25 | #include "esp_littlefs.h" 26 | 27 | static const char *TAG = "demo_esp_littlefs"; 28 | 29 | void app_main(void) 30 | { 31 | printf("Demo LittleFs implementation by esp_littlefs!\n"); 32 | printf(" https://github.com/joltwallet/esp_littlefs\n"); 33 | 34 | /* Print chip information */ 35 | esp_chip_info_t chip_info; 36 | esp_chip_info(&chip_info); 37 | printf("This is %s chip with %d CPU cores, WiFi%s%s, ", 38 | CONFIG_IDF_TARGET, 39 | chip_info.cores, 40 | (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", 41 | (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); 42 | 43 | printf("silicon revision %d, ", chip_info.revision); 44 | 45 | uint32_t size_flash_chip = 0; 46 | esp_flash_get_size(NULL, &size_flash_chip); 47 | printf("%uMB %s flash\n", (unsigned int)size_flash_chip >> 20, 48 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); 49 | 50 | printf("Free heap: %u\n", (unsigned int) esp_get_free_heap_size()); 51 | 52 | printf("Now we are starting the LittleFs Demo ...\n"); 53 | 54 | ESP_LOGI(TAG, "Initializing LittleFS"); 55 | 56 | esp_vfs_littlefs_conf_t conf = { 57 | .base_path = "/littlefs", 58 | .partition_label = "littlefs", 59 | .format_if_mount_failed = true, 60 | .dont_mount = false, 61 | }; 62 | 63 | // Use settings defined above to initialize and mount LittleFS filesystem. 64 | // Note: esp_vfs_littlefs_register is an all-in-one convenience function. 65 | esp_err_t ret = esp_vfs_littlefs_register(&conf); 66 | 67 | if (ret != ESP_OK) 68 | { 69 | if (ret == ESP_FAIL) 70 | { 71 | ESP_LOGE(TAG, "Failed to mount or format filesystem"); 72 | } 73 | else if (ret == ESP_ERR_NOT_FOUND) 74 | { 75 | ESP_LOGE(TAG, "Failed to find LittleFS partition"); 76 | } 77 | else 78 | { 79 | ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret)); 80 | } 81 | return; 82 | } 83 | 84 | size_t total = 0, used = 0; 85 | ret = esp_littlefs_info(conf.partition_label, &total, &used); 86 | if (ret != ESP_OK) 87 | { 88 | ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret)); 89 | } 90 | else 91 | { 92 | ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); 93 | } 94 | 95 | // Use POSIX and C standard library functions to work with files. 96 | // First create a file. 97 | ESP_LOGI(TAG, "Opening file"); 98 | FILE *f = fopen("/littlefs/hello.txt", "w"); 99 | if (f == NULL) 100 | { 101 | ESP_LOGE(TAG, "Failed to open file for writing"); 102 | return; 103 | } 104 | fprintf(f, "LittleFS Rocks!\n"); 105 | fclose(f); 106 | ESP_LOGI(TAG, "File written"); 107 | 108 | // Check if destination file exists before renaming 109 | struct stat st; 110 | if (stat("/littlefs/foo.txt", &st) == 0) 111 | { 112 | // Delete it if it exists 113 | unlink("/littlefs/foo.txt"); 114 | } 115 | 116 | // Rename original file 117 | ESP_LOGI(TAG, "Renaming file"); 118 | if (rename("/littlefs/hello.txt", "/littlefs/foo.txt") != 0) 119 | { 120 | ESP_LOGE(TAG, "Rename failed"); 121 | return; 122 | } 123 | 124 | // Open renamed file for reading 125 | ESP_LOGI(TAG, "Reading file"); 126 | f = fopen("/littlefs/foo.txt", "r"); 127 | if (f == NULL) 128 | { 129 | ESP_LOGE(TAG, "Failed to open file for reading"); 130 | return; 131 | } 132 | 133 | char line[128]; 134 | char *pos; 135 | 136 | fgets(line, sizeof(line), f); 137 | fclose(f); 138 | // strip newline 139 | pos = strchr(line, '\n'); 140 | if (pos) 141 | { 142 | *pos = '\0'; 143 | } 144 | ESP_LOGI(TAG, "Read from file: '%s'", line); 145 | 146 | ESP_LOGI(TAG, "Reading from flashed filesystem example.txt"); 147 | f = fopen("/littlefs/example.txt", "r"); 148 | if (f == NULL) 149 | { 150 | ESP_LOGE(TAG, "Failed to open file for reading"); 151 | return; 152 | } 153 | fgets(line, sizeof(line), f); 154 | fclose(f); 155 | // strip newline 156 | pos = strchr(line, '\n'); 157 | if (pos) 158 | { 159 | *pos = '\0'; 160 | } 161 | ESP_LOGI(TAG, "Read from file: '%s'", line); 162 | 163 | // All done, unmount partition and disable LittleFS 164 | esp_vfs_littlefs_unregister(conf.partition_label); 165 | ESP_LOGI(TAG, "LittleFS unmounted"); 166 | } 167 | -------------------------------------------------------------------------------- /example/partitions_demo_esp_littlefs.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 1M, 6 | littlefs, data, littlefs, , 0xF0000, 7 | -------------------------------------------------------------------------------- /example/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_PARTITION_TABLE_CUSTOM=y 2 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_demo_esp_littlefs.csv" 3 | 4 | # 5 | # Serial flasher config 6 | # 7 | CONFIG_ESPTOOLPY_BAUD_921600B=y 8 | CONFIG_ESPTOOLPY_COMPRESSED=y 9 | CONFIG_ESPTOOLPY_MONITOR_BAUD_CONSOLE=y 10 | 11 | # BOOTLOADER 12 | CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y 13 | -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | version: "1.20.1" 2 | description: LittleFS is a small fail-safe filesystem for micro-controllers. 3 | url: https://github.com/joltwallet/esp_littlefs 4 | dependencies: 5 | idf: ">=5.0" 6 | -------------------------------------------------------------------------------- /image-building-requirements.txt: -------------------------------------------------------------------------------- 1 | littlefs-python==0.13.3 2 | -------------------------------------------------------------------------------- /include/esp_littlefs.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP_LITTLEFS_H__ 2 | #define ESP_LITTLEFS_H__ 3 | 4 | #include "esp_err.h" 5 | #include "esp_idf_version.h" 6 | #include 7 | #include "esp_partition.h" 8 | 9 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 10 | #include 11 | #endif 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #define ESP_LITTLEFS_VERSION_NUMBER "1.20.1" 18 | #define ESP_LITTLEFS_VERSION_MAJOR 1 19 | #define ESP_LITTLEFS_VERSION_MINOR 20 20 | #define ESP_LITTLEFS_VERSION_PATCH 1 21 | 22 | #ifdef ESP8266 23 | // ESP8266 RTOS SDK default enables VFS DIR support 24 | #define CONFIG_VFS_SUPPORT_DIR 1 25 | #endif 26 | 27 | #if CONFIG_VFS_SUPPORT_DIR 28 | #define ESP_LITTLEFS_ENABLE_FTRUNCATE 29 | #endif // CONFIG_VFS_SUPPORT_DIR 30 | 31 | /** 32 | *Configuration structure for esp_vfs_littlefs_register. 33 | */ 34 | typedef struct { 35 | const char *base_path; /**< Mounting point. */ 36 | const char *partition_label; /**< Label of partition to use. If partition_label, partition, and sdcard are all NULL, 37 | then the first partition with data subtype 'littlefs' will be used. */ 38 | const esp_partition_t* partition; /**< partition to use if partition_label is NULL */ 39 | 40 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 41 | sdmmc_card_t *sdcard; /**< SD card handle to use if both esp_partition handle & partition label is NULL */ 42 | #endif 43 | 44 | uint8_t format_if_mount_failed:1; /**< Format the file system if it fails to mount. */ 45 | uint8_t read_only : 1; /**< Mount the partition as read-only. */ 46 | uint8_t dont_mount:1; /**< Don't attempt to mount.*/ 47 | uint8_t grow_on_mount:1; /**< Grow filesystem to match partition size on mount.*/ 48 | } esp_vfs_littlefs_conf_t; 49 | 50 | /** 51 | * Register and mount (if configured to) littlefs to VFS with given path prefix. 52 | * 53 | * @param conf Pointer to esp_vfs_littlefs_conf_t configuration structure 54 | * 55 | * @return 56 | * - ESP_OK if success 57 | * - ESP_ERR_NO_MEM if objects could not be allocated 58 | * - ESP_ERR_INVALID_STATE if already mounted or partition is encrypted 59 | * - ESP_ERR_NOT_FOUND if partition for littlefs was not found 60 | * - ESP_FAIL if mount or format fails 61 | */ 62 | esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t * conf); 63 | 64 | /** 65 | * Unregister and unmount littlefs from VFS 66 | * 67 | * @param partition_label Label of the partition to unregister. 68 | * 69 | * @return 70 | * - ESP_OK if successful 71 | * - ESP_ERR_INVALID_STATE already unregistered 72 | */ 73 | esp_err_t esp_vfs_littlefs_unregister(const char* partition_label); 74 | 75 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 76 | /** 77 | * Unregister and unmount LittleFS from VFS for SD card 78 | * 79 | * @param sdcard SD card to unregister. 80 | * 81 | * @return 82 | * - ESP_OK if successful 83 | * - ESP_ERR_INVALID_STATE already unregistered 84 | */ 85 | esp_err_t esp_vfs_littlefs_unregister_sdmmc(sdmmc_card_t *sdcard); 86 | #endif 87 | 88 | /** 89 | * Unregister and unmount littlefs from VFS 90 | * 91 | * @param partition partition to unregister. 92 | * 93 | * @return 94 | * - ESP_OK if successful 95 | * - ESP_ERR_INVALID_STATE already unregistered 96 | */ 97 | esp_err_t esp_vfs_littlefs_unregister_partition(const esp_partition_t* partition); 98 | 99 | /** 100 | * Check if littlefs is mounted 101 | * 102 | * @param partition_label Label of the partition to check. 103 | * 104 | * @return 105 | * - true if mounted 106 | * - false if not mounted 107 | */ 108 | bool esp_littlefs_mounted(const char* partition_label); 109 | 110 | /** 111 | * Check if littlefs is mounted 112 | * 113 | * @param partition partition to check. 114 | * 115 | * @return 116 | * - true if mounted 117 | * - false if not mounted 118 | */ 119 | bool esp_littlefs_partition_mounted(const esp_partition_t* partition); 120 | 121 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 122 | /** 123 | * Check if littlefs is mounted 124 | * 125 | * @param sdcard SD card to check. 126 | * 127 | * @return 128 | * - true if mounted 129 | * - false if not mounted 130 | */ 131 | bool esp_littlefs_sdmmc_mounted(sdmmc_card_t *sdcard); 132 | #endif 133 | 134 | /** 135 | * Format the littlefs partition 136 | * 137 | * @param partition_label Label of the partition to format. 138 | * @return 139 | * - ESP_OK if successful 140 | * - ESP_FAIL on error 141 | */ 142 | esp_err_t esp_littlefs_format(const char* partition_label); 143 | 144 | /** 145 | * Format the littlefs partition 146 | * 147 | * @param partition partition to format. 148 | * @return 149 | * - ESP_OK if successful 150 | * - ESP_FAIL on error 151 | */ 152 | esp_err_t esp_littlefs_format_partition(const esp_partition_t* partition); 153 | 154 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 155 | /** 156 | * Format the LittleFS on a SD card 157 | * 158 | * @param sdcard SD card to format 159 | * @return 160 | * - ESP_OK if successful 161 | * - ESP_FAIL on error 162 | */ 163 | esp_err_t esp_littlefs_format_sdmmc(sdmmc_card_t *sdcard); 164 | #endif 165 | 166 | /** 167 | * Get information for littlefs 168 | * 169 | * @param partition_label Optional, label of the partition to get info for. 170 | * @param[out] total_bytes Size of the file system 171 | * @param[out] used_bytes Current used bytes in the file system 172 | * 173 | * @return 174 | * - ESP_OK if success 175 | * - ESP_ERR_INVALID_STATE if not mounted 176 | */ 177 | esp_err_t esp_littlefs_info(const char* partition_label, size_t* total_bytes, size_t* used_bytes); 178 | 179 | /** 180 | * Get information for littlefs 181 | * 182 | * @param parition the partition to get info for. 183 | * @param[out] total_bytes Size of the file system 184 | * @param[out] used_bytes Current used bytes in the file system 185 | * 186 | * @return 187 | * - ESP_OK if success 188 | * - ESP_ERR_INVALID_STATE if not mounted 189 | */ 190 | esp_err_t esp_littlefs_partition_info(const esp_partition_t* partition, size_t *total_bytes, size_t *used_bytes); 191 | 192 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 193 | /** 194 | * Get information for littlefs on SD card 195 | * 196 | * @param[in] sdcard the SD card to get info for. 197 | * @param[out] total_bytes Size of the file system 198 | * @param[out] used_bytes Current used bytes in the file system 199 | * 200 | * @return 201 | * - ESP_OK if success 202 | * - ESP_ERR_INVALID_STATE if not mounted 203 | */ 204 | esp_err_t esp_littlefs_sdmmc_info(sdmmc_card_t *sdcard, size_t *total_bytes, size_t *used_bytes); 205 | #endif 206 | 207 | #ifdef __cplusplus 208 | } // extern "C" 209 | #endif 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esp_littlefs", 3 | "version": "1.20.1", 4 | "description": "LittleFS is a small fail-safe filesystem for micro-controllers.", 5 | "frameworks": "espidf", 6 | "platforms": "*", 7 | "build": { 8 | "srcFilter": "+<*> - - -", 9 | "flags": [ 10 | "-I ./src/littlefs/", 11 | "-DLFS_CONFIG=lfs_config.h" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /partition_table_unit_test_app.csv: -------------------------------------------------------------------------------- 1 | # Special partition table for unit test app 2 | # 3 | # Name, Type, SubType, Offset, Size, Flags 4 | # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild 5 | nvs, data, nvs, 0x9000, 0x4000 6 | otadata, data, ota, 0xd000, 0x2000 7 | phy_init, data, phy, 0xf000, 0x1000 8 | factory, 0, 0, 0x10000, 2M 9 | # these OTA partitions are used for tests, but can't fit real OTA apps in them 10 | # (done this way so tests can run in 2MB of flash.) 11 | ota_0, 0, ota_0, , 64K 12 | ota_1, 0, ota_1, , 64K 13 | # flash_test partition used for SPI flash tests, WL FAT tests, and SPIFFS tests 14 | fat_store, data, fat, , 528K 15 | spiffs_store, data, spiffs, , 512K 16 | flash_test, data, spiffs, , 512K 17 | named_part, data, littlefs, , 64K 18 | -------------------------------------------------------------------------------- /project_include.cmake: -------------------------------------------------------------------------------- 1 | 2 | # littlefs_create_partition_image 3 | # 4 | # Create a littlefs image of the specified directory on the host during build and optionally 5 | # have the created image flashed using `idf.py flash` 6 | 7 | set(littlefs_py_venv "${CMAKE_CURRENT_BINARY_DIR}/littlefs_py_venv") 8 | set(littlefs_py_requirements "${CMAKE_CURRENT_LIST_DIR}/image-building-requirements.txt") 9 | 10 | set_directory_properties(PROPERTIES 11 | ADDITIONAL_CLEAN_FILES "${littlefs_py_venv}" 12 | ) 13 | 14 | function(littlefs_create_partition_image partition base_dir) 15 | set(options FLASH_IN_PROJECT) 16 | set(multi DEPENDS) 17 | cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") 18 | 19 | idf_build_get_property(idf_path IDF_PATH) 20 | 21 | get_filename_component(base_dir_full_path ${base_dir} ABSOLUTE) 22 | 23 | partition_table_get_partition_info(size "--partition-name ${partition}" "size") 24 | partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") 25 | 26 | if("${size}" AND "${offset}") 27 | set(image_file ${CMAKE_BINARY_DIR}/${partition}.bin) 28 | 29 | if(CMAKE_HOST_WIN32) 30 | set(littlefs_py "${littlefs_py_venv}/Scripts/littlefs-python.exe") 31 | add_custom_command( 32 | OUTPUT ${littlefs_py_venv} 33 | COMMAND ${PYTHON} -m venv ${littlefs_py_venv} && ${littlefs_py_venv}/Scripts/pip.exe install -r ${littlefs_py_requirements} 34 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 35 | DEPENDS ${littlefs_py_requirements} 36 | ) 37 | else() 38 | set(littlefs_py "${littlefs_py_venv}/bin/littlefs-python") 39 | add_custom_command( 40 | OUTPUT ${littlefs_py_venv} 41 | COMMAND ${PYTHON} -m venv ${littlefs_py_venv} && ${littlefs_py_venv}/bin/pip install -r ${littlefs_py_requirements} 42 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 43 | DEPENDS ${littlefs_py_requirements} 44 | ) 45 | endif() 46 | 47 | # Execute LittleFS image generation; this always executes as there is no way to specify for CMake to watch for 48 | # contents of the base dir changing. 49 | 50 | add_custom_target(littlefs_${partition}_bin ALL 51 | COMMAND ${littlefs_py} create ${base_dir_full_path} ${image_file} -v --fs-size=${size} --name-max=${CONFIG_LITTLEFS_OBJ_NAME_LEN} --block-size=4096 52 | DEPENDS ${arg_DEPENDS} ${littlefs_py_venv} 53 | ) 54 | 55 | set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY 56 | ADDITIONAL_MAKE_CLEAN_FILES 57 | ${image_file}) 58 | 59 | set(IDF_VER_NO_V "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}") 60 | 61 | if(${IDF_VER_NO_V} VERSION_LESS 5.0) 62 | message(WARNING "Unsupported/unmaintained/deprecated ESP-IDF version ${IDF_VER}") 63 | endif() 64 | 65 | idf_component_get_property(main_args esptool_py FLASH_ARGS) 66 | idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) 67 | esptool_py_flash_target(${partition}-flash "${main_args}" "${sub_args}") 68 | esptool_py_flash_target_image(${partition}-flash "${partition}" "${offset}" "${image_file}") 69 | 70 | add_dependencies(${partition}-flash littlefs_${partition}_bin) 71 | 72 | if(arg_FLASH_IN_PROJECT) 73 | esptool_py_flash_target_image(flash "${partition}" "${offset}" "${image_file}") 74 | add_dependencies(flash littlefs_${partition}_bin) 75 | endif() 76 | 77 | else() 78 | set(message "Failed to create littlefs image for partition '${partition}'. " 79 | "Check project configuration if using the correct partition table file." 80 | ) 81 | fail_at_build_time(littlefs_${partition}_bin "${message}") 82 | endif() 83 | endfunction() 84 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # 2 | # Partition Table 3 | # 4 | CONFIG_PARTITION_TABLE_CUSTOM=y 5 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table_unit_test_app.csv" 6 | CONFIG_PARTITION_TABLE_FILENAME="partition_table_unit_test_app.csv" 7 | CONFIG_PARTITION_TABLE_OFFSET=0x8000 8 | CONFIG_PARTITION_TABLE_MD5=y 9 | 10 | # 11 | # Heap 12 | # 13 | CONFIG_HEAP_POISONING_COMPREHENSIVE=y 14 | 15 | # 16 | # Watchdog 17 | # 18 | CONFIG_ESP_TASK_WDT=y 19 | CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n 20 | CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n 21 | 22 | # 23 | # ESP32-specific 24 | # 25 | CONFIG_IDF_TARGET_ESP32=y 26 | CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y 27 | CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 28 | 29 | CONFIG_ESP32_XTAL_FREQ_AUTO=y 30 | 31 | # 32 | # Serial flasher config 33 | # 34 | CONFIG_ESPTOOLPY_BAUD_921600B=y 35 | CONFIG_ESPTOOLPY_COMPRESSED=y 36 | CONFIG_ESPTOOLPY_FLASHMODE_QIO=y 37 | CONFIG_ESPTOOLPY_FLASHFREQ_80M=y 38 | CONFIG_ESPTOOLPY_FLASHFREQ="80m" 39 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 40 | CONFIG_ESPTOOLPY_FLASHSIZE="4MB" 41 | CONFIG_ESPTOOLPY_BEFORE_RESET=y 42 | CONFIG_ESPTOOLPY_BEFORE="default_reset" 43 | CONFIG_ESPTOOLPY_AFTER_RESET=y 44 | CONFIG_ESPTOOLPY_AFTER_NORESET=n 45 | CONFIG_ESPTOOLPY_AFTER="hard_reset" 46 | CONFIG_ESPTOOLPY_MONITOR_BAUD_CONSOLE=y 47 | CONFIG_ESPTOOLPY_FLASHSIZE="4MB" 48 | CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=n 49 | 50 | CONFIG_ESP_CONSOLE_UART_NUM=0 51 | 52 | # 53 | # SPI Flash driver 54 | # 55 | CONFIG_SPI_FLASH_VERIFY_WRITE=n 56 | CONFIG_SPI_FLASH_ENABLE_COUNTERS=n 57 | CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y 58 | CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y 59 | CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=n 60 | CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED=n 61 | 62 | # 63 | # SPIFFS Configuration 64 | # 65 | CONFIG_SPIFFS_MAX_PARTITIONS=3 66 | 67 | # 68 | # SPIFFS Cache Configuration 69 | # 70 | CONFIG_SPIFFS_CACHE=y 71 | CONFIG_SPIFFS_CACHE_WR=y 72 | CONFIG_SPIFFS_CACHE_STATS=n 73 | CONFIG_SPIFFS_PAGE_CHECK=y 74 | CONFIG_SPIFFS_GC_MAX_RUNS=10 75 | CONFIG_SPIFFS_GC_STATS=n 76 | CONFIG_SPIFFS_PAGE_SIZE=256 77 | CONFIG_SPIFFS_OBJ_NAME_LEN=32 78 | CONFIG_SPIFFS_USE_MAGIC=y 79 | CONFIG_SPIFFS_USE_MAGIC_LENGTH=y 80 | CONFIG_SPIFFS_META_LENGTH=4 81 | CONFIG_SPIFFS_USE_MTIME=n 82 | 83 | # 84 | # FAT Filesystem support 85 | # 86 | CONFIG_FATFS_CODEPAGE_DYNAMIC=n 87 | CONFIG_FATFS_CODEPAGE_437=y 88 | CONFIG_FATFS_CODEPAGE_720=n 89 | CONFIG_FATFS_CODEPAGE_737=n 90 | CONFIG_FATFS_CODEPAGE_771=n 91 | CONFIG_FATFS_CODEPAGE_775=n 92 | CONFIG_FATFS_CODEPAGE_850=n 93 | CONFIG_FATFS_CODEPAGE_852=n 94 | CONFIG_FATFS_CODEPAGE_855=n 95 | CONFIG_FATFS_CODEPAGE_857=n 96 | CONFIG_FATFS_CODEPAGE_860=n 97 | CONFIG_FATFS_CODEPAGE_861=n 98 | CONFIG_FATFS_CODEPAGE_862=n 99 | CONFIG_FATFS_CODEPAGE_863=n 100 | CONFIG_FATFS_CODEPAGE_864=n 101 | CONFIG_FATFS_CODEPAGE_865=n 102 | CONFIG_FATFS_CODEPAGE_866=n 103 | CONFIG_FATFS_CODEPAGE_869=n 104 | CONFIG_FATFS_CODEPAGE_932=n 105 | CONFIG_FATFS_CODEPAGE_936=n 106 | CONFIG_FATFS_CODEPAGE_949=n 107 | CONFIG_FATFS_CODEPAGE_950=n 108 | CONFIG_FATFS_CODEPAGE=437 109 | CONFIG_FATFS_LFN_NONE=y 110 | CONFIG_FATFS_LFN_HEAP=n 111 | CONFIG_FATFS_LFN_STACK=n 112 | CONFIG_FATFS_FS_LOCK=0 113 | CONFIG_FATFS_TIMEOUT_MS=10000 114 | CONFIG_FATFS_PER_FILE_CACHE=y 115 | 116 | CONFIG_UNITY_FREERTOS_PRIORITY=5 117 | CONFIG_UNITY_FREERTOS_CPU=0 118 | CONFIG_UNITY_FREERTOS_STACK_SIZE=12000 119 | CONFIG_UNITY_WARN_LEAK_LEVEL_GENERAL=255 120 | CONFIG_UNITY_CRITICAL_LEAK_LEVEL_GENERAL=1024 121 | CONFIG_UNITY_CRITICAL_LEAK_LEVEL_LWIP=4095 122 | CONFIG_UNITY_ENABLE_FLOAT=y 123 | CONFIG_UNITY_ENABLE_DOUBLE=y 124 | CONFIG_UNITY_ENABLE_COLOR=y 125 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y 126 | CONFIG_UNITY_ENABLE_FIXTURE=y 127 | CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y 128 | 129 | # BOOTLOADER 130 | CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y 131 | -------------------------------------------------------------------------------- /src/lfs_config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs util functions 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include "lfs_config.h" 8 | 9 | const char ESP_LITTLEFS_TAG[] = "esp_littlefs"; 10 | 11 | // Software CRC implementation with small lookup table 12 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 13 | static const uint32_t rtable[16] = { 14 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 15 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 16 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 17 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 18 | }; 19 | 20 | const uint8_t *data = buffer; 21 | 22 | for (size_t i = 0; i < size; i++) { 23 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 24 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 25 | } 26 | 27 | return crc; 28 | } 29 | -------------------------------------------------------------------------------- /src/lfs_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs utility functions 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS_CFG_H 8 | #define LFS_CFG_H 9 | 10 | // System includes 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "sdkconfig.h" 16 | #include "esp_log.h" 17 | 18 | 19 | #if defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT) || \ 20 | defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL) || \ 21 | defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM) 22 | #include 23 | #include "esp_heap_caps.h" 24 | #endif 25 | 26 | #ifdef CONFIG_LITTLEFS_ASSERTS 27 | #include 28 | #endif 29 | 30 | #if !defined(LFS_NO_DEBUG) || \ 31 | !defined(LFS_NO_WARN) || \ 32 | !defined(LFS_NO_ERROR) || \ 33 | defined(LFS_YES_TRACE) 34 | #include 35 | #endif 36 | 37 | #ifdef __cplusplus 38 | extern "C" 39 | { 40 | #endif 41 | 42 | 43 | // Macros, may be replaced by system specific wrappers. Arguments to these 44 | // macros must not have side-effects as the macros can be removed for a smaller 45 | // code footprint 46 | extern const char ESP_LITTLEFS_TAG[]; 47 | 48 | // Logging functions 49 | #ifndef LFS_TRACE 50 | #ifdef LFS_YES_TRACE 51 | #define LFS_TRACE_(fmt, ...) \ 52 | ESP_LOGV(ESP_LITTLEFS_TAG, "%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 53 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 54 | #else 55 | #define LFS_TRACE(...) 56 | #endif 57 | #endif 58 | 59 | #ifndef LFS_DEBUG 60 | #ifndef LFS_NO_DEBUG 61 | #define LFS_DEBUG_(fmt, ...) \ 62 | ESP_LOGD(ESP_LITTLEFS_TAG, "%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 63 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 64 | #else 65 | #define LFS_DEBUG(...) 66 | #endif 67 | #endif 68 | 69 | #ifndef LFS_WARN 70 | #ifndef LFS_NO_WARN 71 | #define LFS_WARN_(fmt, ...) \ 72 | ESP_LOGW(ESP_LITTLEFS_TAG, "%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 73 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 74 | #else 75 | #define LFS_WARN(...) 76 | #endif 77 | #endif 78 | 79 | #ifndef LFS_ERROR 80 | #ifndef LFS_NO_ERROR 81 | #define LFS_ERROR_(fmt, ...) \ 82 | ESP_LOGE(ESP_LITTLEFS_TAG, "%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 83 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 84 | #else 85 | #define LFS_ERROR(...) 86 | #endif 87 | #endif 88 | 89 | // Runtime assertions 90 | #ifdef CONFIG_LITTLEFS_ASSERTS 91 | #define LFS_ASSERT(test) assert(test) 92 | #else 93 | #define LFS_ASSERT(test) 94 | #endif 95 | 96 | 97 | // Builtin functions, these may be replaced by more efficient 98 | // toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more 99 | // expensive basic C implementation for debugging purposes 100 | 101 | // Min/max functions for unsigned 32-bit numbers 102 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 103 | return (a > b) ? a : b; 104 | } 105 | 106 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 107 | return (a < b) ? a : b; 108 | } 109 | 110 | // Align to nearest multiple of a size 111 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 112 | return a - (a % alignment); 113 | } 114 | 115 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 116 | return lfs_aligndown(a + alignment-1, alignment); 117 | } 118 | 119 | // Find the smallest power of 2 greater than or equal to a 120 | static inline uint32_t lfs_npw2(uint32_t a) { 121 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 122 | return 32 - __builtin_clz(a-1); 123 | #else 124 | uint32_t r = 0; 125 | uint32_t s; 126 | a -= 1; 127 | s = (a > 0xffff) << 4; a >>= s; r |= s; 128 | s = (a > 0xff ) << 3; a >>= s; r |= s; 129 | s = (a > 0xf ) << 2; a >>= s; r |= s; 130 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 131 | return (r | (a >> 1)) + 1; 132 | #endif 133 | } 134 | 135 | // Count the number of trailing binary zeros in a 136 | // lfs_ctz(0) may be undefined 137 | static inline uint32_t lfs_ctz(uint32_t a) { 138 | #if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) 139 | return __builtin_ctz(a); 140 | #else 141 | return lfs_npw2((a & -a) + 1) - 1; 142 | #endif 143 | } 144 | 145 | // Count the number of binary ones in a 146 | static inline uint32_t lfs_popc(uint32_t a) { 147 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 148 | return __builtin_popcount(a); 149 | #else 150 | a = a - ((a >> 1) & 0x55555555); 151 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 152 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 153 | #endif 154 | } 155 | 156 | // Find the sequence comparison of a and b, this is the distance 157 | // between a and b ignoring overflow 158 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 159 | return (int)(unsigned)(a - b); 160 | } 161 | 162 | // Convert between 32-bit little-endian and native order 163 | static inline uint32_t lfs_fromle32(uint32_t a) { 164 | #if !defined(LFS_NO_INTRINSICS) && ( \ 165 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 166 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 167 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 168 | return a; 169 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 170 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 171 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 172 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 173 | return __builtin_bswap32(a); 174 | #else 175 | return (((uint8_t*)&a)[0] << 0) | 176 | (((uint8_t*)&a)[1] << 8) | 177 | (((uint8_t*)&a)[2] << 16) | 178 | (((uint8_t*)&a)[3] << 24); 179 | #endif 180 | } 181 | 182 | static inline uint32_t lfs_tole32(uint32_t a) { 183 | return lfs_fromle32(a); 184 | } 185 | 186 | // Convert between 32-bit big-endian and native order 187 | static inline uint32_t lfs_frombe32(uint32_t a) { 188 | #if !defined(LFS_NO_INTRINSICS) && ( \ 189 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 190 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 191 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 192 | return __builtin_bswap32(a); 193 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 194 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 195 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 196 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 197 | return a; 198 | #else 199 | return (((uint8_t*)&a)[0] << 24) | 200 | (((uint8_t*)&a)[1] << 16) | 201 | (((uint8_t*)&a)[2] << 8) | 202 | (((uint8_t*)&a)[3] << 0); 203 | #endif 204 | } 205 | 206 | static inline uint32_t lfs_tobe32(uint32_t a) { 207 | return lfs_frombe32(a); 208 | } 209 | 210 | // Calculate CRC-32 with polynomial = 0x04c11db7 211 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 212 | 213 | // Allocate memory, only used if buffers are not provided to littlefs 214 | // For the lookahead buffer, memory must be 32-bit aligned 215 | static inline void *lfs_malloc(size_t size) { 216 | #if defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT) 217 | return malloc(size); // Equivalent to heap_caps_malloc_default(size); 218 | #elif defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL) 219 | return heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); 220 | #elif defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM) 221 | return heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); 222 | #else // CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE or not defined 223 | (void)size; 224 | return NULL; 225 | #endif 226 | } 227 | 228 | // Deallocate memory, only used if buffers are not provided to littlefs 229 | static inline void lfs_free(void *p) { 230 | #if defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_DEFAULT) || \ 231 | defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL) || \ 232 | defined(CONFIG_LITTLEFS_MALLOC_STRATEGY_SPIRAM) 233 | free(p); 234 | #else // CONFIG_LITTLEFS_MALLOC_STRATEGY_DISABLE or not defined 235 | (void)p; 236 | #endif 237 | } 238 | 239 | 240 | #ifdef __cplusplus 241 | } /* extern "C" */ 242 | #endif 243 | 244 | #endif 245 | -------------------------------------------------------------------------------- /src/littlefs_api.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP_LITTLEFS_API_H__ 2 | #define ESP_LITTLEFS_API_H__ 3 | 4 | #include 5 | #include 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | #include "freertos/semphr.h" 9 | #include "esp_vfs.h" 10 | #include "esp_partition.h" 11 | #include "littlefs/lfs.h" 12 | #include "sdkconfig.h" 13 | 14 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 15 | #include 16 | #endif 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #if CONFIG_LITTLEFS_USE_MTIME 23 | #define ESP_LITTLEFS_ATTR_COUNT 1 24 | #else 25 | #define ESP_LITTLEFS_ATTR_COUNT 0 26 | #endif 27 | 28 | /** 29 | * @brief a file descriptor 30 | * That's also a singly linked list used for keeping tracks of all opened file descriptor 31 | * 32 | * Shortcomings/potential issues of 32-bit hash (when CONFIG_LITTLEFS_USE_ONLY_HASH) listed here: 33 | * * unlink - If a different file is open that generates a hash collision, it will report an 34 | * error that it cannot unlink an open file. 35 | * * rename - If a different file is open that generates a hash collision with 36 | * src or dst, it will report an error that it cannot rename an open file. 37 | * Potential consequences: 38 | * 1. A file cannot be deleted while a collision-geneating file is open. 39 | * Worst-case, if the other file is always open during the lifecycle 40 | * of your app, it's collision file cannot be deleted, which in the 41 | * worst-case could cause storage-capacity issues. 42 | * 2. Same as (1), but for renames 43 | */ 44 | typedef struct _vfs_littlefs_file_t { 45 | lfs_file_t file; 46 | 47 | /* Allocate all other necessary buffers */ 48 | struct lfs_file_config lfs_file_config; 49 | uint8_t lfs_buffer[CONFIG_LITTLEFS_CACHE_SIZE]; 50 | #if ESP_LITTLEFS_ATTR_COUNT 51 | struct lfs_attr lfs_attr[ESP_LITTLEFS_ATTR_COUNT]; 52 | time_t lfs_attr_time_buffer; 53 | #endif 54 | 55 | uint32_t hash; 56 | struct _vfs_littlefs_file_t * next; /*!< Pointer to next file in Singly Linked List */ 57 | #ifndef CONFIG_LITTLEFS_USE_ONLY_HASH 58 | char * path; 59 | #endif 60 | } vfs_littlefs_file_t; 61 | 62 | /** 63 | * @brief littlefs definition structure 64 | */ 65 | typedef struct { 66 | lfs_t *fs; /*!< Handle to the underlying littlefs */ 67 | SemaphoreHandle_t lock; /*!< FS lock */ 68 | 69 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 70 | sdmmc_card_t *sdcard; /*!< The SD card driver handle on which littlefs is located */ 71 | #endif 72 | 73 | const esp_partition_t* partition; /*!< The partition on which littlefs is located */ 74 | 75 | #ifdef CONFIG_LITTLEFS_MMAP_PARTITION 76 | const void *mmap_data; /*!< Buffer of mmapped partition */ 77 | esp_partition_mmap_handle_t mmap_handle; /*!< Handle to mmapped partition */ 78 | #endif 79 | 80 | char base_path[ESP_VFS_PATH_MAX+1]; /*!< Mount point */ 81 | 82 | struct lfs_config cfg; /*!< littlefs Mount configuration */ 83 | 84 | vfs_littlefs_file_t *file; /*!< Singly Linked List of files */ 85 | 86 | vfs_littlefs_file_t **cache; /*!< A cache of pointers to the opened files */ 87 | uint16_t cache_size; /*!< The cache allocated size (in pointers) */ 88 | uint16_t fd_count; /*!< The count of opened file descriptor used to speed up computation */ 89 | bool read_only; /*!< Filesystem is read-only */ 90 | } esp_littlefs_t; 91 | 92 | #ifdef CONFIG_LITTLEFS_MMAP_PARTITION 93 | /** 94 | * @brief Read a region in a block, only for use with an mmapped partition. 95 | * 96 | * Negative error codes are propogated to the user. 97 | * 98 | * @return errorcode. 0 on success. 99 | */ 100 | int littlefs_esp_part_read_mmap(const struct lfs_config *c, lfs_block_t block, 101 | lfs_off_t off, void *buffer, lfs_size_t size); 102 | #endif 103 | 104 | /** 105 | * @brief Read a region in a block. 106 | * 107 | * Negative error codes are propogated to the user. 108 | * 109 | * @return errorcode. 0 on success. 110 | */ 111 | int littlefs_esp_part_read(const struct lfs_config *c, lfs_block_t block, 112 | lfs_off_t off, void *buffer, lfs_size_t size); 113 | 114 | /** 115 | * @brief Program a region in a block. 116 | * 117 | * The block must have previously been erased. 118 | * Negative error codes are propogated to the user. 119 | * May return LFS_ERR_CORRUPT if the block should be considered bad. 120 | * 121 | * @return errorcode. 0 on success. 122 | */ 123 | int littlefs_esp_part_write(const struct lfs_config *c, lfs_block_t block, 124 | lfs_off_t off, const void *buffer, lfs_size_t size); 125 | 126 | /** 127 | * @brief Erase a block. 128 | * 129 | * A block must be erased before being programmed. 130 | * The state of an erased block is undefined. 131 | * Negative error codes are propogated to the user. 132 | * May return LFS_ERR_CORRUPT if the block should be considered bad. 133 | * @return errorcode. 0 on success. 134 | */ 135 | int littlefs_esp_part_erase(const struct lfs_config *c, lfs_block_t block); 136 | 137 | /** 138 | * @brief Sync the state of the underlying block device. 139 | * 140 | * Negative error codes are propogated to the user. 141 | * 142 | * @return errorcode. 0 on success. 143 | */ 144 | int littlefs_esp_part_sync(const struct lfs_config *c); 145 | 146 | #ifdef CONFIG_LITTLEFS_SDMMC_SUPPORT 147 | 148 | /** 149 | * @brief Read a region in a block on SD card 150 | * 151 | * Negative error codes are propogated to the user. 152 | * 153 | * @return errorcode. 0 on success. 154 | */ 155 | int littlefs_sdmmc_read(const struct lfs_config *c, lfs_block_t block, 156 | lfs_off_t off, void *buffer, lfs_size_t size); 157 | 158 | /** 159 | * @brief Program a region in a block on SD card. 160 | * 161 | * The block must have previously been erased. 162 | * Negative error codes are propogated to the user. 163 | * May return LFS_ERR_CORRUPT if the block should be considered bad. 164 | * 165 | * @return errorcode. 0 on success. 166 | */ 167 | int littlefs_sdmmc_write(const struct lfs_config *c, lfs_block_t block, 168 | lfs_off_t off, const void *buffer, lfs_size_t size); 169 | 170 | /** 171 | * @brief Erase a block on SD card. 172 | * 173 | * A block must be erased before being programmed. 174 | * The state of an erased block is undefined. 175 | * Negative error codes are propogated to the user. 176 | * May return LFS_ERR_CORRUPT if the block should be considered bad. 177 | * @return errorcode. 0 on success. 178 | */ 179 | int littlefs_sdmmc_erase(const struct lfs_config *c, lfs_block_t block); 180 | 181 | /** 182 | * @brief Sync the state of the underlying SD card. 183 | * 184 | * Negative error codes are propogated to the user. 185 | * 186 | * @return errorcode. 0 on success. 187 | */ 188 | int littlefs_sdmmc_sync(const struct lfs_config *c); 189 | 190 | #endif // CONFIG_LITTLEFS_SDMMC_SUPPORT 191 | 192 | #ifdef __cplusplus 193 | } 194 | #endif 195 | 196 | #endif 197 | -------------------------------------------------------------------------------- /src/littlefs_esp_part.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file littlefs_api.c 3 | * @brief Maps the HAL of esp_partition <-> littlefs 4 | * @author Brian Pugh 5 | */ 6 | 7 | //#define ESP_LOCAL_LOG_LEVEL ESP_LOG_INFO 8 | 9 | #include "esp_log.h" 10 | #include "esp_partition.h" 11 | #include "esp_vfs.h" 12 | #include "littlefs/lfs.h" 13 | #include "esp_littlefs.h" 14 | #include "littlefs_api.h" 15 | 16 | #ifdef CONFIG_LITTLEFS_MMAP_PARTITION 17 | int littlefs_esp_part_read_mmap(const struct lfs_config *c, lfs_block_t block, 18 | lfs_off_t off, void *buffer, lfs_size_t size) { 19 | esp_littlefs_t * efs = c->context; 20 | size_t part_off = (block * c->block_size) + off; 21 | if (part_off > efs->partition->size || part_off + size > efs->partition->size) { 22 | ESP_LOGE(ESP_LITTLEFS_TAG, "attempt to read out bounds of mmaped region %08x-%08x", (unsigned int)part_off, (unsigned int)(part_off + size)); 23 | return LFS_ERR_IO; 24 | } 25 | memcpy(buffer, efs->mmap_data + part_off, size); 26 | return 0; 27 | } 28 | #endif 29 | 30 | int littlefs_esp_part_read(const struct lfs_config *c, lfs_block_t block, 31 | lfs_off_t off, void *buffer, lfs_size_t size) { 32 | esp_littlefs_t * efs = c->context; 33 | size_t part_off = (block * c->block_size) + off; 34 | esp_err_t err = esp_partition_read(efs->partition, part_off, buffer, size); 35 | if (err) { 36 | ESP_LOGE(ESP_LITTLEFS_TAG, "failed to read addr %08x, size %08x, err %d", (unsigned int) part_off, (unsigned int) size, err); 37 | return LFS_ERR_IO; 38 | } 39 | return 0; 40 | } 41 | 42 | int littlefs_esp_part_write(const struct lfs_config *c, lfs_block_t block, 43 | lfs_off_t off, const void *buffer, lfs_size_t size) { 44 | esp_littlefs_t * efs = c->context; 45 | size_t part_off = (block * c->block_size) + off; 46 | esp_err_t err = esp_partition_write(efs->partition, part_off, buffer, size); 47 | if (err) { 48 | ESP_LOGE(ESP_LITTLEFS_TAG, "failed to write addr %08x, size %08x, err %d", (unsigned int) part_off, (unsigned int) size, err); 49 | return LFS_ERR_IO; 50 | } 51 | return 0; 52 | } 53 | 54 | int littlefs_esp_part_erase(const struct lfs_config *c, lfs_block_t block) { 55 | esp_littlefs_t * efs = c->context; 56 | size_t part_off = block * c->block_size; 57 | esp_err_t err = esp_partition_erase_range(efs->partition, part_off, c->block_size); 58 | if (err) { 59 | ESP_LOGE(ESP_LITTLEFS_TAG, "failed to erase addr %08x, size %08x, err %d", (unsigned int) part_off, (unsigned int) c->block_size, err); 60 | return LFS_ERR_IO; 61 | } 62 | return 0; 63 | 64 | } 65 | 66 | int littlefs_esp_part_sync(const struct lfs_config *c) { 67 | /* Unnecessary for esp-idf */ 68 | return 0; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/littlefs_sdmmc.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file littlefs_sdmmc.c 3 | * @brief Maps the HAL of sdmmc driver <-> littlefs 4 | * @author Jackson Ming Hu 5 | */ 6 | 7 | #include 8 | #include 9 | #include "littlefs_api.h" 10 | 11 | #if CONFIG_LITTLEFS_SDMMC_SUPPORT 12 | 13 | int littlefs_sdmmc_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) 14 | { 15 | esp_littlefs_t * efs = c->context; 16 | uint32_t part_off = (block * c->block_size) + off; 17 | 18 | esp_err_t ret = sdmmc_read_sectors(efs->sdcard, buffer, block, MIN(size / efs->cfg.read_size, 1)); 19 | if (ret != ESP_OK) { 20 | ESP_LOGE(ESP_LITTLEFS_TAG, "Failed to read addr 0x%08lx: off 0x%08lx, block 0x%08lx, size %lu, err=0x%x", part_off, off, block, size, ret); 21 | return LFS_ERR_IO; 22 | } 23 | 24 | return LFS_ERR_OK; 25 | } 26 | 27 | int littlefs_sdmmc_write(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) 28 | { 29 | esp_littlefs_t * efs = c->context; 30 | uint32_t part_off = (block * c->block_size) + off; 31 | 32 | esp_err_t ret = sdmmc_write_sectors(efs->sdcard, buffer, block, MIN(size / efs->cfg.prog_size, 1)); 33 | if (ret != ESP_OK) { 34 | ESP_LOGE(ESP_LITTLEFS_TAG, "Failed to write addr 0x%08lx: off 0x%08lx, block 0x%08lx, size %lu, err=0x%x", part_off, off, block, size, ret); 35 | return LFS_ERR_IO; 36 | } 37 | 38 | return LFS_ERR_OK; 39 | } 40 | 41 | int littlefs_sdmmc_erase(const struct lfs_config *c, lfs_block_t block) 42 | { 43 | esp_littlefs_t * efs = c->context; 44 | esp_err_t ret = sdmmc_erase_sectors(efs->sdcard, block, 1, SDMMC_ERASE_ARG); 45 | if (ret != ESP_OK) { 46 | ESP_LOGE(ESP_LITTLEFS_TAG, "Failed to erase block %lu: ret=0x%x %s", block, ret, esp_err_to_name(ret)); 47 | return LFS_ERR_IO; 48 | } 49 | 50 | return LFS_ERR_OK; 51 | } 52 | 53 | int littlefs_sdmmc_sync(const struct lfs_config *c) 54 | { 55 | return LFS_ERR_OK; // Doesn't require & doesn't support sync 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRC_DIRS "." 2 | INCLUDE_DIRS "." 3 | REQUIRES spi_flash unity test_utils littlefs spiffs fatfs esp_timer vfs) 4 | 5 | target_add_binary_data(${COMPONENT_TARGET} "./testfs.bin" BINARY) -------------------------------------------------------------------------------- /test/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive 2 | -------------------------------------------------------------------------------- /test/test_benchmark.c: -------------------------------------------------------------------------------- 1 | #include "test_littlefs_common.h" 2 | #include "esp_vfs_fat.h" 3 | 4 | static const char TAG[] = "[littlefs_benchmark]"; 5 | 6 | // Handle of the wear levelling library instance 7 | wl_handle_t s_wl_handle = WL_INVALID_HANDLE; 8 | #define MAX_FILES 5 9 | 10 | static void setup_spiffs(){ 11 | ESP_LOGI(TAG, "Initializing SPIFFS"); 12 | 13 | esp_spiffs_format("spiffs_store"); 14 | 15 | esp_vfs_spiffs_conf_t conf = { 16 | .base_path = "/spiffs", 17 | .partition_label = "spiffs_store", 18 | .max_files = MAX_FILES, 19 | .format_if_mount_failed = true 20 | }; 21 | 22 | esp_err_t ret = esp_vfs_spiffs_register(&conf); 23 | 24 | if (ret != ESP_OK) { 25 | if (ret == ESP_FAIL) { 26 | ESP_LOGE(TAG, "Failed to mount or format filesystem"); 27 | } else if (ret == ESP_ERR_NOT_FOUND) { 28 | ESP_LOGE(TAG, "Failed to find SPIFFS partition"); 29 | } else { 30 | ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); 31 | } 32 | TEST_FAIL(); 33 | return; 34 | } 35 | } 36 | 37 | static void setup_fat(){ 38 | const esp_vfs_fat_mount_config_t conf = { 39 | .max_files = MAX_FILES, 40 | .format_if_mount_failed = true, 41 | .allocation_unit_size = CONFIG_WL_SECTOR_SIZE 42 | }; 43 | esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl("/fat", "fat_store", &conf, &s_wl_handle); 44 | if (err != ESP_OK) { 45 | ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); 46 | TEST_FAIL(); 47 | return; 48 | } 49 | } 50 | 51 | static void setup_littlefs() { 52 | esp_vfs_littlefs_conf_t conf = { 53 | .base_path = "/littlefs", 54 | .partition_label = "flash_test", 55 | .format_if_mount_failed = true 56 | }; 57 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 58 | esp_littlefs_format("flash_test"); 59 | } 60 | 61 | static void test_benchmark_setup() { 62 | setup_fat(); 63 | setup_spiffs(); 64 | setup_littlefs(); 65 | printf("Test setup complete.\n"); 66 | } 67 | 68 | static void test_benchmark_teardown() 69 | { 70 | assert(ESP_OK == esp_vfs_fat_spiflash_unmount_rw_wl("/fat", s_wl_handle)); 71 | TEST_ESP_OK(esp_vfs_spiffs_unregister("spiffs_store")); 72 | TEST_ESP_OK(esp_vfs_littlefs_unregister("flash_test")); 73 | printf("Test teardown complete.\n"); 74 | } 75 | 76 | /** 77 | * @brief Fill partitions with dummy data 78 | */ 79 | static void fill_partitions() 80 | { 81 | const esp_partition_t* part; 82 | const char dummy_data[] = { 83 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 84 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 85 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 86 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 87 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 88 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 89 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6', 90 | 'D','U','M','M','Y','D','A','T','A','0','1','2', '3', '4', '5', '6' 91 | }; 92 | 93 | ESP_LOGI(TAG, "Filling SPIFFS partition with dummy data"); 94 | part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, 95 | "spiffs_store"); 96 | esp_partition_erase_range(part, 0, part->size); 97 | for(uint32_t i=0; i < part->size; i += sizeof(dummy_data)) 98 | esp_partition_write(part, i, dummy_data, sizeof(dummy_data)); 99 | 100 | ESP_LOGI(TAG, "Filling FAT partition with dummy data"); 101 | part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, 102 | "fat_store"); 103 | esp_partition_erase_range(part, 0, part->size); 104 | for(uint32_t i=0; i < part->size; i += sizeof(dummy_data)) 105 | esp_partition_write(part, i, dummy_data, sizeof(dummy_data)); 106 | 107 | ESP_LOGI(TAG, "Filling LittleFS partition with dummy data"); 108 | part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, 109 | "flash_test"); 110 | esp_partition_erase_range(part, 0, part->size); 111 | for(uint32_t i=0; i < part->size; i += sizeof(dummy_data)) 112 | esp_partition_write(part, i, dummy_data, sizeof(dummy_data)); 113 | } 114 | 115 | static int get_file_size(const char *fname) { 116 | struct stat sb; 117 | 118 | if( 0 != stat(fname, &sb) ) { 119 | return -1; 120 | } 121 | 122 | return sb.st_size; 123 | } 124 | 125 | /** 126 | * @brief Writes and deletes files 127 | * @param[in] mount_pt 128 | * @param[in] iter Number of files to write 129 | */ 130 | static void read_write_test_1(const char *mount_pt, uint32_t iter) { 131 | char fmt_fn[64] = { 0 }; 132 | char fname[128] = { 0 }; 133 | uint64_t t_write = 0; 134 | uint64_t t_read = 0; 135 | uint64_t t_delete = 0; 136 | 137 | int n_write = 0; 138 | int n_read = 0; 139 | int n_delete = 0; 140 | 141 | strcat(fmt_fn, mount_pt); 142 | if(fmt_fn[strlen(fmt_fn)-1] != '/') strcat(fmt_fn, "/"); 143 | strcat(fmt_fn, "%d.txt"); 144 | 145 | /* WRITE */ 146 | for(uint8_t i=0; i < iter; i++){ 147 | snprintf(fname, sizeof(fname), fmt_fn, i); 148 | uint64_t t_start = esp_timer_get_time(); 149 | FILE* f = fopen(fname, "w"); 150 | if (f == NULL) { 151 | ESP_LOGE(TAG, "Failed to open file %d for writing", i); 152 | continue; 153 | } 154 | for(uint32_t j=0; j < 2000; j++) { 155 | fprintf(f, "All work and no play makes Jack a dull boy.\n"); 156 | } 157 | fclose(f); 158 | uint64_t t_end = esp_timer_get_time(); 159 | int fsize = get_file_size(fname); 160 | printf("%d bytes written in %lld us\n", fsize, (t_end - t_start)); 161 | t_write += (t_end - t_start); 162 | n_write += fsize; 163 | } 164 | 165 | printf("------------\n"); 166 | 167 | /* READ */ 168 | for(uint8_t i=0; i < iter; i++){ 169 | int fsize = 0; 170 | snprintf(fname, sizeof(fname), fmt_fn, i); 171 | uint64_t t_start = esp_timer_get_time(); 172 | FILE* f = fopen(fname, "r"); 173 | if (f == NULL) { 174 | ESP_LOGE(TAG, "Failed to open file %d for reading", i); 175 | continue; 176 | } 177 | 178 | while(fgetc(f) != EOF) fsize ++; 179 | 180 | fclose(f); 181 | uint64_t t_end = esp_timer_get_time(); 182 | printf("%d bytes read in %lld us\n", fsize, (t_end - t_start)); 183 | t_read += (t_end - t_start); 184 | n_read += fsize; 185 | } 186 | 187 | printf("------------\n"); 188 | 189 | /* DELETE */ 190 | for(uint8_t i=0; i < iter; i++){ 191 | snprintf(fname, sizeof(fname), fmt_fn, i); 192 | 193 | int fsize = get_file_size(fname); 194 | if (fsize < 0) { 195 | continue; 196 | } 197 | 198 | uint64_t t_start = esp_timer_get_time(); 199 | snprintf(fname, sizeof(fname), fmt_fn, i); 200 | unlink(fname); 201 | uint64_t t_end = esp_timer_get_time(); 202 | printf("deleted file %d in %lld us\n", i, (t_end - t_start)); 203 | t_delete += (t_end - t_start); 204 | n_delete += fsize; 205 | } 206 | 207 | printf("------------\n"); 208 | 209 | printf("Total (%d) Write: %lld us\n", n_write, t_write); 210 | printf("Total (%d) Read: %lld us\n", n_read, t_read); 211 | printf("Total (%d) Delete: %lld us\n", n_delete, t_delete); 212 | printf("\n"); 213 | } 214 | 215 | 216 | TEST_CASE("Format", TAG){ 217 | uint64_t t_fat, t_spiffs, t_littlefs, t_start; 218 | 219 | fill_partitions(); 220 | 221 | t_start = esp_timer_get_time(); 222 | esp_spiffs_format(NULL); 223 | t_spiffs = esp_timer_get_time() - t_start; 224 | printf("SPIFFS Formatted in %lld us\n", t_spiffs); 225 | 226 | t_start = esp_timer_get_time(); 227 | setup_fat(); 228 | assert(ESP_OK == esp_vfs_fat_spiflash_unmount("/fat", s_wl_handle)); 229 | t_fat = esp_timer_get_time() - t_start; 230 | printf("FAT Formatted in %lld us\n", t_fat); 231 | 232 | t_start = esp_timer_get_time(); 233 | esp_littlefs_format("flash_test"); 234 | t_littlefs = esp_timer_get_time() - t_start; 235 | printf("LittleFS Formatted in %lld us\n", t_littlefs); 236 | 237 | } 238 | 239 | TEST_CASE("Write 5 files, read 5 files, then delete 5 files", TAG){ 240 | test_benchmark_setup(); 241 | 242 | printf("FAT:\n"); 243 | read_write_test_1("/fat", 5); 244 | printf("\n"); 245 | 246 | printf("SPIFFS:\n"); 247 | read_write_test_1("/spiffs", 5); 248 | printf("\n"); 249 | 250 | printf("LittleFS:\n"); 251 | read_write_test_1("/littlefs", 5); 252 | printf("\n"); 253 | 254 | test_benchmark_teardown(); 255 | } 256 | -------------------------------------------------------------------------------- /test/test_dir.c: -------------------------------------------------------------------------------- 1 | #include "sdkconfig.h" 2 | 3 | #ifdef CONFIG_VFS_SUPPORT_DIR 4 | 5 | #include "test_littlefs_common.h" 6 | 7 | //#define LOG_LOCAL_LEVEL 4 8 | 9 | TEST_CASE("truncate", "[littlefs]") 10 | { 11 | test_setup(); 12 | 13 | FILE* f; 14 | char buf[10] = { 0 }; 15 | const char fn[] = littlefs_base_path "/truncate.txt"; 16 | 17 | f = fopen(fn, "w"); 18 | TEST_ASSERT_NOT_NULL(f); 19 | TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); 20 | TEST_ASSERT_EQUAL(0, fclose(f)); 21 | 22 | TEST_ASSERT_EQUAL(0, truncate(fn, 3)); 23 | 24 | f = fopen(fn, "r"); 25 | TEST_ASSERT_NOT_NULL(f); 26 | TEST_ASSERT_EQUAL(3, fread(buf, 1, 8, f)); 27 | TEST_ASSERT_EQUAL(0, fclose(f)); 28 | TEST_ASSERT_EQUAL_STRING_LEN("012", buf, 8); 29 | 30 | test_teardown(); 31 | } 32 | 33 | #ifdef ESP_LITTLEFS_ENABLE_FTRUNCATE 34 | TEST_CASE("ftruncate", "[littlefs]") 35 | { 36 | test_setup(); 37 | 38 | int fd; 39 | FILE* f; 40 | char buf[10] = { 0 }; 41 | const char fn[] = littlefs_base_path "/truncate.txt"; 42 | 43 | f = fopen(fn, "w"); 44 | TEST_ASSERT_NOT_NULL(f); 45 | TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); 46 | TEST_ASSERT_EQUAL(0, fclose(f)); 47 | 48 | fd = open(fn, O_RDWR); 49 | TEST_ASSERT_EQUAL(0, ftruncate(fd, 3)); 50 | TEST_ASSERT_EQUAL(0, close(fd)); 51 | 52 | 53 | f = fopen(fn, "r"); 54 | TEST_ASSERT_NOT_NULL(f); 55 | TEST_ASSERT_EQUAL(3, fread(buf, 1, 8, f)); 56 | TEST_ASSERT_EQUAL(0, fclose(f)); 57 | TEST_ASSERT_EQUAL_STRING_LEN("012", buf, 8); 58 | 59 | test_teardown(); 60 | } 61 | #endif // ESP_LITTLEFS_ENABLE_FTRUNCATE 62 | 63 | 64 | static void test_littlefs_readdir_many_files(const char* dir_prefix) 65 | { 66 | const int n_files = 40; 67 | const int n_folders = 4; 68 | unsigned char file_count[n_files * n_folders]; 69 | memset(file_count, 0, sizeof(file_count)/sizeof(file_count[0])); 70 | char file_name[ESP_VFS_PATH_MAX + CONFIG_LITTLEFS_OBJ_NAME_LEN]; 71 | 72 | /* clean stale files before the test */ 73 | mkdir(dir_prefix, 0755); 74 | DIR* dir = opendir(dir_prefix); 75 | if (dir) { 76 | while (true) { 77 | struct dirent* de = readdir(dir); 78 | if (!de) { 79 | break; 80 | } 81 | int len = snprintf(file_name, sizeof(file_name), "%s/%s", dir_prefix, de->d_name); 82 | assert(len < sizeof(file_name)); 83 | unlink(file_name); 84 | } 85 | } 86 | 87 | /* create files */ 88 | for (int d = 0; d < n_folders; ++d) { 89 | printf("filling directory %d\n", d); 90 | snprintf(file_name, sizeof(file_name), "%s/%d", dir_prefix, d); 91 | mkdir( file_name, 0755 ); 92 | for (int f = 0; f < n_files; ++f) { 93 | snprintf(file_name, sizeof(file_name), "%s/%d/%d.txt", dir_prefix, d, f); 94 | test_littlefs_create_file_with_text(file_name, file_name); 95 | } 96 | } 97 | 98 | /* list files */ 99 | for (int d = 0; d < n_folders; ++d) { 100 | printf("listing files in directory %d\n", d); 101 | snprintf(file_name, sizeof(file_name), "%s/%d", dir_prefix, d); 102 | dir = opendir(file_name); 103 | TEST_ASSERT_NOT_NULL(dir); 104 | while (true) { 105 | struct dirent* de = readdir(dir); 106 | if (!de) { 107 | break; 108 | } 109 | int file_id; 110 | TEST_ASSERT_EQUAL(1, sscanf(de->d_name, "%d.txt", &file_id)); 111 | file_count[file_id + d * n_files]++; 112 | } 113 | closedir(dir); 114 | } 115 | 116 | /* check that all created files have been seen */ 117 | for (int d = 0; d < n_folders; ++d) { 118 | printf("checking that all files have been found in directory %d\n", d); 119 | for (int f = 0; f < n_files; ++f) { 120 | TEST_ASSERT_EQUAL(1, file_count[f + d * n_files]); 121 | } 122 | } 123 | } 124 | 125 | TEST_CASE("mkdir, rmdir", "[littlefs]") 126 | { 127 | test_setup(); 128 | const char filename_prefix[] = littlefs_base_path "/"; 129 | 130 | char name_dir1[64]; 131 | char name_dir2[64]; 132 | char name_dir2_file[64]; 133 | 134 | snprintf(name_dir1, sizeof(name_dir1), "%s1", filename_prefix); 135 | snprintf(name_dir2, sizeof(name_dir2), "%s2", filename_prefix); 136 | snprintf(name_dir2_file, sizeof(name_dir2_file), "%s2/1.txt", filename_prefix); 137 | 138 | TEST_ASSERT_EQUAL(0, mkdir(name_dir1, 0755)); 139 | struct stat st; 140 | TEST_ASSERT_EQUAL(0, stat(name_dir1, &st)); 141 | TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); 142 | TEST_ASSERT_FALSE(st.st_mode & S_IFREG); 143 | TEST_ASSERT_EQUAL(0, rmdir(name_dir1)); 144 | 145 | // Attempt to stat a removed directory 146 | TEST_ASSERT_EQUAL(-1, stat(name_dir1, &st)); 147 | TEST_ASSERT_EQUAL(ENOENT, errno); 148 | 149 | TEST_ASSERT_EQUAL(0, mkdir(name_dir2, 0755)); 150 | test_littlefs_create_file_with_text(name_dir2_file, "foo\n"); 151 | TEST_ASSERT_EQUAL(0, stat(name_dir2, &st)); 152 | TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); 153 | TEST_ASSERT_FALSE(st.st_mode & S_IFREG); 154 | TEST_ASSERT_EQUAL(0, stat(name_dir2_file, &st)); 155 | TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); 156 | TEST_ASSERT_TRUE(st.st_mode & S_IFREG); 157 | 158 | // Can't remove directory, its not empty 159 | TEST_ASSERT_EQUAL(-1, rmdir(name_dir2)); 160 | TEST_ASSERT_EQUAL(ENOTEMPTY, errno); 161 | 162 | TEST_ASSERT_EQUAL(0, unlink(name_dir2_file)); 163 | #if !CONFIG_LITTLEFS_SPIFFS_COMPAT 164 | /* this will have already been deleted */ 165 | TEST_ASSERT_EQUAL(0, rmdir(name_dir2)); 166 | #endif 167 | 168 | test_teardown(); 169 | } 170 | 171 | TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[littlefs]") 172 | { 173 | test_setup(); 174 | const char dir_prefix[] = littlefs_base_path "/dir"; 175 | 176 | char name_dir_inner_file[64]; 177 | char name_dir_inner[64]; 178 | char name_dir_file3[64]; 179 | char name_dir_file2[64]; 180 | char name_dir_file1[64]; 181 | 182 | snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix); 183 | snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix); 184 | snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix); 185 | snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix); 186 | snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix); 187 | 188 | /* Remove files/dirs that may exist */ 189 | unlink(name_dir_inner_file); 190 | rmdir(name_dir_inner); 191 | unlink(name_dir_file1); 192 | unlink(name_dir_file2); 193 | unlink(name_dir_file3); 194 | rmdir(dir_prefix); 195 | 196 | /* Create the files */ 197 | TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755)); 198 | TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755)); 199 | test_littlefs_create_file_with_text(name_dir_file1, "1\n"); 200 | test_littlefs_create_file_with_text(name_dir_file2, "2\n"); 201 | test_littlefs_create_file_with_text(name_dir_file3, "\01\02\03"); 202 | test_littlefs_create_file_with_text(name_dir_inner_file, "3\n"); 203 | 204 | DIR* dir = opendir(dir_prefix); 205 | TEST_ASSERT_NOT_NULL(dir); 206 | int count = 0; 207 | const char* names[4]; 208 | while( true ) { 209 | struct dirent* de = readdir(dir); 210 | if (!de) { 211 | break; 212 | } 213 | if (strcasecmp(de->d_name, "1.txt") == 0) { 214 | TEST_ASSERT_TRUE(de->d_type == DT_REG); 215 | names[count] = "1.txt"; 216 | ++count; 217 | } else if (strcasecmp(de->d_name, "2.txt") == 0) { 218 | TEST_ASSERT_TRUE(de->d_type == DT_REG); 219 | names[count] = "2.txt"; 220 | ++count; 221 | } else if (strcasecmp(de->d_name, "inner") == 0) { 222 | TEST_ASSERT_TRUE(de->d_type == DT_DIR); 223 | names[count] = "inner"; 224 | ++count; 225 | } else if (strcasecmp(de->d_name, "boo.bin") == 0) { 226 | TEST_ASSERT_TRUE(de->d_type == DT_REG); 227 | names[count] = "boo.bin"; 228 | ++count; 229 | } else { 230 | char buf[512] = { 0 }; 231 | snprintf(buf, sizeof(buf), "unexpected directory entry \"%s\"", de->d_name); 232 | TEST_FAIL_MESSAGE(buf); 233 | } 234 | } 235 | TEST_ASSERT_EQUAL(count, 4); 236 | 237 | rewinddir(dir); 238 | struct dirent* de = readdir(dir); 239 | TEST_ASSERT_NOT_NULL(de); 240 | TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0])); 241 | seekdir(dir, 3); 242 | de = readdir(dir); 243 | TEST_ASSERT_NOT_NULL(de); 244 | TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3])); 245 | seekdir(dir, 1); 246 | de = readdir(dir); 247 | TEST_ASSERT_NOT_NULL(de); 248 | TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1])); 249 | seekdir(dir, 2); 250 | de = readdir(dir); 251 | TEST_ASSERT_NOT_NULL(de); 252 | TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2])); 253 | 254 | TEST_ASSERT_EQUAL(0, closedir(dir)); 255 | 256 | test_teardown(); 257 | } 258 | 259 | TEST_CASE("readdir with large number of files", "[littlefs][timeout=30]") 260 | { 261 | test_setup(); 262 | test_littlefs_readdir_many_files(littlefs_base_path "/dir2"); 263 | test_teardown(); 264 | } 265 | 266 | TEST_CASE("can opendir root directory of FS", "[littlefs]") 267 | { 268 | test_setup(); 269 | 270 | const char path[] = littlefs_base_path; 271 | 272 | char name_dir_file[64]; 273 | const char * file_name = "test_opd.txt"; 274 | snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", path, file_name); 275 | unlink(name_dir_file); 276 | test_littlefs_create_file_with_text(name_dir_file, "test_opendir\n"); 277 | DIR* dir = opendir(path); 278 | TEST_ASSERT_NOT_NULL(dir); 279 | bool found = false; 280 | while (true) { 281 | struct dirent* de = readdir(dir); 282 | if (!de) { 283 | break; 284 | } 285 | if (strcasecmp(de->d_name, file_name) == 0) { 286 | found = true; 287 | break; 288 | } 289 | } 290 | TEST_ASSERT_TRUE(found); 291 | TEST_ASSERT_EQUAL(0, closedir(dir)); 292 | unlink(name_dir_file); 293 | 294 | test_teardown(); 295 | } 296 | 297 | TEST_CASE("unlink removes a file", "[littlefs]") 298 | { 299 | test_setup(); 300 | 301 | const char filename[] = littlefs_base_path "/unlink.txt"; 302 | 303 | test_littlefs_create_file_with_text(filename, "unlink\n"); 304 | TEST_ASSERT_EQUAL(0, unlink(filename)); 305 | TEST_ASSERT_NULL(fopen(filename, "r")); 306 | 307 | test_teardown(); 308 | } 309 | 310 | TEST_CASE("rename moves a file", "[littlefs]") 311 | { 312 | test_setup(); 313 | const char filename_prefix[] = littlefs_base_path "/move"; 314 | 315 | char name_dst[64]; 316 | char name_src[64]; 317 | snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix); 318 | snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix); 319 | 320 | unlink(name_dst); 321 | unlink(name_src); 322 | 323 | FILE* f = fopen(name_src, "w+"); 324 | TEST_ASSERT_NOT_NULL(f); 325 | const char* str = "0123456789"; 326 | for (int i = 0; i < 400; ++i) { 327 | TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); 328 | } 329 | TEST_ASSERT_EQUAL(0, fclose(f)); 330 | TEST_ASSERT_EQUAL(0, rename(name_src, name_dst)); 331 | TEST_ASSERT_NULL(fopen(name_src, "r")); 332 | FILE* fdst = fopen(name_dst, "r"); 333 | TEST_ASSERT_NOT_NULL(fdst); 334 | TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); 335 | TEST_ASSERT_EQUAL(4000, ftell(fdst)); 336 | TEST_ASSERT_EQUAL(0, fclose(fdst)); 337 | 338 | test_teardown(); 339 | } 340 | 341 | 342 | #endif 343 | -------------------------------------------------------------------------------- /test/test_littlefs.c: -------------------------------------------------------------------------------- 1 | //#define LOG_LOCAL_LEVEL 4 2 | #include "test_littlefs_common.h" 3 | 4 | static void test_littlefs_write_file_with_offset(const char *filename); 5 | static void test_littlefs_read_file_with_offset(const char *filename); 6 | static void test_littlefs_overwrite_append(const char* filename); 7 | static void test_littlefs_open_max_files(const char* filename_prefix, size_t files_count); 8 | static void test_littlefs_concurrent_rw(const char* filename_prefix); 9 | 10 | static int test_littlefs_stat(const char *path, struct stat *buf); 11 | 12 | TEST_CASE("can initialize LittleFS in erased partition", "[littlefs]") 13 | { 14 | /* Gets the partition labeled "flash_test" */ 15 | const esp_partition_t* part = get_test_data_partition(); 16 | TEST_ASSERT_NOT_NULL(part); 17 | TEST_ESP_OK(esp_partition_erase_range(part, 0, part->size)); 18 | test_setup(); 19 | size_t total = 0, used = 0; 20 | TEST_ESP_OK(esp_littlefs_info(littlefs_test_partition_label, &total, &used)); 21 | printf("total: %d, used: %d\n", total, used); 22 | TEST_ASSERT_EQUAL(8192, used); // 2 blocks are used on a fresh filesystem 23 | test_teardown(); 24 | } 25 | 26 | TEST_CASE("can format mounted partition", "[littlefs]") 27 | { 28 | // Mount LittleFS, create file, format, check that the file does not exist. 29 | const esp_partition_t* part = get_test_data_partition(); 30 | TEST_ASSERT_NOT_NULL(part); 31 | test_setup(); 32 | const char* filename = littlefs_base_path "/hello.txt"; 33 | test_littlefs_create_file_with_text(filename, littlefs_test_hello_str); 34 | printf("Deleting \"%s\" via formatting fs.\n", filename); 35 | esp_littlefs_format(part->label); 36 | FILE* f = fopen(filename, "r"); 37 | TEST_ASSERT_NULL(f); 38 | test_teardown(); 39 | } 40 | 41 | TEST_CASE("can format unmounted partition", "[littlefs]") 42 | { 43 | // Mount LittleFS, create file, unmount. Format. Mount again, check that 44 | // the file does not exist. 45 | const esp_partition_t* part = get_test_data_partition(); 46 | TEST_ASSERT_NOT_NULL(part); 47 | 48 | test_setup(); 49 | const char* filename = littlefs_base_path "/hello.txt"; 50 | test_littlefs_create_file_with_text(filename, littlefs_test_hello_str); 51 | test_teardown(); 52 | 53 | esp_littlefs_format(part->label); 54 | // Don't use test_setup here, need to mount without formatting 55 | const esp_vfs_littlefs_conf_t conf = { 56 | .base_path = littlefs_base_path, 57 | .partition_label = littlefs_test_partition_label, 58 | .format_if_mount_failed = false 59 | }; 60 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 61 | 62 | FILE* f = fopen(filename, "r"); 63 | TEST_ASSERT_NULL(f); 64 | test_teardown(); 65 | } 66 | 67 | TEST_CASE("NULL label mounts first littlefs partition.", "[littlefs]") 68 | { 69 | esp_littlefs_format(littlefs_test_partition_label); 70 | const esp_vfs_littlefs_conf_t conf = { 71 | .base_path = littlefs_base_path, 72 | .partition_label = NULL, 73 | .format_if_mount_failed = true 74 | }; 75 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 76 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 77 | 78 | TEST_ASSERT_TRUE( esp_littlefs_mounted(NULL) ); 79 | TEST_ASSERT_TRUE( esp_littlefs_mounted("named_part") ); 80 | 81 | TEST_ESP_OK(esp_vfs_littlefs_unregister(NULL)); 82 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 83 | } 84 | 85 | TEST_CASE("can create and write file", "[littlefs]") 86 | { 87 | test_setup(); 88 | test_littlefs_create_file_with_text(littlefs_base_path "/hello.txt", littlefs_test_hello_str); 89 | test_teardown(); 90 | } 91 | 92 | TEST_CASE("can read file", "[littlefs]") 93 | { 94 | test_setup(); 95 | test_littlefs_create_file_with_text(littlefs_base_path "/hello.txt", littlefs_test_hello_str); 96 | test_littlefs_read_file(littlefs_base_path "/hello.txt"); 97 | test_teardown(); 98 | } 99 | 100 | TEST_CASE("can write to file with offset (pwrite)", "[littlefs]") 101 | { 102 | test_setup(); 103 | test_littlefs_write_file_with_offset(littlefs_base_path "/hello.txt"); 104 | test_teardown(); 105 | } 106 | 107 | TEST_CASE("can read from file with offset (pread)", "[littlefs]") 108 | { 109 | test_setup(); 110 | test_littlefs_read_file_with_offset(littlefs_base_path "/hello.txt"); 111 | test_teardown(); 112 | } 113 | 114 | TEST_CASE("r+ mode read and write file", "[littlefs]") 115 | { 116 | /* Note: despite some online resources, "r+" should not create a file 117 | * if it does not exist */ 118 | 119 | const char fn[] = littlefs_base_path "/hello.txt"; 120 | char buf[100] = { 0 }; 121 | 122 | test_setup(); 123 | 124 | test_littlefs_create_file_with_text(fn, "foo"); 125 | 126 | /* Read back the previously written foo, and add bar*/ 127 | { 128 | FILE* f = fopen(fn, "r+"); 129 | TEST_ASSERT_NOT_NULL(f); 130 | TEST_ASSERT_EQUAL(3, fread(buf, 1, sizeof(buf), f)); 131 | TEST_ASSERT_EQUAL_STRING("foo", buf); 132 | TEST_ASSERT_TRUE(fputs("bar", f) != EOF); 133 | TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); 134 | TEST_ASSERT_EQUAL(6, fread(buf, 1, 6, f)); 135 | TEST_ASSERT_EQUAL_STRING("foobar", buf); 136 | TEST_ASSERT_EQUAL(0, fclose(f)); 137 | } 138 | 139 | /* Just normal read the whole contents */ 140 | { 141 | FILE* f = fopen(fn, "r+"); 142 | TEST_ASSERT_NOT_NULL(f); 143 | TEST_ASSERT_EQUAL(6, fread(buf, 1, sizeof(buf), f)); 144 | TEST_ASSERT_EQUAL_STRING("foobar", buf); 145 | TEST_ASSERT_EQUAL(0, fclose(f)); 146 | } 147 | 148 | test_teardown(); 149 | } 150 | 151 | TEST_CASE("w+ mode read and write file", "[littlefs]") 152 | { 153 | const char fn[] = littlefs_base_path "/hello.txt"; 154 | char buf[100] = { 0 }; 155 | 156 | test_setup(); 157 | 158 | test_littlefs_create_file_with_text(fn, "foo"); 159 | 160 | /* this should overwrite the file and be readable */ 161 | { 162 | FILE* f = fopen(fn, "w+"); 163 | TEST_ASSERT_NOT_NULL(f); 164 | TEST_ASSERT_TRUE(fputs("bar", f) != EOF); 165 | TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); 166 | TEST_ASSERT_EQUAL(3, fread(buf, 1, sizeof(buf), f)); 167 | TEST_ASSERT_EQUAL_STRING("bar", buf); 168 | TEST_ASSERT_EQUAL(0, fclose(f)); 169 | } 170 | 171 | test_teardown(); 172 | } 173 | 174 | 175 | TEST_CASE("can open maximum number of files", "[littlefs]") 176 | { 177 | size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr, esp-idf defaults to maximum 64 file descriptors */ 178 | 179 | test_setup(); 180 | test_littlefs_open_max_files("/littlefs/f", max_files); 181 | test_teardown(); 182 | } 183 | 184 | TEST_CASE("overwrite and append file", "[littlefs]") 185 | { 186 | test_setup(); 187 | test_littlefs_overwrite_append(littlefs_base_path "/hello.txt"); 188 | test_teardown(); 189 | } 190 | 191 | TEST_CASE("use append with other flags", "[littlefs]") 192 | { 193 | // https://github.com/joltwallet/esp_littlefs/issues/154 194 | test_setup(); 195 | 196 | int fd; 197 | 198 | fd = open(littlefs_base_path "/fcntl.txt", O_CREAT | O_WRONLY | O_TRUNC, 0777); 199 | TEST_ASSERT_EQUAL(6, write(fd, "test1\n", 6)); 200 | TEST_ASSERT_EQUAL(0, close(fd)); 201 | 202 | fd = open(littlefs_base_path "/fcntl.txt", O_CREAT | O_WRONLY | O_APPEND, 0777); 203 | TEST_ASSERT_EQUAL(0, lseek(fd, 0, SEEK_CUR)); 204 | TEST_ASSERT_EQUAL(6, write(fd, "test2\n", 6)); 205 | TEST_ASSERT_EQUAL(12, lseek(fd, 0, SEEK_CUR)); 206 | TEST_ASSERT_EQUAL(0, close(fd)); 207 | 208 | test_teardown(); 209 | } 210 | 211 | TEST_CASE("can lseek", "[littlefs]") 212 | { 213 | test_setup(); 214 | 215 | FILE* f = fopen(littlefs_base_path "/seek.txt", "wb+"); 216 | TEST_ASSERT_NOT_NULL(f); 217 | TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); 218 | TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); 219 | TEST_ASSERT_EQUAL('9', fgetc(f)); 220 | TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); 221 | TEST_ASSERT_EQUAL('3', fgetc(f)); 222 | TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); 223 | TEST_ASSERT_EQUAL('8', fgetc(f)); 224 | TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); 225 | TEST_ASSERT_EQUAL(11, ftell(f)); 226 | // Appending to end 227 | TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); 228 | TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); 229 | TEST_ASSERT_EQUAL(15, ftell(f)); 230 | // Appending past end of file, creating a "hole" 231 | TEST_ASSERT_EQUAL(0, fseek(f, 2, SEEK_END)); 232 | TEST_ASSERT_EQUAL(4, fprintf(f, "foo\n")); 233 | TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); 234 | char buf[32]; 235 | TEST_ASSERT_EQUAL(21, fread(buf, 1, sizeof(buf), f)); 236 | const char ref_buf[] = "0123456789\nabc\n\0\0foo\n"; 237 | TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); 238 | 239 | // Error checking 240 | // Attempting to seek before the beginning of file should return an error 241 | TEST_ASSERT_EQUAL(-1, fseek(f, 100, 100)); // Bad mode 242 | TEST_ASSERT_EQUAL(EINVAL, errno); 243 | TEST_ASSERT_EQUAL(-1, fseek(f, -1, SEEK_SET)); // Seeking to before start of file 244 | TEST_ASSERT_EQUAL(EINVAL, errno); 245 | 246 | 247 | TEST_ASSERT_EQUAL(0, fclose(f)); 248 | test_teardown(); 249 | } 250 | 251 | TEST_CASE("stat/fstat returns correct values", "[littlefs]") 252 | { 253 | test_setup(); 254 | const char filename[] = littlefs_base_path "/stat.txt"; 255 | 256 | test_littlefs_create_file_with_text(filename, "foo\n"); 257 | struct stat st; 258 | for(uint8_t i=0; i < 2; i++) { 259 | if(i == 0){ 260 | // Test stat 261 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 262 | } 263 | else { 264 | #ifndef CONFIG_LITTLEFS_USE_ONLY_HASH 265 | // Test fstat 266 | FILE *f = fopen(filename, "r"); 267 | TEST_ASSERT_NOT_NULL(f); 268 | TEST_ASSERT_EQUAL(0, fstat(fileno(f), &st)); 269 | TEST_ASSERT_EQUAL(0, fclose(f)); 270 | #endif 271 | } 272 | TEST_ASSERT(st.st_mode & S_IFREG); 273 | TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); 274 | TEST_ASSERT_EQUAL(4, st.st_size); 275 | } 276 | 277 | test_teardown(); 278 | } 279 | 280 | TEST_CASE("multiple tasks can use same volume", "[littlefs]") 281 | { 282 | test_setup(); 283 | test_littlefs_concurrent_rw(littlefs_base_path "/f"); 284 | test_teardown(); 285 | } 286 | 287 | TEST_CASE("esp_littlefs_info", "[littlefs]") 288 | { 289 | test_setup(); 290 | 291 | char filename[] = littlefs_base_path "/test_esp_littlefs_info.bin"; 292 | unlink(filename); /* Delete the file incase it exists */ 293 | 294 | /* Get starting system size */ 295 | size_t total_og = 0, used_og = 0; 296 | TEST_ESP_OK(esp_littlefs_info(littlefs_test_partition_label, &total_og, &used_og)); 297 | 298 | /* Write 100,000 bytes */ 299 | FILE* f = fopen(filename, "wb"); 300 | TEST_ASSERT_NOT_NULL(f); 301 | char val = 'c'; 302 | size_t n_bytes = 100000; 303 | for(int i=0; i < n_bytes; i++) { 304 | TEST_ASSERT_EQUAL(1, fwrite(&val, 1, 1, f)); 305 | } 306 | TEST_ASSERT_EQUAL(0, fclose(f)); 307 | 308 | /* Re-check system size */ 309 | size_t total_new = 0, used_new = 0; 310 | TEST_ESP_OK(esp_littlefs_info(littlefs_test_partition_label, &total_new, &used_new)); 311 | 312 | printf("old: %d; new: %d; diff: %d\n", used_og, used_new, used_new-used_og); 313 | 314 | /* total amount of storage shouldn't change */ 315 | TEST_ASSERT_EQUAL_INT(total_og, total_new); 316 | 317 | /* The actual amount of used storage should be within 2 blocks of expected.*/ 318 | size_t diff = used_new - used_og; 319 | TEST_ASSERT_GREATER_THAN_INT(n_bytes - (2 * 4096), diff); 320 | TEST_ASSERT_LESS_THAN_INT(n_bytes + (2 * 4096), diff); 321 | 322 | unlink(filename); 323 | test_teardown(); 324 | } 325 | 326 | #if CONFIG_LITTLEFS_USE_MTIME 327 | 328 | #if CONFIG_LITTLEFS_MTIME_USE_SECONDS 329 | TEST_CASE("mtime support", "[littlefs]") 330 | { 331 | test_setup(); 332 | 333 | /* Open a file, check that mtime is set correctly */ 334 | const char* filename = littlefs_base_path "/time"; 335 | time_t t_before_create = time(NULL); 336 | test_littlefs_create_file_with_text(filename, "test"); 337 | time_t t_after_create = time(NULL); 338 | 339 | struct stat st; 340 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 341 | printf("mtime=%d\n", (int) st.st_mtime); 342 | TEST_ASSERT(st.st_mtime >= t_before_create); 343 | TEST_ASSERT(st.st_mtime <= t_after_create); 344 | 345 | /* Wait a bit, open & close again, check that mtime is updated */ 346 | vTaskDelay(2000 / portTICK_PERIOD_MS); 347 | time_t t_before_close = time(NULL); 348 | FILE *f = fopen(filename, "a"); 349 | TEST_ASSERT_EQUAL(0, fclose(f)); 350 | time_t t_after_close = time(NULL); 351 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 352 | printf("mtime=%d\n", (int) st.st_mtime); 353 | time_t append_mtime = st.st_mtime; 354 | TEST_ASSERT(append_mtime >= t_before_close); 355 | TEST_ASSERT(append_mtime <= t_after_close); 356 | 357 | /* Wait a bit, open for reading, check that mtime is not updated */ 358 | vTaskDelay(2000 / portTICK_PERIOD_MS); 359 | time_t t_before_close_ro = time(NULL); 360 | f = fopen(filename, "r"); 361 | TEST_ASSERT_EQUAL(0, fclose(f)); 362 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 363 | printf("mtime=%d\n", (int) st.st_mtime); 364 | TEST_ASSERT(t_before_close_ro > t_after_close); // sufficient time has passed for this test to be valid. 365 | // make sure the st_mtime is the same as bfore 366 | TEST_ASSERT(st.st_mtime == append_mtime); 367 | 368 | test_teardown(); 369 | } 370 | #endif 371 | 372 | #if CONFIG_LITTLEFS_MTIME_USE_NONCE 373 | TEST_CASE("mnonce support", "[littlefs]") 374 | { 375 | /* Open a file, check that mtime is set correctly */ 376 | struct stat st; 377 | const char* filename = littlefs_base_path "/time"; 378 | test_setup(); 379 | test_littlefs_create_file_with_text(filename, "test"); 380 | 381 | int nonce1; 382 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 383 | nonce1 = (int) st.st_mtime; 384 | printf("mtime=%d\n", nonce1); 385 | TEST_ASSERT(nonce1 >= 0); 386 | 387 | /* open again, check that mtime is updated */ 388 | int nonce2; 389 | FILE *f = fopen(filename, "a"); 390 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 391 | nonce2 = (int) st.st_mtime; 392 | printf("mtime=%d\n", nonce2); 393 | if( nonce1 == UINT32_MAX ) { 394 | TEST_ASSERT_EQUAL_INT(1, nonce2); 395 | } 396 | else { 397 | TEST_ASSERT_EQUAL_INT(1, nonce2-nonce1); 398 | } 399 | TEST_ASSERT_EQUAL(0, fclose(f)); 400 | 401 | /* open for reading, check that mtime is not updated */ 402 | int nonce3; 403 | f = fopen(filename, "r"); 404 | TEST_ASSERT_EQUAL(0, test_littlefs_stat(filename, &st)); 405 | nonce3 = (int) st.st_mtime; 406 | printf("mtime=%d\n", (int) st.st_mtime); 407 | TEST_ASSERT_EQUAL_INT(nonce2, nonce3); 408 | TEST_ASSERT_EQUAL(0, fclose(f)); 409 | 410 | test_teardown(); 411 | } 412 | #endif 413 | 414 | #endif 415 | 416 | static void test_littlefs_write_file_with_offset(const char *filename) 417 | { 418 | const char *source = "Replace this character: [k]"; 419 | off_t offset = strstr(source, "k") - source; 420 | size_t len = strlen(source); 421 | const char new_char = 'y'; 422 | 423 | // Create file with string at source string 424 | test_littlefs_create_file_with_text(filename, source); 425 | 426 | // Replace k with y at the file 427 | int fd = open(filename, O_RDWR); 428 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 429 | int written = pwrite(fd, &new_char, 1, offset); 430 | TEST_ASSERT_EQUAL(1, written); 431 | TEST_ASSERT_EQUAL(0, close(fd)); 432 | 433 | char buf[len]; 434 | 435 | // Compare if both are equal 436 | FILE *f = fopen(filename, "r"); 437 | TEST_ASSERT_NOT_NULL(f); 438 | int rd = fread(buf, len, 1, f); 439 | TEST_ASSERT_EQUAL(1, rd); 440 | TEST_ASSERT_EQUAL(buf[offset], new_char); 441 | TEST_ASSERT_EQUAL(0, fclose(f)); 442 | } 443 | 444 | static void test_littlefs_read_file_with_offset(const char *filename) 445 | { 446 | const char *source = "This text will be partially read"; 447 | off_t offset = strstr(source, "p") - source; 448 | size_t len = strlen(source); 449 | char buf[len - offset + 1]; 450 | buf[len-offset] = '\0'; // EOS 451 | 452 | // Create file with string at source string 453 | test_littlefs_create_file_with_text(filename, source); 454 | 455 | // Read file content beginning at `partially` word 456 | int fd = open(filename, O_RDONLY); 457 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 458 | int rd = pread(fd, buf, len - offset, offset); 459 | TEST_ASSERT_EQUAL(len - offset, rd); 460 | // Compare if string read from file and source string related slice are equal 461 | int res = strcmp(buf, &source[offset]); 462 | TEST_ASSERT_EQUAL(0, res); 463 | TEST_ASSERT_EQUAL(0, close(fd)); 464 | } 465 | 466 | static void test_littlefs_overwrite_append(const char* filename) 467 | { 468 | /* Create new file with 'aaaa' */ 469 | test_littlefs_create_file_with_text(filename, "aaaa"); 470 | 471 | /* Append 'bbbb' to file */ 472 | FILE *f_a = fopen(filename, "a"); 473 | TEST_ASSERT_NOT_NULL(f_a); 474 | TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a)); 475 | TEST_ASSERT_EQUAL(0, fclose(f_a)); 476 | 477 | /* Read back 8 bytes from file, verify it's 'aaaabbbb' */ 478 | char buf[10] = { 0 }; 479 | FILE *f_r = fopen(filename, "r"); 480 | TEST_ASSERT_NOT_NULL(f_r); 481 | TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r)); 482 | TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8); 483 | 484 | /* Be sure we're at end of file */ 485 | TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r)); 486 | 487 | TEST_ASSERT_EQUAL(0, fclose(f_r)); 488 | 489 | /* Overwrite file with 'cccc' */ 490 | test_littlefs_create_file_with_text(filename, "cccc"); 491 | 492 | /* Verify file now only contains 'cccc' */ 493 | f_r = fopen(filename, "r"); 494 | TEST_ASSERT_NOT_NULL(f_r); 495 | bzero(buf, sizeof(buf)); 496 | TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4 497 | TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4); 498 | TEST_ASSERT_EQUAL(0, fclose(f_r)); 499 | } 500 | 501 | static void test_littlefs_open_max_files(const char* filename_prefix, size_t files_count) 502 | { 503 | FILE** files = calloc(files_count, sizeof(FILE*)); 504 | assert(files); 505 | for (size_t i = 0; i < files_count; ++i) { 506 | char name[32]; 507 | snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i); 508 | printf("Opening \"%s\"\n", name); 509 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 510 | files[i] = fopen(name, "w"); 511 | TEST_ASSERT_NOT_NULL(files[i]); 512 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 513 | } 514 | /* close everything and clean up */ 515 | for (size_t i = 0; i < files_count; ++i) { 516 | TEST_ASSERT_EQUAL(0, fclose(files[i])); 517 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 518 | } 519 | free(files); 520 | } 521 | 522 | typedef enum { 523 | CONCURRENT_TASK_ACTION_READ, 524 | CONCURRENT_TASK_ACTION_WRITE, 525 | CONCURRENT_TASK_ACTION_STAT, 526 | } concurrent_task_action_t; 527 | 528 | typedef struct { 529 | const char* filename; 530 | concurrent_task_action_t action; 531 | size_t word_count; 532 | int seed; 533 | SemaphoreHandle_t done; 534 | int result; 535 | } read_write_test_arg_t; 536 | 537 | #define READ_WRITE_TEST_ARG_INIT(name, seed_) \ 538 | { \ 539 | .filename = name, \ 540 | .seed = seed_, \ 541 | .word_count = 4096, \ 542 | .action = CONCURRENT_TASK_ACTION_WRITE, \ 543 | .done = xSemaphoreCreateBinary() \ 544 | } 545 | 546 | static void read_write_task(void* param) 547 | { 548 | FILE *f = NULL; 549 | read_write_test_arg_t* args = (read_write_test_arg_t*) param; 550 | if (args->action == CONCURRENT_TASK_ACTION_WRITE) { 551 | f = fopen(args->filename, "wb"); 552 | if (f == NULL) {args->result = ESP_ERR_NOT_FOUND; goto done;} 553 | } else if (args->action == CONCURRENT_TASK_ACTION_READ) { 554 | f = fopen(args->filename, "rb"); 555 | if (f == NULL) {args->result = ESP_ERR_NOT_FOUND; goto done;} 556 | } else if (args->action == CONCURRENT_TASK_ACTION_STAT) { 557 | } 558 | 559 | srand(args->seed); 560 | for (size_t i = 0; i < args->word_count; ++i) { 561 | uint32_t val = rand(); 562 | if (args->action == CONCURRENT_TASK_ACTION_WRITE) { 563 | int cnt = fwrite(&val, sizeof(val), 1, f); 564 | if (cnt != 1) { 565 | esp_rom_printf("E(w): i=%d, cnt=%d val=%d\n\n", i, cnt, val); 566 | args->result = ESP_FAIL; 567 | goto close; 568 | } 569 | } else if (args->action == CONCURRENT_TASK_ACTION_READ) { 570 | uint32_t rval; 571 | int cnt = fread(&rval, sizeof(rval), 1, f); 572 | if (cnt != 1) { 573 | esp_rom_printf("E(r): i=%d, cnt=%d rval=%d\n\n", i, cnt, rval); 574 | args->result = ESP_FAIL; 575 | goto close; 576 | } 577 | } else if (args->action == CONCURRENT_TASK_ACTION_STAT) { 578 | int res; 579 | struct stat buf; 580 | res = stat(args->filename, &buf); 581 | if(res < 0) { 582 | args->result = ESP_FAIL; 583 | goto done; 584 | } 585 | } 586 | } 587 | args->result = ESP_OK; 588 | 589 | close: 590 | if(f) { 591 | TEST_ASSERT_EQUAL(0, fclose(f)); 592 | } 593 | 594 | done: 595 | xSemaphoreGive(args->done); 596 | vTaskDelay(1); 597 | vTaskDelete(NULL); 598 | } 599 | 600 | 601 | static void test_littlefs_concurrent_rw(const char* filename_prefix) 602 | { 603 | #define TASK_SIZE 4096 604 | char names[4][64]; 605 | for (size_t i = 0; i < 4; ++i) { 606 | snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1); 607 | } 608 | 609 | /************************************************ 610 | * TESTING CONCURRENT WRITES TO DIFFERENT FILES * 611 | ************************************************/ 612 | read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1); 613 | read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2); 614 | printf("writing f1 and f2\n"); 615 | const int cpuid_0 = 0; 616 | const int cpuid_1 = portNUM_PROCESSORS - 1; 617 | xTaskCreatePinnedToCore(&read_write_task, "rw1", TASK_SIZE, &args1, 3, NULL, cpuid_0); 618 | xTaskCreatePinnedToCore(&read_write_task, "rw2", TASK_SIZE, &args2, 3, NULL, cpuid_1); 619 | 620 | xSemaphoreTake(args1.done, portMAX_DELAY); 621 | printf("f1 done\n"); 622 | TEST_ASSERT_EQUAL(ESP_OK, args1.result); 623 | xSemaphoreTake(args2.done, portMAX_DELAY); 624 | printf("f2 done\n"); 625 | TEST_ASSERT_EQUAL(ESP_OK, args2.result); 626 | 627 | args1.action = CONCURRENT_TASK_ACTION_READ; 628 | args2.action = CONCURRENT_TASK_ACTION_READ; 629 | read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT(names[2], 3); 630 | read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4); 631 | 632 | printf("reading f1 and f2, writing f3 and f4, stating f1 concurrently from 2 cores\n"); 633 | 634 | xTaskCreatePinnedToCore(&read_write_task, "rw3", TASK_SIZE, &args3, 3, NULL, cpuid_1); 635 | xTaskCreatePinnedToCore(&read_write_task, "rw4", TASK_SIZE, &args4, 3, NULL, cpuid_0); 636 | xTaskCreatePinnedToCore(&read_write_task, "rw1", TASK_SIZE, &args1, 3, NULL, cpuid_0); 637 | xTaskCreatePinnedToCore(&read_write_task, "rw2", TASK_SIZE, &args2, 3, NULL, cpuid_1); 638 | 639 | #if CONFIG_VFS_SUPPORT_DIR 640 | read_write_test_arg_t args5 = READ_WRITE_TEST_ARG_INIT(names[0], 3); 641 | args5.action = CONCURRENT_TASK_ACTION_STAT; 642 | args5.word_count = 300; 643 | read_write_test_arg_t args6 = READ_WRITE_TEST_ARG_INIT(names[0], 3); 644 | args6.action = CONCURRENT_TASK_ACTION_STAT; 645 | args6.word_count = 300; 646 | 647 | 648 | xTaskCreatePinnedToCore(&read_write_task, "stat1", TASK_SIZE, &args5, 3, NULL, cpuid_0); 649 | xTaskCreatePinnedToCore(&read_write_task, "stat2", TASK_SIZE, &args6, 3, NULL, cpuid_1); 650 | #endif 651 | 652 | 653 | xSemaphoreTake(args1.done, portMAX_DELAY); 654 | printf("f1 done\n"); 655 | TEST_ASSERT_EQUAL(ESP_OK, args1.result); 656 | xSemaphoreTake(args2.done, portMAX_DELAY); 657 | printf("f2 done\n"); 658 | TEST_ASSERT_EQUAL(ESP_OK, args2.result); 659 | xSemaphoreTake(args3.done, portMAX_DELAY); 660 | printf("f3 done\n"); 661 | TEST_ASSERT_EQUAL(ESP_OK, args3.result); 662 | xSemaphoreTake(args4.done, portMAX_DELAY); 663 | printf("f4 done\n"); 664 | 665 | #if CONFIG_VFS_SUPPORT_DIR 666 | TEST_ASSERT_EQUAL(ESP_OK, args5.result); 667 | xSemaphoreTake(args5.done, portMAX_DELAY); 668 | printf("stat1 done\n"); 669 | TEST_ASSERT_EQUAL(ESP_OK, args6.result); 670 | xSemaphoreTake(args6.done, portMAX_DELAY); 671 | printf("stat2 done\n"); 672 | #endif 673 | 674 | 675 | vSemaphoreDelete(args1.done); 676 | vSemaphoreDelete(args2.done); 677 | vSemaphoreDelete(args3.done); 678 | vSemaphoreDelete(args4.done); 679 | #undef TASK_SIZE 680 | } 681 | 682 | #if CONFIG_LITTLEFS_SPIFFS_COMPAT 683 | TEST_CASE("SPIFFS COMPAT: file creation and deletion", "[littlefs]") 684 | { 685 | test_setup(); 686 | 687 | const char* filename = littlefs_base_path "/spiffs_compat/foo/bar/spiffs_compat.bin"; 688 | 689 | FILE* f = fopen(filename, "w"); 690 | TEST_ASSERT_NOT_NULL(f); 691 | TEST_ASSERT_TRUE(fputs("bar", f) != EOF); 692 | TEST_ASSERT_EQUAL(0, fclose(f)); 693 | 694 | TEST_ASSERT_EQUAL(0, unlink(filename)); 695 | 696 | /* check to see if all the directories were deleted */ 697 | struct stat sb; 698 | if (test_littlefs_stat(littlefs_base_path "/spiffs_compat", &sb) == 0 && S_ISDIR(sb.st_mode)) { 699 | TEST_FAIL_MESSAGE("Empty directories were not deleted"); 700 | } 701 | 702 | test_teardown(); 703 | } 704 | 705 | TEST_CASE("SPIFFS COMPAT: file creation and rename", "[littlefs]") 706 | { 707 | test_setup(); 708 | 709 | int res; 710 | char message[256]; 711 | const char* src = littlefs_base_path "/spiffs_compat/src/foo/bar/spiffs_compat.bin"; 712 | const char* dst = littlefs_base_path "/spiffs_compat/dst/foo/bar/spiffs_compat.bin"; 713 | 714 | FILE* f = fopen(src, "w"); 715 | TEST_ASSERT_NOT_NULL(f); 716 | TEST_ASSERT_TRUE(fputs("bar", f) != EOF); 717 | TEST_ASSERT_EQUAL(0, fclose(f)); 718 | 719 | res = rename(src, dst); 720 | snprintf(message, sizeof(message), "errno: %d", errno); 721 | TEST_ASSERT_EQUAL_MESSAGE(0, res, message); 722 | 723 | /* check to see if all the directories were deleted */ 724 | struct stat sb; 725 | if (test_littlefs_stat(littlefs_base_path "/spiffs_compat/src", &sb) == 0 && S_ISDIR(sb.st_mode)) { 726 | TEST_FAIL_MESSAGE("Empty directories were not deleted"); 727 | } 728 | 729 | test_teardown(); 730 | } 731 | 732 | #endif // CONFIG_LITTLEFS_SPIFFS_COMPAT 733 | 734 | TEST_CASE("Rewriting file frees space immediately (#7426)", "[littlefs]") 735 | { 736 | /* modified from: 737 | * https://github.com/esp8266/Arduino/commit/c663c55926f205723c3d56dd7030bacbe7960f8e 738 | */ 739 | 740 | test_setup(); 741 | 742 | size_t total = 0, used = 0; 743 | TEST_ESP_OK(esp_littlefs_info(littlefs_test_partition_label, &total, &used)); 744 | 745 | // 2 block overhead 746 | int kb_to_write = (total - used - (2*4096)) / 1024; 747 | 748 | // Create and overwrite a file >50% of spaceA (48/64K) 749 | uint8_t buf[1024]; 750 | memset(buf, 0xaa, 1024); 751 | for (uint8_t x = 0; x < 2; x++) { 752 | FILE *f = fopen(littlefs_base_path "/file1.bin", "w"); 753 | TEST_ASSERT_NOT_NULL(f); 754 | 755 | for (size_t i = 0; i < kb_to_write; i++) { 756 | TEST_ASSERT_EQUAL_INT(1024, fwrite(buf, 1, 1024, f)); 757 | } 758 | TEST_ASSERT_EQUAL(0, fclose(f)); 759 | } 760 | test_teardown(); 761 | } 762 | 763 | TEST_CASE("esp_littlefs_info returns used_bytes > total_bytes", "[littlefs]") 764 | { 765 | // https://github.com/joltwallet/esp_littlefs/issues/66 766 | test_setup(); 767 | const char foo[] = "foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo"; 768 | 769 | char names[7][64]; 770 | for (size_t i = 0; i < 7; ++i) { 771 | snprintf(names[i], sizeof(names[i]), littlefs_base_path "/%d", i + 1); 772 | unlink(names[i]); // Make sure these files don't exist 773 | } 774 | 775 | for (size_t i = 0; i < 7; ++i) { 776 | FILE* f = fopen(names[i], "wb"); 777 | TEST_ASSERT_NOT_NULL(f); 778 | char val = 'c'; 779 | size_t n_bytes = 65432; 780 | for(int i=0; i < n_bytes; i++) { 781 | TEST_ASSERT_EQUAL(1, fwrite(&val, 1, 1, f)); 782 | } 783 | TEST_ASSERT_EQUAL(0, fclose(f)); 784 | } 785 | 786 | bool disk_full = false; 787 | int i = 0; 788 | while(!disk_full){ 789 | char *filename = names[i % 7]; 790 | FILE* f = fopen(filename, "a+b"); 791 | TEST_ASSERT_NOT_NULL(f); 792 | size_t n_bytes = 200 + i % 17; 793 | int amount_written = fwrite(foo, n_bytes, 1, f); 794 | if(amount_written != 1) { 795 | disk_full = true; 796 | } 797 | if(0 != fclose(f)){ 798 | disk_full = true; 799 | } 800 | 801 | size_t total = 0, used = 0; 802 | TEST_ESP_OK(esp_littlefs_info(littlefs_test_partition_label, &total, &used)); 803 | TEST_ASSERT_GREATER_OR_EQUAL_INT(used, total); 804 | //printf("used: %d total: %d\n", used, total); 805 | i++; 806 | } 807 | test_teardown(); 808 | } 809 | 810 | #if CONFIG_LITTLEFS_OPEN_DIR 811 | TEST_CASE("open with flag O_DIRECTORY", "[littlefs]") 812 | { 813 | int fd; 814 | int ret; 815 | struct stat stat; 816 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 817 | char path[MAXPATHLEN]; 818 | #endif 819 | 820 | test_setup(); 821 | 822 | fd = open("/littlefs/dir1/", O_DIRECTORY | O_NOFOLLOW); 823 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 824 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 825 | ret = fcntl(fd, F_GETPATH, path); 826 | TEST_ASSERT_EQUAL(0, ret); 827 | TEST_ASSERT_EQUAL_STRING("/littlefs/dir1/", path); 828 | memset(path, 0, MAXPATHLEN); 829 | #endif 830 | ret = fstat(fd, &stat); 831 | TEST_ASSERT_EQUAL(0, ret); 832 | TEST_ASSERT_NOT_EQUAL(0, stat.st_size); 833 | TEST_ASSERT_EQUAL(S_IFDIR, stat.st_mode); 834 | ret = close(fd); 835 | TEST_ASSERT_EQUAL(0, ret); 836 | 837 | fd = open("/littlefs/dir1/dir2/", O_DIRECTORY | O_NOFOLLOW); 838 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 839 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 840 | ret = fcntl(fd, F_GETPATH, path); 841 | TEST_ASSERT_EQUAL(0, ret); 842 | TEST_ASSERT_EQUAL_STRING("/littlefs/dir1/dir2/", path); 843 | memset(path, 0, MAXPATHLEN); 844 | #endif 845 | ret = fstat(fd, &stat); 846 | TEST_ASSERT_EQUAL(0, ret); 847 | TEST_ASSERT_NOT_EQUAL(0, stat.st_size); 848 | TEST_ASSERT_EQUAL(S_IFDIR, stat.st_mode); 849 | ret = close(fd); 850 | TEST_ASSERT_EQUAL(0, ret); 851 | 852 | fd = open("/littlefs/dir1/dir2/test.txt", O_CREAT | O_RDWR); 853 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 854 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 855 | ret = fcntl(fd, F_GETPATH, path); 856 | TEST_ASSERT_EQUAL(0, ret); 857 | TEST_ASSERT_EQUAL_STRING("/littlefs/dir1/dir2/test.txt", path); 858 | memset(path, 0, MAXPATHLEN); 859 | #endif 860 | ret = fstat(fd, &stat); 861 | TEST_ASSERT_EQUAL(0, ret); 862 | TEST_ASSERT_EQUAL(0, stat.st_size); 863 | TEST_ASSERT_EQUAL(S_IFREG, stat.st_mode); 864 | ret = close(fd); 865 | TEST_ASSERT_EQUAL(0, ret); 866 | 867 | /* File is created in previous step */ 868 | fd = open("/littlefs/dir1/dir2/test.txt", O_DIRECTORY | O_NOFOLLOW); 869 | TEST_ASSERT_EQUAL(-1, fd); 870 | TEST_ASSERT_EQUAL(ENOTDIR, errno); 871 | 872 | test_teardown(); 873 | } 874 | #endif 875 | 876 | TEST_CASE("fcntl get flags", "[littlefs]") 877 | { 878 | int fd; 879 | int ret; 880 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 881 | char path[MAXPATHLEN]; 882 | #endif 883 | 884 | test_setup(); 885 | 886 | fd = open("/littlefs/test.txt", O_CREAT | O_WRONLY); 887 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 888 | ret = fcntl(fd, F_GETFL); 889 | TEST_ASSERT_EQUAL(O_WRONLY, ret); 890 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 891 | ret = fcntl(fd, F_GETPATH, path); 892 | TEST_ASSERT_EQUAL(0, ret); 893 | TEST_ASSERT_EQUAL_STRING("/littlefs/test.txt", path); 894 | memset(path, 0, MAXPATHLEN); 895 | #endif 896 | ret = close(fd); 897 | TEST_ASSERT_EQUAL(0, ret); 898 | 899 | fd = open("/littlefs/test.txt", O_RDONLY); 900 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 901 | ret = fcntl(fd, F_GETFL); 902 | TEST_ASSERT_EQUAL(O_RDONLY, ret); 903 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 904 | ret = fcntl(fd, F_GETPATH, path); 905 | TEST_ASSERT_EQUAL(0, ret); 906 | TEST_ASSERT_EQUAL_STRING("/littlefs/test.txt", path); 907 | memset(path, 0, MAXPATHLEN); 908 | #endif 909 | ret = close(fd); 910 | TEST_ASSERT_EQUAL(0, ret); 911 | 912 | fd = open("/littlefs/test.txt", O_RDWR); 913 | TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); 914 | ret = fcntl(fd, F_GETFL); 915 | TEST_ASSERT_EQUAL(O_RDWR, ret); 916 | #if CONFIG_LITTLEFS_FCNTL_GET_PATH 917 | ret = fcntl(fd, F_GETPATH, path); 918 | TEST_ASSERT_EQUAL(0, ret); 919 | TEST_ASSERT_EQUAL_STRING("/littlefs/test.txt", path); 920 | memset(path, 0, MAXPATHLEN); 921 | #endif 922 | ret = close(fd); 923 | TEST_ASSERT_EQUAL(0, ret); 924 | 925 | test_teardown(); 926 | } 927 | 928 | /** 929 | * Cannot use buitin `stat` since it depends on CONFIG_VFS_SUPPORT_DIR. 930 | */ 931 | static int test_littlefs_stat(const char *path, struct stat *buf){ 932 | int res; 933 | FILE* f = fopen(path, "r"); 934 | if(!f){ 935 | return -1; 936 | } 937 | int fd = fileno(f); 938 | res = fstat(fd, buf); 939 | fclose(f); 940 | return res; 941 | } 942 | -------------------------------------------------------------------------------- /test/test_littlefs_common.c: -------------------------------------------------------------------------------- 1 | #include "test_littlefs_common.h" 2 | 3 | 4 | const char littlefs_test_partition_label[] = "flash_test"; 5 | const char littlefs_test_hello_str[] = "Hello, World!\n"; 6 | 7 | 8 | void test_littlefs_create_file_with_text(const char* name, const char* text) 9 | { 10 | printf("Writing to \"%s\"\n", name); 11 | FILE* f = fopen(name, "wb"); 12 | TEST_ASSERT_NOT_NULL(f); 13 | TEST_ASSERT_TRUE(fputs(text, f) != EOF); 14 | TEST_ASSERT_EQUAL(0, fclose(f)); 15 | } 16 | 17 | void test_littlefs_read_file(const char* filename) 18 | { 19 | FILE* f = fopen(filename, "r"); 20 | TEST_ASSERT_NOT_NULL(f); 21 | char buf[32] = { 0 }; 22 | int cb = fread(buf, 1, sizeof(buf), f); 23 | TEST_ASSERT_EQUAL(strlen(littlefs_test_hello_str), cb); 24 | TEST_ASSERT_EQUAL(0, strcmp(littlefs_test_hello_str, buf)); 25 | TEST_ASSERT_EQUAL(0, fclose(f)); 26 | } 27 | 28 | void test_littlefs_read_file_with_content(const char* filename, const char* expected_content) 29 | { 30 | FILE* f = fopen(filename, "r"); 31 | TEST_ASSERT_NOT_NULL(f); 32 | char buf[32] = { 0 }; 33 | 34 | const size_t expected_content_length = strlen(expected_content); 35 | size_t read_length = 0; 36 | int cb = 0; 37 | do { 38 | cb = fread(buf, 1, sizeof(buf), f); 39 | TEST_ASSERT_TRUE(read_length + cb <= expected_content_length); 40 | TEST_ASSERT_TRUE(memcmp(buf, &expected_content[read_length], cb) == 0); 41 | read_length += cb; 42 | } while(cb != 0); 43 | 44 | TEST_ASSERT_EQUAL(expected_content_length, read_length); 45 | TEST_ASSERT_EQUAL(0, fclose(f)); 46 | } 47 | 48 | void test_setup() { 49 | esp_littlefs_format(littlefs_test_partition_label); 50 | const esp_vfs_littlefs_conf_t conf = { 51 | .base_path = littlefs_base_path, 52 | .partition_label = littlefs_test_partition_label, 53 | .format_if_mount_failed = true 54 | }; 55 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 56 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 57 | printf("Test setup complete.\n"); 58 | } 59 | 60 | void test_teardown(){ 61 | TEST_ESP_OK(esp_vfs_littlefs_unregister(littlefs_test_partition_label)); 62 | TEST_ASSERT_TRUE( heap_caps_check_integrity_all(true) ); 63 | printf("Test teardown complete.\n"); 64 | } 65 | -------------------------------------------------------------------------------- /test/test_littlefs_common.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_LITTLEFS_COMMON_H__ 2 | #define TEST_LITTLEFS_COMMON_H__ 3 | 4 | #include "esp_littlefs.h" 5 | #include "sdkconfig.h" 6 | 7 | #include "errno.h" 8 | #include "esp_err.h" 9 | #include "esp_heap_caps.h" 10 | #include "esp_idf_version.h" 11 | #include "esp_log.h" 12 | #include "esp_partition.h" 13 | #include "esp_rom_sys.h" 14 | #include "esp_spiffs.h" 15 | #include "esp_system.h" 16 | #include "esp_timer.h" 17 | #include "esp_vfs.h" 18 | #include "freertos/FreeRTOS.h" 19 | #include "freertos/queue.h" 20 | #include "freertos/semphr.h" 21 | #include "freertos/task.h" 22 | #include "test_utils.h" 23 | #include "unity.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define littlefs_base_path "/littlefs" 34 | extern const char littlefs_test_partition_label[]; 35 | extern const char littlefs_test_hello_str[]; 36 | 37 | 38 | void test_littlefs_create_file_with_text(const char* name, const char* text); 39 | void test_littlefs_read_file(const char* filename); 40 | void test_littlefs_read_file_with_content(const char* filename, const char* expected_content); 41 | 42 | void test_setup(); 43 | void test_teardown(); 44 | 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /test/test_littlefs_static_partition.c: -------------------------------------------------------------------------------- 1 | // #define LOG_LOCAL_LEVEL 4 2 | #include "test_littlefs_common.h" 3 | #include "spi_flash_mmap.h" 4 | #include "esp_flash.h" 5 | 6 | static esp_partition_t get_test_data_static_partition(void); 7 | static void test_setup_static(const esp_partition_t* partition); 8 | static void test_teardown_static(const esp_partition_t* partition); 9 | 10 | void test_setup_static(const esp_partition_t* partition) 11 | { 12 | const esp_vfs_littlefs_conf_t conf = { 13 | .base_path = littlefs_base_path, 14 | .partition_label = NULL, 15 | .partition = partition, 16 | .dont_mount = false, 17 | .read_only = true, 18 | }; 19 | 20 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 21 | TEST_ASSERT_TRUE(heap_caps_check_integrity_all(true)); 22 | TEST_ASSERT_TRUE(esp_littlefs_partition_mounted(partition)); 23 | printf("Test setup complete.\n"); 24 | } 25 | 26 | void test_teardown_static(const esp_partition_t* partition) 27 | { 28 | TEST_ESP_OK(esp_vfs_littlefs_unregister_partition(partition)); 29 | TEST_ASSERT_TRUE(heap_caps_check_integrity_all(true)); 30 | TEST_ASSERT_FALSE(esp_littlefs_partition_mounted(partition)); 31 | printf("Test teardown complete.\n"); 32 | } 33 | 34 | esp_partition_t get_test_data_static_partition(void) 35 | { 36 | /* This finds the static partition embedded in the firmware and constructs a partition struct */ 37 | extern const uint8_t partition_blob_start[] asm("_binary_testfs_bin_start"); 38 | extern const uint8_t partition_blob_end[] asm("_binary_testfs_bin_end"); 39 | 40 | return (esp_partition_t){ 41 | .flash_chip = esp_flash_default_chip, 42 | .type = ESP_PARTITION_TYPE_DATA, 43 | .subtype = ESP_PARTITION_SUBTYPE_DATA_FAT, 44 | .address = spi_flash_cache2phys(partition_blob_start), 45 | .size = ((uint32_t)partition_blob_end) - ((uint32_t)partition_blob_start), 46 | .label = "", 47 | .encrypted = false, 48 | }; 49 | } 50 | 51 | TEST_CASE("can read partition", "[littlefs_static]") 52 | { 53 | const esp_partition_t partition = get_test_data_static_partition(); 54 | test_setup_static(&partition); 55 | size_t total = 0, used = 0; 56 | TEST_ESP_OK(esp_littlefs_partition_info(&partition, &total, &used)); 57 | printf("total: %d, used: %d\n", total, used); 58 | TEST_ASSERT_EQUAL(20480, used); // The test FS has 5 used blocks of 4K 59 | test_teardown_static(&partition); 60 | } 61 | 62 | TEST_CASE("can read file", "[littlefs_static]") 63 | { 64 | const esp_partition_t partition = get_test_data_static_partition(); 65 | test_setup_static(&partition); 66 | 67 | test_littlefs_read_file_with_content(littlefs_base_path "/test1.txt", "test1"); 68 | test_littlefs_read_file_with_content(littlefs_base_path "/test2.txt", "test2"); 69 | test_littlefs_read_file_with_content(littlefs_base_path "/pangram.txt", "The quick brown fox jumps over the lazy dog"); 70 | test_littlefs_read_file_with_content( 71 | littlefs_base_path "/test_folder/lorem_ipsum.txt", 72 | "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet " 73 | "dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit " 74 | "lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit " 75 | "esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio " 76 | "dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber " 77 | "tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. " 78 | "Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes " 79 | "demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui " 80 | "sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, " 81 | "anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc " 82 | "nobis videntur parum clari, fiant sollemnes in futurum."); 83 | 84 | test_teardown_static(&partition); 85 | } 86 | 87 | TEST_CASE("can't create file", "[littlefs_static]") 88 | { 89 | const esp_partition_t partition = get_test_data_static_partition(); 90 | test_setup_static(&partition); 91 | 92 | FILE* f = fopen(littlefs_base_path "/new_file.txt", "wb"); 93 | TEST_ASSERT_NULL(f); 94 | test_teardown_static(&partition); 95 | } 96 | 97 | TEST_CASE("can't delete file", "[littlefs_static]") 98 | { 99 | const esp_partition_t partition = get_test_data_static_partition(); 100 | test_setup_static(&partition); 101 | 102 | TEST_ASSERT_EQUAL(unlink(littlefs_base_path "/test1.txt"), -1); 103 | test_littlefs_read_file_with_content(littlefs_base_path "/test1.txt", "test1"); 104 | 105 | test_teardown_static(&partition); 106 | } 107 | 108 | TEST_CASE("can't write to file", "[littlefs_static]") 109 | { 110 | const esp_partition_t partition = get_test_data_static_partition(); 111 | test_setup_static(&partition); 112 | 113 | FILE* f = fopen(littlefs_base_path "/test1.txt", "wb"); 114 | TEST_ASSERT_NULL(f); 115 | test_littlefs_read_file_with_content(littlefs_base_path "/test1.txt", "test1"); 116 | 117 | test_teardown_static(&partition); 118 | } 119 | 120 | TEST_CASE("grow filesystem", "[littlefs]") 121 | { 122 | esp_partition_t partition; 123 | const esp_partition_t *partition_ro = esp_partition_find_first( 124 | ESP_PARTITION_TYPE_DATA, 125 | ESP_PARTITION_SUBTYPE_ANY, 126 | littlefs_test_partition_label 127 | ); 128 | 129 | esp_vfs_littlefs_conf_t conf = { 130 | .base_path = littlefs_base_path, 131 | .partition = (const esp_partition_t *) &partition, 132 | }; 133 | 134 | size_t shrink_bytes; 135 | 136 | /* Format a smaller partition */ 137 | { 138 | memcpy(&partition, partition_ro, sizeof(esp_partition_t)); 139 | // Shrink the partition by 2 blocks 140 | partition.size -= 8192; 141 | TEST_ESP_OK(esp_littlefs_format_partition(&partition)); 142 | partition.size += 8192; 143 | } 144 | 145 | /* Mount, ensure that it does NOT grow */ 146 | { 147 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 148 | TEST_ESP_OK(esp_littlefs_partition_info(&partition, &shrink_bytes, NULL)); 149 | TEST_ESP_OK(esp_vfs_littlefs_unregister_partition(&partition)); 150 | TEST_ASSERT_EQUAL(partition.size - 8192, shrink_bytes); 151 | } 152 | 153 | /* Mount, ensure that it DOES grow */ 154 | { 155 | size_t grow_bytes; 156 | conf.grow_on_mount = true; 157 | TEST_ESP_OK(esp_vfs_littlefs_register(&conf)); 158 | TEST_ESP_OK(esp_littlefs_partition_info(&partition, &grow_bytes, NULL)); 159 | TEST_ESP_OK(esp_vfs_littlefs_unregister_partition(&partition)); 160 | TEST_ASSERT_EQUAL(shrink_bytes + 8192, grow_bytes); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/testfs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joltwallet/esp_littlefs/8964c59ae4c591390ae09b5b5e298d0e8e74bdb0/test/testfs.bin -------------------------------------------------------------------------------- /testdir/pangram.txt: -------------------------------------------------------------------------------- 1 | The quick brown fox jumps over the lazy dog -------------------------------------------------------------------------------- /testdir/test1.txt: -------------------------------------------------------------------------------- 1 | test1 -------------------------------------------------------------------------------- /testdir/test2.txt: -------------------------------------------------------------------------------- 1 | test2 -------------------------------------------------------------------------------- /testdir/test_folder/lorem_ipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum. --------------------------------------------------------------------------------