├── .mbedignore ├── TESTS ├── util │ ├── template_subunit.fmt │ ├── clean.sh │ ├── template_unit.fmt │ ├── Makefile │ ├── stats.py │ ├── echo.py │ ├── test.py │ ├── replacements_mbed.yml │ ├── replacements_retarget.yml │ └── template_wrapper.fmt ├── COMMON │ └── atomic_usage.h ├── filesystem_recovery │ ├── resilience │ │ └── main.cpp │ ├── resilience_functional │ │ └── main.cpp │ └── wear_leveling │ │ └── main.cpp ├── host_tests │ └── unexpected_reset.py ├── filesystem_integration │ └── format │ │ └── main.cpp └── filesystem │ └── interspersed │ └── main.cpp ├── littlefs ├── .gitignore ├── scripts │ ├── readblock.py │ ├── prefix.py │ ├── readtree.py │ ├── readmdir.py │ └── explode_asserts.py ├── lfs2_util.c ├── Makefile ├── LICENSE.md ├── bd │ ├── lfs2_rambd.h │ ├── lfs2_filebd.h │ ├── lfs2_testbd.h │ ├── lfs2_rambd.c │ ├── lfs2_filebd.c │ └── lfs2_testbd.c ├── tests │ ├── test_superblocks.toml │ ├── test_orphans.toml │ ├── test_badblocks.toml │ ├── test_interspersed.toml │ ├── test_paths.toml │ ├── test_evil.toml │ └── test_relocations.toml ├── lfs2_util.h └── README.md ├── .travis.yml ├── mbed_lib.json ├── README.md └── LittleFileSystem2.h /.mbedignore: -------------------------------------------------------------------------------- 1 | littlefs/emubd/ 2 | littlefs/tests/ 3 | TESTS/util 4 | -------------------------------------------------------------------------------- /TESTS/util/template_subunit.fmt: -------------------------------------------------------------------------------- 1 | 2 | {{ 3 | {test} 4 | }} 5 | -------------------------------------------------------------------------------- /TESTS/util/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -f main.cpp 4 | rm -f template_all_names.txt 5 | -------------------------------------------------------------------------------- /TESTS/util/template_unit.fmt: -------------------------------------------------------------------------------- 1 | 2 | void {test_name}() {{ 3 | int res = bd.init(); 4 | TEST_ASSERT_EQUAL(0, res); 5 | {test} 6 | res = bd.deinit(); 7 | TEST_ASSERT_EQUAL(0, res); 8 | }} 9 | -------------------------------------------------------------------------------- /littlefs/.gitignore: -------------------------------------------------------------------------------- 1 | # Compilation output 2 | *.o 3 | *.d 4 | *.a 5 | 6 | # Testing things 7 | blocks/ 8 | lfs2 9 | test.c 10 | tests/*.toml.* 11 | scripts/__pycache__ 12 | .gdb_history 13 | -------------------------------------------------------------------------------- /TESTS/util/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: test_dirs test_files test_seek test_parallel 3 | 4 | test_%: ../../littlefs/tests/test_%.sh 5 | cp $< $(notdir $<) 6 | sed -i -e 's/tests\//.\//' -e 's/echo/.\/echo.py/' $(notdir $<) 7 | 8 | ./clean.sh 9 | ln -f -s replacements_mbed.yml replacements.yml 10 | ./$(notdir $<) 11 | mkdir -p ../filesystem/$(patsubst test_%,%,$@) 12 | cp main.cpp ../filesystem/$(patsubst test_%,%,$@)/main.cpp 13 | 14 | ./clean.sh 15 | ln -f -s replacements_retarget.yml replacements.yml 16 | ./$(notdir $<) 17 | mkdir -p ../filesystem_retarget/$(patsubst test_%,%,$@) 18 | cp main.cpp ../filesystem_retarget/$(patsubst test_%,%,$@)/main.cpp 19 | 20 | clean: 21 | ./clean.sh 22 | -------------------------------------------------------------------------------- /littlefs/scripts/readblock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess as sp 4 | 5 | def main(args): 6 | with open(args.disk, 'rb') as f: 7 | f.seek(args.block * args.block_size) 8 | block = (f.read(args.block_size) 9 | .ljust(args.block_size, b'\xff')) 10 | 11 | # what did you expect? 12 | print("%-8s %-s" % ('off', 'data')) 13 | return sp.run(['xxd', '-g1', '-'], input=block).returncode 14 | 15 | if __name__ == "__main__": 16 | import argparse 17 | import sys 18 | parser = argparse.ArgumentParser( 19 | description="Hex dump a specific block in a disk.") 20 | parser.add_argument('disk', 21 | help="File representing the block device.") 22 | parser.add_argument('block_size', type=lambda x: int(x, 0), 23 | help="Size of a block in bytes.") 24 | parser.add_argument('block', type=lambda x: int(x, 0), 25 | help="Address of block to dump.") 26 | sys.exit(main(parser.parse_args())) 27 | -------------------------------------------------------------------------------- /TESTS/util/stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import subprocess 6 | import os 7 | 8 | def main(*args): 9 | with open('main.cpp') as file: 10 | tests = file.read() 11 | 12 | cases = [] 13 | with open('template_all_names.txt') as file: 14 | while True: 15 | name = file.readline().strip('\n') 16 | desc = file.readline().strip('\n') 17 | if name == 'test_results': 18 | break 19 | 20 | cases.append((name, desc)) 21 | 22 | with open('template_wrapper.fmt') as file: 23 | template = file.read() 24 | 25 | with open('main.cpp', 'w') as file: 26 | file.write(template.format( 27 | tests=tests, 28 | test_cases='\n'.join( 29 | 4*' '+'Case("{desc}", {name}),'.format( 30 | name=name, desc=desc) for name, desc in cases))) 31 | 32 | if __name__ == "__main__": 33 | main(*sys.argv[1:]) 34 | -------------------------------------------------------------------------------- /TESTS/util/echo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import subprocess 6 | import os 7 | 8 | def main(*args): 9 | desc = ' '.join(args).strip('-= ') 10 | name = 'test_' + desc.lower().replace(' ', '_').replace('-', '_') 11 | 12 | exists = os.path.isfile('template_all_names.txt') 13 | 14 | with open('template_all_names.txt', 'a') as file: 15 | file.write(name + '\n') 16 | file.write(desc + '\n') 17 | 18 | with open('template_unit.fmt') as file: 19 | template = file.read() 20 | 21 | template_header, template_footer = template.split('{test}') 22 | 23 | if exists: 24 | with open('main.cpp', 'a') as file: 25 | file.write(template_footer.format( 26 | test_name=name)) 27 | 28 | if name != 'test_results': 29 | with open('main.cpp', 'a') as file: 30 | file.write(template_header.format( 31 | test_name=name)) 32 | 33 | if __name__ == "__main__": 34 | main(*sys.argv[1:]) 35 | -------------------------------------------------------------------------------- /littlefs/lfs2_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs2 util functions 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include "lfs2_util.h" 8 | 9 | // Only compile if user does not provide custom config 10 | #ifndef LFS2_CONFIG 11 | #ifndef __MBED__ 12 | 13 | // Software CRC implementation with small lookup table 14 | uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) { 15 | static const uint32_t rtable[16] = { 16 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 17 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 18 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 19 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 20 | }; 21 | 22 | const uint8_t *data = buffer; 23 | 24 | for (size_t i = 0; i < size; i++) { 25 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 26 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 27 | } 28 | 29 | return crc; 30 | } 31 | 32 | #endif 33 | #endif 34 | -------------------------------------------------------------------------------- /littlefs/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = lfs2.a 2 | ifneq ($(wildcard test.c main.c),) 3 | override TARGET = lfs2 4 | endif 5 | 6 | CC ?= gcc 7 | AR ?= ar 8 | SIZE ?= size 9 | 10 | SRC += $(wildcard *.c bd/*.c) 11 | OBJ := $(SRC:.c=.o) 12 | DEP := $(SRC:.c=.d) 13 | ASM := $(SRC:.c=.s) 14 | 15 | ifdef DEBUG 16 | override CFLAGS += -O0 -g3 17 | else 18 | override CFLAGS += -Os 19 | endif 20 | ifdef WORD 21 | override CFLAGS += -m$(WORD) 22 | endif 23 | ifdef TRACE 24 | override CFLAGS += -DLFS2_YES_TRACE 25 | endif 26 | override CFLAGS += -I. 27 | override CFLAGS += -std=c99 -Wall -pedantic 28 | override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef 29 | # Remove missing-field-initializers because of GCC bug 30 | override CFLAGS += -Wno-missing-field-initializers 31 | 32 | ifdef VERBOSE 33 | override TFLAGS += -v 34 | endif 35 | 36 | 37 | all: $(TARGET) 38 | 39 | asm: $(ASM) 40 | 41 | size: $(OBJ) 42 | $(SIZE) -t $^ 43 | 44 | test: 45 | ./scripts/test.py $(TFLAGS) 46 | .SECONDEXPANSION: 47 | test%: tests/test$$(firstword $$(subst \#, ,%)).toml 48 | ./scripts/test.py $@ $(TFLAGS) 49 | 50 | -include $(DEP) 51 | 52 | lfs2: $(OBJ) 53 | $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ 54 | 55 | %.a: $(OBJ) 56 | $(AR) rcs $@ $^ 57 | 58 | %.o: %.c 59 | $(CC) -c -MMD $(CFLAGS) $< -o $@ 60 | 61 | %.s: %.c 62 | $(CC) -S $(CFLAGS) $< -o $@ 63 | 64 | clean: 65 | rm -f $(TARGET) 66 | rm -f $(OBJ) 67 | rm -f $(DEP) 68 | rm -f $(ASM) 69 | rm -f tests/*.toml.* 70 | -------------------------------------------------------------------------------- /littlefs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Arm Limited. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | - Redistributions in binary form must reproduce the above copyright notice, this 9 | list of conditions and the following disclaimer in the documentation and/or 10 | other materials provided with the distribution. 11 | - Neither the name of ARM nor the names of its contributors may be used to 12 | endorse or promote products derived from this software without specific prior 13 | written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /TESTS/util/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import subprocess 6 | import os 7 | import yaml 8 | 9 | def generate(test): 10 | with open('replacements.yml') as file: 11 | replacements = yaml.load(file) 12 | 13 | lines = [] 14 | for line in re.split('(?<=[;{}])\n', test.read()): 15 | for pattern, replacement in replacements: 16 | line = re.sub(pattern, replacement, line, 0, re.DOTALL | re.MULTILINE) 17 | 18 | match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE) 19 | if match: 20 | tab, test, expect = match.groups() 21 | lines.append(tab+'res = {test};'.format(test=test.strip())) 22 | lines.append(tab+'TEST_ASSERT_EQUAL({expect}, res);'.format( 23 | name=re.match('\w*', test.strip()).group(), 24 | expect=expect.strip())) 25 | else: 26 | lines.append(line) 27 | 28 | lines = lines[:-1] 29 | 30 | with open('template_subunit.fmt') as file: 31 | template = file.read() 32 | 33 | with open('main.cpp', 'a') as file: 34 | file.write(template.format( 35 | test=('\n'.join( 36 | 4*' '+line.replace('\n', '\n'+4*' ') 37 | for line in lines)))) 38 | 39 | def main(test=None): 40 | if test and not test.startswith('-'): 41 | with open(test) as file: 42 | generate(file) 43 | else: 44 | generate(sys.stdin) 45 | 46 | 47 | if __name__ == "__main__": 48 | main(*sys.argv[1:]) 49 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 2.7 3 | 4 | script: 5 | # Check that example compiles 6 | - sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp 7 | - PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F 8 | --source=. --build=BUILD/K82F/GCC_ARM -j0 9 | 10 | # Check that tests compile 11 | - rm -rf main.cpp BUILD 12 | - PYTHONPATH=mbed-os python mbed-os/tools/test.py -t GCC_ARM -m K82F 13 | --source=. --build=BUILD/TESTS/K82F/GCC_ARM -j0 14 | -n 'tests*' 15 | 16 | # Run littlefs functional tests 17 | - make -Clittlefs test QUIET=1 18 | 19 | # Run littlefs functional tests with different configurations 20 | # Note: r/w size of 64 is default in mbed 21 | - make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=64 -DLFS2_CACHE_SIZE=64" 22 | - make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=1 -DLFS2_CACHE_SIZE=1" 23 | - make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=512 -DLFS2_CACHE_SIZE=512 -DLFS2_BLOCK_CYCLES=16" 24 | - make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=8 -DLFS2_CACHE_SIZE=16 -DLFS2_BLOCK_CYCLES=2" 25 | - make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_BLOCK_COUNT=1023 -DLFS2_LOOKAHEAD_SIZE=256" 26 | 27 | install: 28 | # Get arm-none-eabi-gcc 29 | - sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa 30 | - sudo apt-get update -qq 31 | - sudo apt-get install -qq gcc-arm-embedded 32 | # Get dependencies 33 | - git clone https://github.com/armmbed/mbed-os.git 34 | # Install python dependencies 35 | - pip install -r mbed-os/requirements.txt 36 | - sudo apt-get install python3 python3-pip 37 | - sudo pip3 install toml 38 | # Check versions 39 | - arm-none-eabi-gcc --version 40 | - gcc --version 41 | -------------------------------------------------------------------------------- /TESTS/util/replacements_mbed.yml: -------------------------------------------------------------------------------- 1 | - ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)'] 2 | - ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)'] 3 | - ['lfs2_unmount\(&lfs2\)', 'fs.unmount()'] 4 | - ['lfs2_mkdir\(&lfs2, (.*)\)', 'fs.mkdir(\1, 0777)'] 5 | - ['lfs2_remove\(&lfs2, (.*)\)', 'fs.remove(\1)'] 6 | - ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'fs.rename(\1, \2)'] 7 | 8 | - ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', 'dir[\1].open(&fs, \2)'] 9 | - ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].close()'] 10 | - ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', 'dir[\1].read(&ent)'] 11 | - ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'dir[\1].seek(\2);'] # no dir errors 12 | - ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'dir[\1].rewind();'] # no dir errors 13 | - ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].tell()'] 14 | 15 | - ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].open(&fs, \2)'] 16 | - ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'file[\1].close()'] 17 | - ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'file[\1].sync()'] 18 | - ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].write(\2, \3)'] 19 | - ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].read(\2, \3)'] 20 | - ['lfs2_file_seek\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].seek(\2)'] 21 | - ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'file[\1].tell()'] 22 | - ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'file[\1].rewind();'] # no errors 23 | - ['lfs2_file_size\(&lfs2, &file\[(.*)\]\)', 'file[\1].size()'] 24 | 25 | - ['LFS2_TYPE_([A-Z]+)', 'DT_\1'] 26 | - ['LFS2_O_([A-Z]+)', 'O_\1'] 27 | - ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1'] 28 | - ['LFS2_ERR_([A-Z]+)', '-E\1'] 29 | - ['lfs2_(s?)size_t', '\1size_t'] 30 | - ['lfs2_soff_t', 'off_t'] 31 | - ['info\.name', 'ent.d_name'] 32 | - ['info\.type', 'ent.d_type'] 33 | - ['^.*info\.size.*$', ''] # dirent sizes not supported 34 | -------------------------------------------------------------------------------- /TESTS/util/replacements_retarget.yml: -------------------------------------------------------------------------------- 1 | - ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)'] 2 | - ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)'] 3 | - ['lfs2_unmount\(&lfs2\)', 'fs.unmount()'] 4 | - ['lfs2_mkdir\(&lfs2, (.*)\)', 'mkdir("/fs/" \1, 0777)'] 5 | - ['lfs2_remove\(&lfs2, (.*)\)', 'remove("/fs/" \1)'] 6 | - ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'rename("/fs/" \1, "/fs/" \2)'] 7 | 8 | - ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', '!((dd[\1] = opendir("/fs/" \2)) != NULL)'] 9 | - ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'closedir(dd[\1])'] 10 | - ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', '((ed = readdir(dd[\1])) != NULL)'] 11 | - ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'seekdir(dd[\1], \2);'] # no dir errors 12 | - ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'rewinddir(dd[\1]);'] # no dir errors 13 | - ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'telldir(dd[\1])'] 14 | 15 | - ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', '!((fd[\1] = fopen("/fs/" \2)) != NULL)'] 16 | - ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'fclose(fd[\1])'] 17 | - ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'fflush(fd[\1])'] 18 | - ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fwrite(\2, 1, \3, fd[\1])'] 19 | - ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fread(\2, 1, \3, fd[\1])'] 20 | - ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'ftell(fd[\1])'] 21 | - ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'rewind(fd[\1]);'] # no errors 22 | 23 | - ['LFS2_TYPE_([A-Z]+)', 'DT_\1'] 24 | - ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1'] 25 | - ['LFS2_ERR_([A-Z]+)', '-E\1'] 26 | - ['lfs2_(s?)size_t', '\1size_t'] 27 | - ['lfs2_soff_t', 'off_t'] 28 | - ['info\.name', 'ed->d_name'] 29 | - ['info\.type', 'ed->d_type'] 30 | - ['^.*info\.size.*$', ''] # dirent sizes not supported 31 | 32 | - ['LFS2_O_WRONLY \| LFS2_O_CREAT \| LFS2_O_APPEND', '"ab"'] 33 | - ['LFS2_O_WRONLY \| LFS2_O_TRUNC', '"wb"'] 34 | - ['LFS2_O_CREAT \| LFS2_O_WRONLY', '"wb"'] 35 | - ['LFS2_O_WRONLY \| LFS2_O_CREAT', '"wb"'] 36 | - ['LFS2_O_RDONLY', '"rb"'] 37 | - ['LFS2_O_RDWR', '"r+b"'] 38 | -------------------------------------------------------------------------------- /littlefs/scripts/prefix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # This script replaces prefixes of files, and symbols in that file. 4 | # Useful for creating different versions of the codebase that don't 5 | # conflict at compile time. 6 | # 7 | # example: 8 | # $ ./scripts/prefix.py lfs22 9 | 10 | import os 11 | import os.path 12 | import re 13 | import glob 14 | import itertools 15 | import tempfile 16 | import shutil 17 | import subprocess 18 | 19 | DEFAULT_PREFIX = "lfs2" 20 | 21 | def subn(from_prefix, to_prefix, name): 22 | name, count1 = re.subn('\\b'+from_prefix, to_prefix, name) 23 | name, count2 = re.subn('\\b'+from_prefix.upper(), to_prefix.upper(), name) 24 | name, count3 = re.subn('\\B-D'+from_prefix.upper(), 25 | '-D'+to_prefix.upper(), name) 26 | return name, count1+count2+count3 27 | 28 | def main(from_prefix, to_prefix=None, files=None): 29 | if not to_prefix: 30 | from_prefix, to_prefix = DEFAULT_PREFIX, from_prefix 31 | 32 | if not files: 33 | files = subprocess.check_output([ 34 | 'git', 'ls-tree', '-r', '--name-only', 'HEAD']).split() 35 | 36 | for oldname in files: 37 | # Rename any matching file names 38 | newname, namecount = subn(from_prefix, to_prefix, oldname) 39 | if namecount: 40 | subprocess.check_call(['git', 'mv', oldname, newname]) 41 | 42 | # Rename any prefixes in file 43 | count = 0 44 | with open(newname+'~', 'w') as tempf: 45 | with open(newname) as newf: 46 | for line in newf: 47 | line, n = subn(from_prefix, to_prefix, line) 48 | count += n 49 | tempf.write(line) 50 | shutil.copystat(newname, newname+'~') 51 | os.rename(newname+'~', newname) 52 | subprocess.check_call(['git', 'add', newname]) 53 | 54 | # Summary 55 | print '%s: %d replacements' % ( 56 | '%s -> %s' % (oldname, newname) if namecount else oldname, 57 | count) 58 | 59 | if __name__ == "__main__": 60 | import sys 61 | sys.exit(main(*sys.argv[1:])) 62 | -------------------------------------------------------------------------------- /TESTS/util/template_wrapper.fmt: -------------------------------------------------------------------------------- 1 | #include "mbed.h" 2 | #include "greentea-client/test_env.h" 3 | #include "unity.h" 4 | #include "utest.h" 5 | #include 6 | #include 7 | 8 | using namespace utest::v1; 9 | 10 | // test configuration 11 | #ifndef MBED_TEST_FILESYSTEM 12 | #define MBED_TEST_FILESYSTEM LittleFileSystem2 13 | #endif 14 | 15 | #ifndef MBED_TEST_FILESYSTEM_DECL 16 | #define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") 17 | #endif 18 | 19 | #ifndef MBED_TEST_BLOCKDEVICE 20 | #define MBED_TEST_BLOCKDEVICE SPIFBlockDevice 21 | #define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5) 22 | #endif 23 | 24 | #ifndef MBED_TEST_BLOCKDEVICE_DECL 25 | #define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd 26 | #endif 27 | 28 | #ifndef MBED_TEST_FILES 29 | #define MBED_TEST_FILES 4 30 | #endif 31 | 32 | #ifndef MBED_TEST_DIRS 33 | #define MBED_TEST_DIRS 4 34 | #endif 35 | 36 | #ifndef MBED_TEST_BUFFER 37 | #define MBED_TEST_BUFFER 8192 38 | #endif 39 | 40 | #ifndef MBED_TEST_TIMEOUT 41 | #define MBED_TEST_TIMEOUT 120 42 | #endif 43 | 44 | 45 | // declarations 46 | #define STRINGIZE(x) STRINGIZE2(x) 47 | #define STRINGIZE2(x) #x 48 | #define INCLUDE(x) STRINGIZE(x.h) 49 | 50 | #include INCLUDE(MBED_TEST_FILESYSTEM) 51 | #include INCLUDE(MBED_TEST_BLOCKDEVICE) 52 | 53 | MBED_TEST_FILESYSTEM_DECL; 54 | MBED_TEST_BLOCKDEVICE_DECL; 55 | 56 | Dir dir[MBED_TEST_DIRS]; 57 | File file[MBED_TEST_FILES]; 58 | DIR *dd[MBED_TEST_DIRS]; 59 | FILE *fd[MBED_TEST_FILES]; 60 | struct dirent ent; 61 | struct dirent *ed; 62 | size_t size; 63 | uint8_t buffer[MBED_TEST_BUFFER]; 64 | uint8_t rbuffer[MBED_TEST_BUFFER]; 65 | uint8_t wbuffer[MBED_TEST_BUFFER]; 66 | 67 | 68 | // tests 69 | {tests} 70 | 71 | 72 | // test setup 73 | utest::v1::status_t test_setup(const size_t number_of_cases) {{ 74 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); 75 | return verbose_test_setup_handler(number_of_cases); 76 | }} 77 | 78 | Case cases[] = {{ 79 | {test_cases} 80 | }}; 81 | 82 | Specification specification(test_setup, cases); 83 | 84 | int main() {{ 85 | return !Harness::run(specification); 86 | }} 87 | -------------------------------------------------------------------------------- /littlefs/bd/lfs2_rambd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in RAM 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS2_RAMBD_H 8 | #define LFS2_RAMBD_H 9 | 10 | #include "lfs2.h" 11 | #include "lfs2_util.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" 15 | { 16 | #endif 17 | 18 | 19 | // Block device specific tracing 20 | #ifdef LFS2_RAMBD_YES_TRACE 21 | #define LFS2_RAMBD_TRACE(...) LFS2_TRACE(__VA_ARGS__) 22 | #else 23 | #define LFS2_RAMBD_TRACE(...) 24 | #endif 25 | 26 | // rambd config (optional) 27 | struct lfs2_rambd_config { 28 | // 8-bit erase value to simulate erasing with. -1 indicates no erase 29 | // occurs, which is still a valid block device 30 | int32_t erase_value; 31 | 32 | // Optional statically allocated buffer for the block device. 33 | void *buffer; 34 | }; 35 | 36 | // rambd state 37 | typedef struct lfs2_rambd { 38 | uint8_t *buffer; 39 | const struct lfs2_rambd_config *cfg; 40 | } lfs2_rambd_t; 41 | 42 | 43 | // Create a RAM block device using the geometry in lfs2_config 44 | int lfs2_rambd_create(const struct lfs2_config *cfg); 45 | int lfs2_rambd_createcfg(const struct lfs2_config *cfg, 46 | const struct lfs2_rambd_config *bdcfg); 47 | 48 | // Clean up memory associated with block device 49 | int lfs2_rambd_destroy(const struct lfs2_config *cfg); 50 | 51 | // Read a block 52 | int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block, 53 | lfs2_off_t off, void *buffer, lfs2_size_t size); 54 | 55 | // Program a block 56 | // 57 | // The block must have previously been erased. 58 | int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 59 | lfs2_off_t off, const void *buffer, lfs2_size_t size); 60 | 61 | // Erase a block 62 | // 63 | // A block must be erased before being programmed. The 64 | // state of an erased block is undefined. 65 | int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block); 66 | 67 | // Sync the block device 68 | int lfs2_rambd_sync(const struct lfs2_config *cfg); 69 | 70 | 71 | #ifdef __cplusplus 72 | } /* extern "C" */ 73 | #endif 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /littlefs/bd/lfs2_filebd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in a file 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS2_FILEBD_H 8 | #define LFS2_FILEBD_H 9 | 10 | #include "lfs2.h" 11 | #include "lfs2_util.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" 15 | { 16 | #endif 17 | 18 | 19 | // Block device specific tracing 20 | #ifdef LFS2_FILEBD_YES_TRACE 21 | #define LFS2_FILEBD_TRACE(...) LFS2_TRACE(__VA_ARGS__) 22 | #else 23 | #define LFS2_FILEBD_TRACE(...) 24 | #endif 25 | 26 | // filebd config (optional) 27 | struct lfs2_filebd_config { 28 | // 8-bit erase value to use for simulating erases. -1 does not simulate 29 | // erases, which can speed up testing by avoiding all the extra block-device 30 | // operations to store the erase value. 31 | int32_t erase_value; 32 | }; 33 | 34 | // filebd state 35 | typedef struct lfs2_filebd { 36 | int fd; 37 | const struct lfs2_filebd_config *cfg; 38 | } lfs2_filebd_t; 39 | 40 | 41 | // Create a file block device using the geometry in lfs2_config 42 | int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path); 43 | int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path, 44 | const struct lfs2_filebd_config *bdcfg); 45 | 46 | // Clean up memory associated with block device 47 | int lfs2_filebd_destroy(const struct lfs2_config *cfg); 48 | 49 | // Read a block 50 | int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block, 51 | lfs2_off_t off, void *buffer, lfs2_size_t size); 52 | 53 | // Program a block 54 | // 55 | // The block must have previously been erased. 56 | int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 57 | lfs2_off_t off, const void *buffer, lfs2_size_t size); 58 | 59 | // Erase a block 60 | // 61 | // A block must be erased before being programmed. The 62 | // state of an erased block is undefined. 63 | int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block); 64 | 65 | // Sync the block device 66 | int lfs2_filebd_sync(const struct lfs2_config *cfg); 67 | 68 | 69 | #ifdef __cplusplus 70 | } /* extern "C" */ 71 | #endif 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /mbed_lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "littlefs2", 3 | "config": { 4 | "block_size": { 5 | "macro_name": "MBED_LFS2_BLOCK_SIZE", 6 | "value": 512, 7 | "help": "Size of a logical block. This does not impact ram consumption and may be larger than the physical erase block. If the physical erase block is larger, littlefs will use that instead. Larger values will be faster but waste more storage when files are not aligned to a block size." 8 | }, 9 | "block_cycles": { 10 | "macro_name": "MBED_LFS2_BLOCK_CYCLES", 11 | "value": 1024, 12 | "help": "Number of erase cycles before a block is forcefully evicted. Larger values are more efficient but cause less even wear distribution. 0 disables dynamic wear-leveling." 13 | }, 14 | "cache_size": { 15 | "macro_name": "MBED_LFS2_CACHE_SIZE", 16 | "value": "64", 17 | "help": "Size of read/program caches. Each file uses 1 cache, and littlefs allocates 2 caches for internal operations. Larger values should be faster but uses more RAM." 18 | }, 19 | "lookahead_size": { 20 | "macro_name": "MBED_LFS2_LOOKAHEAD_SIZE", 21 | "value": 64, 22 | "help": "Size of the lookahead buffer. A larger lookahead reduces the allocation scans and results in a faster filesystem but uses more RAM." 23 | }, 24 | "intrinsics": { 25 | "macro_name": "MBED_LFS2_INTRINSICS", 26 | "value": true, 27 | "help": "Enable intrinsics for bit operations such as ctz, popc, and le32 conversion. Can be disabled to help debug toolchain issues" 28 | }, 29 | "enable_info": { 30 | "macro_name": "MBED_LFS2_ENABLE_INFO", 31 | "value": false, 32 | "help": "Enables info logging, true = enabled, false = disabled, null = disabled only in release builds" 33 | }, 34 | "enable_debug": { 35 | "macro_name": "MBED_LFS2_ENABLE_DEBUG", 36 | "value": null, 37 | "help": "Enables debug logging, true = enabled, false = disabled, null = disabled only in release builds" 38 | }, 39 | "enable_warn": { 40 | "macro_name": "MBED_LFS2_ENABLE_WARN", 41 | "value": null, 42 | "help": "Enables warn logging, true = enabled, false = disabled, null = disabled only in release builds" 43 | }, 44 | "enable_error": { 45 | "macro_name": "MBED_LFS2_ENABLE_ERROR", 46 | "value": null, 47 | "help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds" 48 | }, 49 | "enable_assert": { 50 | "macro_name": "MBED_LFS2_ENABLE_ASSERT", 51 | "value": null, 52 | "help": "Enables asserts, true = enabled, false = disabled, null = disabled only in release builds" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TESTS/COMMON/atomic_usage.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | #ifndef MBED_ATOMIC_USAGE_H 23 | #define MBED_ATOMIC_USAGE_H 24 | 25 | #include "BlockDevice.h" 26 | 27 | /** 28 | * Setup the given block device to test littlefs atomic operations 29 | * 30 | * Format the blockdevice with a littlefs filesystem and create 31 | * the files and directories required to test atomic operations. 32 | * 33 | * @param bd Block device format and setup 34 | * @param force_rebuild Force a reformat even if the device is already setup 35 | * @return true if the block device was formatted, false otherwise 36 | * @note utest asserts are used to detect fatal errors so utest must be 37 | * initialized before calling this function. 38 | */ 39 | bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild); 40 | 41 | /** 42 | * Perform a set of atomic littlefs operations on the block device 43 | * 44 | * Mount the block device as a littlefs filesystem and a series of 45 | * atomic operations on it. Since the operations performed are atomic 46 | * the file system will always be in a well defined state. The block 47 | * device must have been setup by calling setup_atomic_operations. 48 | * 49 | * @param bd Block device to perform the operations on 50 | * @return -1 if flash is exhausted, otherwise the cycle count on the fs 51 | * @note utest asserts are used to detect fatal errors so utest must be 52 | * initialized before calling this function. 53 | */ 54 | int64_t perform_atomic_operations(BlockDevice *bd); 55 | 56 | /** 57 | * Check that the littlefs image on the block device is in a good state 58 | * 59 | * Mount the block device as a littlefs filesystem and check the files 60 | * and directories to ensure they are valid. Since all the operations 61 | * performed are atomic the filesystem should always be in a good 62 | * state. 63 | * 64 | * @param bd Block device to check 65 | * @note This function does not change the contents of the block device 66 | * @note utest asserts are used to detect fatal errors so utest must be 67 | * initialized before calling this function. 68 | */ 69 | void check_atomic_operations(BlockDevice *bd); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /TESTS/filesystem_recovery/resilience/main.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "mbed.h" 17 | #include "unity.h" 18 | #include "utest.h" 19 | #include "test_env.h" 20 | 21 | #include "atomic_usage.h" 22 | #include "ObservingBlockDevice.h" 23 | 24 | using namespace utest::v1; 25 | 26 | // test configuration 27 | #ifndef MBED_TEST_SIM_BLOCKDEVICE 28 | #error [NOT_SUPPORTED] Simulation block device required for resilience tests 29 | #endif 30 | 31 | #ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL 32 | #define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) 33 | #endif 34 | 35 | #ifndef MBED_TEST_BLOCK_COUNT 36 | #define MBED_TEST_BLOCK_COUNT 64 37 | #endif 38 | 39 | #ifndef MBED_TEST_CYCLES 40 | #define MBED_TEST_CYCLES 10 41 | #endif 42 | 43 | #ifndef MBED_TEST_TIMEOUT 44 | #define MBED_TEST_TIMEOUT 480 45 | #endif 46 | 47 | // declarations 48 | #define STRINGIZE(x) STRINGIZE2(x) 49 | #define STRINGIZE2(x) #x 50 | #define INCLUDE(x) STRINGIZE(x.h) 51 | 52 | #include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) 53 | 54 | 55 | /** 56 | * Check that the filesystem is valid after every change 57 | * 58 | * This test is to ensure that littlefs contains a valid filesystem at 59 | * all times. This property is required for handling unexpected power 60 | * loss. 61 | */ 62 | void test_resilience() 63 | { 64 | MBED_TEST_SIM_BLOCKDEVICE_DECL; 65 | 66 | // bring up to get block size 67 | bd.init(); 68 | bd_size_t block_size = bd.get_erase_size(); 69 | bd.deinit(); 70 | 71 | SlicingBlockDevice slice(&bd, 0, MBED_TEST_BLOCK_COUNT * block_size); 72 | 73 | // Setup the test 74 | setup_atomic_operations(&slice, true); 75 | 76 | // Run check on every write operation 77 | ObservingBlockDevice observer(&slice); 78 | observer.attach(check_atomic_operations); 79 | 80 | // Perform operations 81 | printf("Performing %i operations on flash\n", MBED_TEST_CYCLES); 82 | for (int i = 1; i <= MBED_TEST_CYCLES; i++) { 83 | int64_t ret = perform_atomic_operations(&observer); 84 | TEST_ASSERT_EQUAL(i, ret); 85 | } 86 | printf("No errors detected\n"); 87 | } 88 | 89 | Case cases[] = { 90 | Case("test resilience", test_resilience), 91 | }; 92 | 93 | utest::v1::status_t greentea_test_setup(const size_t number_of_cases) 94 | { 95 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); 96 | return greentea_test_setup_handler(number_of_cases); 97 | } 98 | 99 | Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); 100 | 101 | int main() 102 | { 103 | Harness::run(specification); 104 | } 105 | -------------------------------------------------------------------------------- /TESTS/filesystem_recovery/resilience_functional/main.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "mbed.h" 17 | #include "unity.h" 18 | #include "utest.h" 19 | #include "test_env.h" 20 | 21 | #include "atomic_usage.h" 22 | #include "ObservingBlockDevice.h" 23 | #include "LittleFileSystem2.h" 24 | 25 | #include 26 | 27 | using namespace utest::v1; 28 | 29 | 30 | // test configuration 31 | #ifndef MBED_TEST_BLOCKDEVICE 32 | #error [NOT_SUPPORTED] Non-volatile block device required for resilience_functional tests 33 | #endif 34 | 35 | #ifndef MBED_TEST_BLOCKDEVICE_DECL 36 | #define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd 37 | #endif 38 | 39 | #ifndef MBED_TEST_BLOCK_COUNT 40 | #define MBED_TEST_BLOCK_COUNT 64 41 | #endif 42 | 43 | #ifndef MBED_TEST_CYCLES 44 | #define MBED_TEST_CYCLES 10 45 | #endif 46 | 47 | #ifndef MBED_TEST_TIMEOUT 48 | #define MBED_TEST_TIMEOUT 480 49 | #endif 50 | 51 | // declarations 52 | #define STRINGIZE(x) STRINGIZE2(x) 53 | #define STRINGIZE2(x) #x 54 | #define INCLUDE(x) STRINGIZE(x.h) 55 | 56 | #include INCLUDE(MBED_TEST_BLOCKDEVICE) 57 | 58 | MBED_TEST_BLOCKDEVICE_DECL; 59 | 60 | typedef enum { 61 | CMD_STATUS_PASS, 62 | CMD_STATUS_FAIL, 63 | CMD_STATUS_CONTINUE, 64 | CMD_STATUS_ERROR 65 | } cmd_status_t; 66 | 67 | void use_filesystem() 68 | { 69 | // Perform operations 70 | while (true) { 71 | int64_t ret = perform_atomic_operations(&bd); 72 | TEST_ASSERT(ret > 0); 73 | } 74 | } 75 | 76 | static cmd_status_t handle_command(const char *key, const char *value) 77 | { 78 | if (strcmp(key, "format") == 0) { 79 | setup_atomic_operations(&bd, true); 80 | greentea_send_kv("format_done", 1); 81 | return CMD_STATUS_CONTINUE; 82 | 83 | } else if (strcmp(key, "run") == 0) { 84 | use_filesystem(); 85 | return CMD_STATUS_CONTINUE; 86 | 87 | } else if (strcmp(key, "exit") == 0) { 88 | if (strcmp(value, "pass") != 0) { 89 | return CMD_STATUS_FAIL; 90 | } 91 | check_atomic_operations(&bd); 92 | return CMD_STATUS_PASS; 93 | 94 | } else { 95 | return CMD_STATUS_ERROR; 96 | 97 | } 98 | } 99 | 100 | int main() 101 | { 102 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "unexpected_reset"); 103 | 104 | static char _key[10 + 1] = {}; 105 | static char _value[128 + 1] = {}; 106 | 107 | greentea_send_kv("start", 1); 108 | 109 | // Handshake with host 110 | cmd_status_t cmd_status = CMD_STATUS_CONTINUE; 111 | while (CMD_STATUS_CONTINUE == cmd_status) { 112 | memset(_key, 0, sizeof(_key)); 113 | memset(_value, 0, sizeof(_value)); 114 | greentea_parse_kv(_key, _value, sizeof(_key) - 1, sizeof(_value) - 1); 115 | cmd_status = handle_command(_key, _value); 116 | } 117 | 118 | GREENTEA_TESTSUITE_RESULT(CMD_STATUS_PASS == cmd_status); 119 | } 120 | -------------------------------------------------------------------------------- /TESTS/host_tests/unexpected_reset.py: -------------------------------------------------------------------------------- 1 | """ 2 | mbed SDK 3 | Copyright (c) 2017-2017 ARM Limited 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | from __future__ import print_function 18 | 19 | from mbed_host_tests import BaseHostTest 20 | from time import sleep 21 | 22 | 23 | class UnexpectedResetTest(BaseHostTest): 24 | """This test checks that a device's RTC keeps count through a reset 25 | 26 | It does this by setting the RTC's time, triggering a reset, 27 | delaying and then reading the RTC's time again to ensure 28 | that the RTC is still counting. 29 | """ 30 | 31 | """Number of times to reset the device in this test""" 32 | RESET_COUNT = 20 33 | RESET_DELAY_BASE = 1.0 34 | RESET_DELAY_INC = 0.02 35 | VALUE_PLACEHOLDER = "0" 36 | 37 | def setup(self): 38 | """Register callbacks required for the test""" 39 | self._error = False 40 | generator = self.unexpected_reset_test() 41 | generator.next() 42 | 43 | def run_gen(key, value, time): 44 | """Run the generator, and fail testing if the iterator stops""" 45 | if self._error: 46 | return 47 | try: 48 | generator.send((key, value, time)) 49 | except StopIteration: 50 | self._error = True 51 | 52 | for resp in ("start", "read", "format_done", "reset_complete"): 53 | self.register_callback(resp, run_gen) 54 | 55 | def teardown(self): 56 | """No work to do here""" 57 | pass 58 | 59 | def unexpected_reset_test(self): 60 | """Generator for running the reset test 61 | 62 | This function calls yield to wait for the next event from 63 | the device. If the device gives the wrong response, then the 64 | generator terminates by returing which raises a StopIteration 65 | exception and fails the test. 66 | """ 67 | 68 | # Wait for start token 69 | key, value, time = yield 70 | if key != "start": 71 | return 72 | 73 | # Format the device before starting the test 74 | self.send_kv("format", self.VALUE_PLACEHOLDER) 75 | key, value, time = yield 76 | if key != "format_done": 77 | return 78 | 79 | for i in range(self.RESET_COUNT): 80 | 81 | self.send_kv("run", self.VALUE_PLACEHOLDER) 82 | sleep(self.RESET_DELAY_BASE + self.RESET_DELAY_INC * i) 83 | 84 | self.reset() 85 | 86 | # Wait for start token 87 | key, value, time = yield 88 | self.log("Key from yield: %s" % key) 89 | if key != "reset_complete": 90 | return 91 | 92 | 93 | self.send_kv("__sync", "00000000-0000-000000000-000000000000") 94 | 95 | # Wait for start token 96 | key, value, time = yield 97 | if key != "start": 98 | return 99 | 100 | self.send_kv("exit", "pass") 101 | 102 | yield # No more events expected 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Mbed OS API for the little filesystem 2 | 3 | This is the Mbed OS API for littlefs, a little fail-safe filesystem 4 | designed for embedded systems. 5 | 6 | ``` 7 | | | | .---._____ 8 | .-----. | | 9 | --|o |---| littlefs | 10 | --| |---| | 11 | '-----' '----------' 12 | | | | 13 | ``` 14 | 15 | **Bounded RAM/ROM** - The littlefs is designed to work with a limited amount 16 | of memory. Recursion is avoided, and dynamic memory is limited to configurable 17 | buffers that can be provided statically. 18 | 19 | **Power-loss resilient** - The littlefs is designed for systems that may have 20 | random power failures. The littlefs has strong copy-on-write guarantees, and 21 | storage on disk is always kept in a valid state. 22 | 23 | **Wear leveling** - Because the most common form of embedded storage is erodible 24 | flash memories, littlefs provides a form of dynamic wear leveling for systems 25 | that cannot fit a full flash translation layer. 26 | 27 | ## Usage 28 | 29 | If you are already using a filesystem in Mbed, adopting the littlefs should 30 | just require a name change to use the [LittleFileSystem2](LittleFileSystem2.h) 31 | class. 32 | 33 | Here is a simple example that updates a file named "boot_count" every time 34 | the application runs: 35 | ``` c++ 36 | #include "LittleFileSystem2.h" 37 | #include "SPIFBlockDevice.h" 38 | 39 | // Physical block device, can be any device that supports the BlockDevice API 40 | SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5); 41 | 42 | // Storage for the littlefs 43 | LittleFileSystem2 fs("fs"); 44 | 45 | // Entry point 46 | int main() { 47 | // Mount the filesystem 48 | int err = fs.mount(&bd); 49 | if (err) { 50 | // Reformat if we can't mount the filesystem, 51 | // this should only happen on the first boot 52 | LittleFileSystem2::format(&bd); 53 | fs.mount(&bd); 54 | } 55 | 56 | // Read the boot count 57 | uint32_t boot_count = 0; 58 | FILE *f = fopen("/fs/boot_count", "r+"); 59 | if (!f) { 60 | // Create the file if it doesn't exist 61 | f = fopen("/fs/boot_count", "w+"); 62 | } 63 | fread(&boot_count, sizeof(boot_count), 1, f); 64 | 65 | // Update the boot count 66 | boot_count += 1; 67 | rewind(f); 68 | fwrite(&boot_count, sizeof(boot_count), 1, f); 69 | 70 | // Remember that storage may not be updated until the file 71 | // is closed successfully 72 | fclose(f); 73 | 74 | // Release any resources we were using 75 | fs.unmount(); 76 | 77 | // Print the boot count 78 | printf("boot_count: %ld\n", boot_count); 79 | } 80 | ``` 81 | 82 | ## Reference material 83 | 84 | [DESIGN.md](littlefs/DESIGN.md) - DESIGN.md contains a fully detailed dive into 85 | how littlefs actually works. We encourage you to read it because the 86 | solutions and tradeoffs at work here are quite interesting. 87 | 88 | [SPEC.md](littlefs/SPEC.md) - SPEC.md contains the on-disk specification of 89 | littlefs with all the nitty-gritty details. This can be useful for developing 90 | tooling. 91 | 92 | ## Related projects 93 | 94 | [littlefs](https://github.com/geky/littlefs) - Where the core of littlefs 95 | currently lives. 96 | 97 | [littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) 98 | wrapper for littlefs. The project allows you to mount littlefs directly in a 99 | Linux machine. This can be useful for debugging littlefs if you have an SD card 100 | handy. 101 | 102 | [littlefs-js](https://github.com/geky/littlefs-js) - A JavaScript wrapper for 103 | littlefs. I'm not sure why you would want this, but it is handy for demos. 104 | You can see it in action [here](http://littlefs.geky.net/demo.html). 105 | -------------------------------------------------------------------------------- /TESTS/filesystem_recovery/wear_leveling/main.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "mbed.h" 17 | #include "unity.h" 18 | #include "utest.h" 19 | #include "test_env.h" 20 | 21 | #include "atomic_usage.h" 22 | #include "ExhaustibleBlockDevice.h" 23 | #include "SlicingBlockDevice.h" 24 | 25 | using namespace utest::v1; 26 | 27 | // test configuration 28 | #ifndef MBED_TEST_SIM_BLOCKDEVICE 29 | #error [NOT_SUPPORTED] Simulation block device required for wear leveling tests 30 | #endif 31 | 32 | #ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL 33 | #define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512) 34 | #endif 35 | 36 | #ifndef MBED_TEST_BLOCK_COUNT 37 | #define MBED_TEST_BLOCK_COUNT 64 38 | #endif 39 | 40 | #ifndef MBED_TEST_ERASE_CYCLES 41 | #define MBED_TEST_ERASE_CYCLES 100 42 | #endif 43 | 44 | #ifndef MBED_TEST_TIMEOUT 45 | #define MBED_TEST_TIMEOUT 480 46 | #endif 47 | 48 | // declarations 49 | #define STRINGIZE(x) STRINGIZE2(x) 50 | #define STRINGIZE2(x) #x 51 | #define INCLUDE(x) STRINGIZE(x.h) 52 | 53 | #include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) 54 | 55 | 56 | static uint32_t test_wear_leveling_size(uint32_t block_count) 57 | { 58 | MBED_TEST_SIM_BLOCKDEVICE_DECL; 59 | 60 | // bring up to get block size 61 | bd.init(); 62 | bd_size_t block_size = bd.get_erase_size(); 63 | bd.deinit(); 64 | 65 | SlicingBlockDevice slice(&bd, 0, block_count * block_size); 66 | ExhaustibleBlockDevice ebd(&slice, MBED_TEST_ERASE_CYCLES); 67 | 68 | printf("Testing size %llu bytes (%lux%llu) blocks\n", 69 | block_count * block_size, block_count, block_size); 70 | setup_atomic_operations(&ebd, true); 71 | 72 | int64_t cycles = 0; 73 | while (true) { 74 | int64_t ret = perform_atomic_operations(&ebd); 75 | check_atomic_operations(&ebd); 76 | if (-1 == ret) { 77 | break; 78 | } 79 | cycles++; 80 | TEST_ASSERT_EQUAL(cycles, ret); 81 | 82 | } 83 | 84 | printf(" Simulated flash lasted %lli cylces\n", cycles); 85 | return cycles; 86 | } 87 | 88 | /** 89 | * Check that storage life is proportional to storage size 90 | * 91 | * This test is to ensure that littlefs is properly handling wear 92 | * leveling. It does this by creating a simulated flash block device 93 | * which can be worn out and then using it until it is exhausted. 94 | * It then doubles the size of the block device and runs the same 95 | * test. If the block device with twice the size lasts at least 96 | * twice as long then the test passes. 97 | */ 98 | void test_wear_leveling() 99 | { 100 | uint32_t cycles_1 = test_wear_leveling_size(MBED_TEST_BLOCK_COUNT / 2); 101 | uint32_t cycles_2 = test_wear_leveling_size(MBED_TEST_BLOCK_COUNT / 1); 102 | TEST_ASSERT(cycles_2 > cycles_1 * 2); 103 | } 104 | 105 | Case cases[] = { 106 | Case("test wear leveling", test_wear_leveling), 107 | }; 108 | 109 | utest::v1::status_t greentea_test_setup(const size_t number_of_cases) 110 | { 111 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); 112 | return greentea_test_setup_handler(number_of_cases); 113 | } 114 | 115 | Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); 116 | 117 | int main() 118 | { 119 | Harness::run(specification); 120 | } 121 | -------------------------------------------------------------------------------- /littlefs/tests/test_superblocks.toml: -------------------------------------------------------------------------------- 1 | [[case]] # simple formatting test 2 | code = ''' 3 | lfs2_format(&lfs2, &cfg) => 0; 4 | ''' 5 | 6 | [[case]] # mount/unmount 7 | code = ''' 8 | lfs2_format(&lfs2, &cfg) => 0; 9 | lfs2_mount(&lfs2, &cfg) => 0; 10 | lfs2_unmount(&lfs2) => 0; 11 | ''' 12 | 13 | [[case]] # reentrant format 14 | reentrant = true 15 | code = ''' 16 | err = lfs2_mount(&lfs2, &cfg); 17 | if (err) { 18 | lfs2_format(&lfs2, &cfg) => 0; 19 | lfs2_mount(&lfs2, &cfg) => 0; 20 | } 21 | lfs2_unmount(&lfs2) => 0; 22 | ''' 23 | 24 | [[case]] # invalid mount 25 | code = ''' 26 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 27 | ''' 28 | 29 | [[case]] # expanding superblock 30 | define.LFS2_BLOCK_CYCLES = [32, 33, 1] 31 | define.N = [10, 100, 1000] 32 | code = ''' 33 | lfs2_format(&lfs2, &cfg) => 0; 34 | lfs2_mount(&lfs2, &cfg) => 0; 35 | for (int i = 0; i < N; i++) { 36 | lfs2_file_open(&lfs2, &file, "dummy", 37 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 38 | lfs2_file_close(&lfs2, &file) => 0; 39 | lfs2_stat(&lfs2, "dummy", &info) => 0; 40 | assert(strcmp(info.name, "dummy") == 0); 41 | assert(info.type == LFS2_TYPE_REG); 42 | lfs2_remove(&lfs2, "dummy") => 0; 43 | } 44 | lfs2_unmount(&lfs2) => 0; 45 | 46 | // one last check after power-cycle 47 | lfs2_mount(&lfs2, &cfg) => 0; 48 | lfs2_file_open(&lfs2, &file, "dummy", 49 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 50 | lfs2_file_close(&lfs2, &file) => 0; 51 | lfs2_stat(&lfs2, "dummy", &info) => 0; 52 | assert(strcmp(info.name, "dummy") == 0); 53 | assert(info.type == LFS2_TYPE_REG); 54 | lfs2_unmount(&lfs2) => 0; 55 | ''' 56 | 57 | [[case]] # expanding superblock with power cycle 58 | define.LFS2_BLOCK_CYCLES = [32, 33, 1] 59 | define.N = [10, 100, 1000] 60 | code = ''' 61 | lfs2_format(&lfs2, &cfg) => 0; 62 | for (int i = 0; i < N; i++) { 63 | lfs2_mount(&lfs2, &cfg) => 0; 64 | // remove lingering dummy? 65 | err = lfs2_stat(&lfs2, "dummy", &info); 66 | assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0)); 67 | if (!err) { 68 | assert(strcmp(info.name, "dummy") == 0); 69 | assert(info.type == LFS2_TYPE_REG); 70 | lfs2_remove(&lfs2, "dummy") => 0; 71 | } 72 | 73 | lfs2_file_open(&lfs2, &file, "dummy", 74 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 75 | lfs2_file_close(&lfs2, &file) => 0; 76 | lfs2_stat(&lfs2, "dummy", &info) => 0; 77 | assert(strcmp(info.name, "dummy") == 0); 78 | assert(info.type == LFS2_TYPE_REG); 79 | lfs2_unmount(&lfs2) => 0; 80 | } 81 | 82 | // one last check after power-cycle 83 | lfs2_mount(&lfs2, &cfg) => 0; 84 | lfs2_stat(&lfs2, "dummy", &info) => 0; 85 | assert(strcmp(info.name, "dummy") == 0); 86 | assert(info.type == LFS2_TYPE_REG); 87 | lfs2_unmount(&lfs2) => 0; 88 | ''' 89 | 90 | [[case]] # reentrant expanding superblock 91 | define.LFS2_BLOCK_CYCLES = [2, 1] 92 | define.N = 24 93 | reentrant = true 94 | code = ''' 95 | err = lfs2_mount(&lfs2, &cfg); 96 | if (err) { 97 | lfs2_format(&lfs2, &cfg) => 0; 98 | lfs2_mount(&lfs2, &cfg) => 0; 99 | } 100 | 101 | for (int i = 0; i < N; i++) { 102 | // remove lingering dummy? 103 | err = lfs2_stat(&lfs2, "dummy", &info); 104 | assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0)); 105 | if (!err) { 106 | assert(strcmp(info.name, "dummy") == 0); 107 | assert(info.type == LFS2_TYPE_REG); 108 | lfs2_remove(&lfs2, "dummy") => 0; 109 | } 110 | 111 | lfs2_file_open(&lfs2, &file, "dummy", 112 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 113 | lfs2_file_close(&lfs2, &file) => 0; 114 | lfs2_stat(&lfs2, "dummy", &info) => 0; 115 | assert(strcmp(info.name, "dummy") == 0); 116 | assert(info.type == LFS2_TYPE_REG); 117 | } 118 | 119 | lfs2_unmount(&lfs2) => 0; 120 | 121 | // one last check after power-cycle 122 | lfs2_mount(&lfs2, &cfg) => 0; 123 | lfs2_stat(&lfs2, "dummy", &info) => 0; 124 | assert(strcmp(info.name, "dummy") == 0); 125 | assert(info.type == LFS2_TYPE_REG); 126 | lfs2_unmount(&lfs2) => 0; 127 | ''' 128 | -------------------------------------------------------------------------------- /littlefs/bd/lfs2_testbd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Testing block device, wraps filebd and rambd while providing a bunch 3 | * of hooks for testing littlefs in various conditions. 4 | * 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS2_TESTBD_H 9 | #define LFS2_TESTBD_H 10 | 11 | #include "lfs2.h" 12 | #include "lfs2_util.h" 13 | #include "bd/lfs2_rambd.h" 14 | #include "bd/lfs2_filebd.h" 15 | 16 | #ifdef __cplusplus 17 | extern "C" 18 | { 19 | #endif 20 | 21 | 22 | // Block device specific tracing 23 | #ifdef LFS2_TESTBD_YES_TRACE 24 | #define LFS2_TESTBD_TRACE(...) LFS2_TRACE(__VA_ARGS__) 25 | #else 26 | #define LFS2_TESTBD_TRACE(...) 27 | #endif 28 | 29 | // Mode determining how "bad blocks" behave during testing. This simulates 30 | // some real-world circumstances such as progs not sticking (prog-noop), 31 | // a readonly disk (erase-noop), and ECC failures (read-error). 32 | // 33 | // Not that read-noop is not allowed. Read _must_ return a consistent (but 34 | // may be arbitrary) value on every read. 35 | enum lfs2_testbd_badblock_behavior { 36 | LFS2_TESTBD_BADBLOCK_PROGERROR, 37 | LFS2_TESTBD_BADBLOCK_ERASEERROR, 38 | LFS2_TESTBD_BADBLOCK_READERROR, 39 | LFS2_TESTBD_BADBLOCK_PROGNOOP, 40 | LFS2_TESTBD_BADBLOCK_ERASENOOP, 41 | }; 42 | 43 | // Type for measuring wear 44 | typedef uint32_t lfs2_testbd_wear_t; 45 | typedef int32_t lfs2_testbd_swear_t; 46 | 47 | // testbd config, this is required for testing 48 | struct lfs2_testbd_config { 49 | // 8-bit erase value to use for simulating erases. -1 does not simulate 50 | // erases, which can speed up testing by avoiding all the extra block-device 51 | // operations to store the erase value. 52 | int32_t erase_value; 53 | 54 | // Number of erase cycles before a block becomes "bad". The exact behavior 55 | // of bad blocks is controlled by the badblock_mode. 56 | uint32_t erase_cycles; 57 | 58 | // The mode determining how bad blocks fail 59 | uint8_t badblock_behavior; 60 | 61 | // Number of write operations (erase/prog) before forcefully killing 62 | // the program with exit. Simulates power-loss. 0 disables. 63 | uint32_t power_cycles; 64 | 65 | // Optional buffer for RAM block device. 66 | void *buffer; 67 | 68 | // Optional buffer for wear 69 | void *wear_buffer; 70 | }; 71 | 72 | // testbd state 73 | typedef struct lfs2_testbd { 74 | union { 75 | struct { 76 | lfs2_filebd_t bd; 77 | struct lfs2_filebd_config cfg; 78 | } file; 79 | struct { 80 | lfs2_rambd_t bd; 81 | struct lfs2_rambd_config cfg; 82 | } ram; 83 | } u; 84 | 85 | bool persist; 86 | uint32_t power_cycles; 87 | lfs2_testbd_wear_t *wear; 88 | 89 | const struct lfs2_testbd_config *cfg; 90 | } lfs2_testbd_t; 91 | 92 | 93 | /// Block device API /// 94 | 95 | // Create a test block device using the geometry in lfs2_config 96 | // 97 | // Note that filebd is used if a path is provided, if path is NULL 98 | // testbd will use rambd which can be much faster. 99 | int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path); 100 | int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path, 101 | const struct lfs2_testbd_config *bdcfg); 102 | 103 | // Clean up memory associated with block device 104 | int lfs2_testbd_destroy(const struct lfs2_config *cfg); 105 | 106 | // Read a block 107 | int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block, 108 | lfs2_off_t off, void *buffer, lfs2_size_t size); 109 | 110 | // Program a block 111 | // 112 | // The block must have previously been erased. 113 | int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 114 | lfs2_off_t off, const void *buffer, lfs2_size_t size); 115 | 116 | // Erase a block 117 | // 118 | // A block must be erased before being programmed. The 119 | // state of an erased block is undefined. 120 | int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block); 121 | 122 | // Sync the block device 123 | int lfs2_testbd_sync(const struct lfs2_config *cfg); 124 | 125 | 126 | /// Additional extended API for driving test features /// 127 | 128 | // Get simulated wear on a given block 129 | lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg, 130 | lfs2_block_t block); 131 | 132 | // Manually set simulated wear on a given block 133 | int lfs2_testbd_setwear(const struct lfs2_config *cfg, 134 | lfs2_block_t block, lfs2_testbd_wear_t wear); 135 | 136 | 137 | #ifdef __cplusplus 138 | } /* extern "C" */ 139 | #endif 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /littlefs/tests/test_orphans.toml: -------------------------------------------------------------------------------- 1 | [[case]] # orphan test 2 | in = "lfs2.c" 3 | if = 'LFS2_PROG_SIZE <= 0x3fe' # only works with one crc per commit 4 | code = ''' 5 | lfs2_format(&lfs2, &cfg) => 0; 6 | lfs2_mount(&lfs2, &cfg) => 0; 7 | lfs2_mkdir(&lfs2, "parent") => 0; 8 | lfs2_mkdir(&lfs2, "parent/orphan") => 0; 9 | lfs2_mkdir(&lfs2, "parent/child") => 0; 10 | lfs2_remove(&lfs2, "parent/orphan") => 0; 11 | lfs2_unmount(&lfs2) => 0; 12 | 13 | // corrupt the child's most recent commit, this should be the update 14 | // to the linked-list entry, which should orphan the orphan. Note this 15 | // makes a lot of assumptions about the remove operation. 16 | lfs2_mount(&lfs2, &cfg) => 0; 17 | lfs2_dir_open(&lfs2, &dir, "parent/child") => 0; 18 | lfs2_block_t block = dir.m.pair[0]; 19 | lfs2_dir_close(&lfs2, &dir) => 0; 20 | lfs2_unmount(&lfs2) => 0; 21 | uint8_t bbuffer[LFS2_BLOCK_SIZE]; 22 | cfg.read(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0; 23 | int off = LFS2_BLOCK_SIZE-1; 24 | while (off >= 0 && bbuffer[off] == LFS2_ERASE_VALUE) { 25 | off -= 1; 26 | } 27 | memset(&bbuffer[off-3], LFS2_BLOCK_SIZE, 3); 28 | cfg.erase(&cfg, block) => 0; 29 | cfg.prog(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0; 30 | cfg.sync(&cfg) => 0; 31 | 32 | lfs2_mount(&lfs2, &cfg) => 0; 33 | lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT; 34 | lfs2_stat(&lfs2, "parent/child", &info) => 0; 35 | lfs2_fs_size(&lfs2) => 8; 36 | lfs2_unmount(&lfs2) => 0; 37 | 38 | lfs2_mount(&lfs2, &cfg) => 0; 39 | lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT; 40 | lfs2_stat(&lfs2, "parent/child", &info) => 0; 41 | lfs2_fs_size(&lfs2) => 8; 42 | // this mkdir should both create a dir and deorphan, so size 43 | // should be unchanged 44 | lfs2_mkdir(&lfs2, "parent/otherchild") => 0; 45 | lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT; 46 | lfs2_stat(&lfs2, "parent/child", &info) => 0; 47 | lfs2_stat(&lfs2, "parent/otherchild", &info) => 0; 48 | lfs2_fs_size(&lfs2) => 8; 49 | lfs2_unmount(&lfs2) => 0; 50 | 51 | lfs2_mount(&lfs2, &cfg) => 0; 52 | lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT; 53 | lfs2_stat(&lfs2, "parent/child", &info) => 0; 54 | lfs2_stat(&lfs2, "parent/otherchild", &info) => 0; 55 | lfs2_fs_size(&lfs2) => 8; 56 | lfs2_unmount(&lfs2) => 0; 57 | ''' 58 | 59 | [[case]] # reentrant testing for orphans, basically just spam mkdir/remove 60 | reentrant = true 61 | # TODO fix this case, caused by non-DAG trees 62 | if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)' 63 | define = [ 64 | {FILES=6, DEPTH=1, CYCLES=20}, 65 | {FILES=26, DEPTH=1, CYCLES=20}, 66 | {FILES=3, DEPTH=3, CYCLES=20}, 67 | ] 68 | code = ''' 69 | err = lfs2_mount(&lfs2, &cfg); 70 | if (err) { 71 | lfs2_format(&lfs2, &cfg) => 0; 72 | lfs2_mount(&lfs2, &cfg) => 0; 73 | } 74 | 75 | srand(1); 76 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 77 | for (int i = 0; i < CYCLES; i++) { 78 | // create random path 79 | char full_path[256]; 80 | for (int d = 0; d < DEPTH; d++) { 81 | sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); 82 | } 83 | 84 | // if it does not exist, we create it, else we destroy 85 | int res = lfs2_stat(&lfs2, full_path, &info); 86 | if (res == LFS2_ERR_NOENT) { 87 | // create each directory in turn, ignore if dir already exists 88 | for (int d = 0; d < DEPTH; d++) { 89 | strcpy(path, full_path); 90 | path[2*d+2] = '\0'; 91 | err = lfs2_mkdir(&lfs2, path); 92 | assert(!err || err == LFS2_ERR_EXIST); 93 | } 94 | 95 | for (int d = 0; d < DEPTH; d++) { 96 | strcpy(path, full_path); 97 | path[2*d+2] = '\0'; 98 | lfs2_stat(&lfs2, path, &info) => 0; 99 | assert(strcmp(info.name, &path[2*d+1]) == 0); 100 | assert(info.type == LFS2_TYPE_DIR); 101 | } 102 | } else { 103 | // is valid dir? 104 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 105 | assert(info.type == LFS2_TYPE_DIR); 106 | 107 | // try to delete path in reverse order, ignore if dir is not empty 108 | for (int d = DEPTH-1; d >= 0; d--) { 109 | strcpy(path, full_path); 110 | path[2*d+2] = '\0'; 111 | err = lfs2_remove(&lfs2, path); 112 | assert(!err || err == LFS2_ERR_NOTEMPTY); 113 | } 114 | 115 | lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; 116 | } 117 | } 118 | lfs2_unmount(&lfs2) => 0; 119 | ''' 120 | 121 | -------------------------------------------------------------------------------- /TESTS/filesystem_integration/format/main.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "mbed.h" 17 | #include "greentea-client/test_env.h" 18 | #include "unity.h" 19 | #include "utest.h" 20 | #include 21 | #include 22 | 23 | using namespace utest::v1; 24 | 25 | // test configuration 26 | #ifndef MBED_TEST_FILESYSTEM 27 | #define MBED_TEST_FILESYSTEM LittleFileSystem2 28 | #endif 29 | 30 | #ifndef MBED_TEST_FILESYSTEM_DECL 31 | #define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") 32 | #endif 33 | 34 | #ifndef MBED_TEST_BLOCKDEVICE 35 | #error [NOT_SUPPORTED] Non-volatile block device required 36 | #endif 37 | 38 | #ifndef MBED_TEST_BLOCKDEVICE_DECL 39 | #define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd 40 | #endif 41 | 42 | #ifndef MBED_TEST_FILES 43 | #define MBED_TEST_FILES 4 44 | #endif 45 | 46 | #ifndef MBED_TEST_DIRS 47 | #define MBED_TEST_DIRS 4 48 | #endif 49 | 50 | #ifndef MBED_TEST_BUFFER 51 | #define MBED_TEST_BUFFER 8192 52 | #endif 53 | 54 | #ifndef MBED_TEST_TIMEOUT 55 | #define MBED_TEST_TIMEOUT 480 56 | #endif 57 | 58 | 59 | // declarations 60 | #define STRINGIZE(x) STRINGIZE2(x) 61 | #define STRINGIZE2(x) #x 62 | #define INCLUDE(x) STRINGIZE(x.h) 63 | 64 | #include INCLUDE(MBED_TEST_FILESYSTEM) 65 | #include INCLUDE(MBED_TEST_BLOCKDEVICE) 66 | 67 | MBED_TEST_FILESYSTEM_DECL; 68 | MBED_TEST_BLOCKDEVICE_DECL; 69 | 70 | Dir dir[MBED_TEST_DIRS]; 71 | File file[MBED_TEST_FILES]; 72 | DIR *dd[MBED_TEST_DIRS]; 73 | FILE *fd[MBED_TEST_FILES]; 74 | struct dirent ent; 75 | struct dirent *ed; 76 | size_t size; 77 | uint8_t buffer[MBED_TEST_BUFFER]; 78 | uint8_t rbuffer[MBED_TEST_BUFFER]; 79 | uint8_t wbuffer[MBED_TEST_BUFFER]; 80 | 81 | 82 | // tests for integration level format operations 83 | 84 | void test_format() 85 | { 86 | int res = bd.init(); 87 | TEST_ASSERT_EQUAL(0, res); 88 | 89 | { 90 | res = MBED_TEST_FILESYSTEM::format(&bd); 91 | TEST_ASSERT_EQUAL(0, res); 92 | } 93 | 94 | res = bd.deinit(); 95 | TEST_ASSERT_EQUAL(0, res); 96 | } 97 | 98 | void test_mount() 99 | { 100 | int res = bd.init(); 101 | TEST_ASSERT_EQUAL(0, res); 102 | 103 | { 104 | res = fs.mount(&bd); 105 | TEST_ASSERT_EQUAL(0, res); 106 | res = fs.unmount(); 107 | TEST_ASSERT_EQUAL(0, res); 108 | } 109 | 110 | res = bd.deinit(); 111 | TEST_ASSERT_EQUAL(0, res); 112 | } 113 | 114 | void test_bad_mount() 115 | { 116 | int res = bd.init(); 117 | TEST_ASSERT_EQUAL(0, res); 118 | 119 | { 120 | res = bd.erase(0, 2 * bd.get_erase_size()); 121 | TEST_ASSERT_EQUAL(0, res); 122 | memset(buffer, 0, bd.get_program_size()); 123 | for (int i = 0; i < 2 * bd.get_erase_size(); i += bd.get_program_size()) { 124 | res = bd.program(buffer, i, bd.get_program_size()); 125 | TEST_ASSERT_EQUAL(0, res); 126 | } 127 | } 128 | 129 | { 130 | res = fs.mount(&bd); 131 | TEST_ASSERT_NOT_EQUAL(0, res); 132 | } 133 | 134 | res = bd.deinit(); 135 | TEST_ASSERT_EQUAL(0, res); 136 | } 137 | 138 | void test_bad_mount_then_reformat() 139 | { 140 | int res = bd.init(); 141 | TEST_ASSERT_EQUAL(0, res); 142 | 143 | { 144 | res = fs.mount(&bd); 145 | TEST_ASSERT_NOT_EQUAL(0, res); 146 | 147 | res = fs.reformat(&bd); 148 | TEST_ASSERT_EQUAL(0, res); 149 | 150 | res = fs.unmount(); 151 | TEST_ASSERT_EQUAL(0, res); 152 | } 153 | 154 | res = bd.deinit(); 155 | TEST_ASSERT_EQUAL(0, res); 156 | } 157 | 158 | void test_good_mount_then_reformat() 159 | { 160 | int res = bd.init(); 161 | TEST_ASSERT_EQUAL(0, res); 162 | 163 | { 164 | res = fs.mount(&bd); 165 | TEST_ASSERT_EQUAL(0, res); 166 | 167 | res = fs.reformat(&bd); 168 | TEST_ASSERT_EQUAL(0, res); 169 | 170 | res = fs.unmount(); 171 | TEST_ASSERT_EQUAL(0, res); 172 | } 173 | 174 | res = bd.deinit(); 175 | TEST_ASSERT_EQUAL(0, res); 176 | } 177 | 178 | 179 | // test setup 180 | utest::v1::status_t test_setup(const size_t number_of_cases) 181 | { 182 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); 183 | return verbose_test_setup_handler(number_of_cases); 184 | } 185 | 186 | Case cases[] = { 187 | Case("Test format", test_format), 188 | Case("Test mount", test_mount), 189 | Case("Test bad mount", test_bad_mount), 190 | Case("Test bad mount than reformat", test_bad_mount_then_reformat), 191 | Case("Test good mount than reformat", test_good_mount_then_reformat), 192 | }; 193 | 194 | Specification specification(test_setup, cases); 195 | 196 | int main() 197 | { 198 | return !Harness::run(specification); 199 | } 200 | -------------------------------------------------------------------------------- /littlefs/bd/lfs2_rambd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Block device emulated in RAM 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include "bd/lfs2_rambd.h" 8 | 9 | int lfs2_rambd_createcfg(const struct lfs2_config *cfg, 10 | const struct lfs2_rambd_config *bdcfg) { 11 | LFS2_RAMBD_TRACE("lfs2_rambd_createcfg(%p {.context=%p, " 12 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 13 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 14 | ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " 15 | "%p {.erase_value=%"PRId32", .buffer=%p})", 16 | (void*)cfg, cfg->context, 17 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 18 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 19 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, 20 | (void*)bdcfg, bdcfg->erase_value, bdcfg->buffer); 21 | lfs2_rambd_t *bd = cfg->context; 22 | bd->cfg = bdcfg; 23 | 24 | // allocate buffer? 25 | if (bd->cfg->buffer) { 26 | bd->buffer = bd->cfg->buffer; 27 | } else { 28 | bd->buffer = lfs2_malloc(cfg->block_size * cfg->block_count); 29 | if (!bd->buffer) { 30 | LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", LFS2_ERR_NOMEM); 31 | return LFS2_ERR_NOMEM; 32 | } 33 | } 34 | 35 | // zero for reproducability? 36 | if (bd->cfg->erase_value != -1) { 37 | memset(bd->buffer, bd->cfg->erase_value, 38 | cfg->block_size * cfg->block_count); 39 | } 40 | 41 | LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", 0); 42 | return 0; 43 | } 44 | 45 | int lfs2_rambd_create(const struct lfs2_config *cfg) { 46 | LFS2_RAMBD_TRACE("lfs2_rambd_create(%p {.context=%p, " 47 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 48 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 49 | ".block_size=%"PRIu32", .block_count=%"PRIu32"})", 50 | (void*)cfg, cfg->context, 51 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 52 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 53 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count); 54 | static const struct lfs2_rambd_config defaults = {.erase_value=-1}; 55 | int err = lfs2_rambd_createcfg(cfg, &defaults); 56 | LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", err); 57 | return err; 58 | } 59 | 60 | int lfs2_rambd_destroy(const struct lfs2_config *cfg) { 61 | LFS2_RAMBD_TRACE("lfs2_rambd_destroy(%p)", (void*)cfg); 62 | // clean up memory 63 | lfs2_rambd_t *bd = cfg->context; 64 | if (!bd->cfg->buffer) { 65 | lfs2_free(bd->buffer); 66 | } 67 | LFS2_RAMBD_TRACE("lfs2_rambd_destroy -> %d", 0); 68 | return 0; 69 | } 70 | 71 | int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block, 72 | lfs2_off_t off, void *buffer, lfs2_size_t size) { 73 | LFS2_RAMBD_TRACE("lfs2_rambd_read(%p, " 74 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 75 | (void*)cfg, block, off, buffer, size); 76 | lfs2_rambd_t *bd = cfg->context; 77 | 78 | // check if read is valid 79 | LFS2_ASSERT(off % cfg->read_size == 0); 80 | LFS2_ASSERT(size % cfg->read_size == 0); 81 | LFS2_ASSERT(block < cfg->block_count); 82 | 83 | // read data 84 | memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size); 85 | 86 | LFS2_RAMBD_TRACE("lfs2_rambd_read -> %d", 0); 87 | return 0; 88 | } 89 | 90 | int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 91 | lfs2_off_t off, const void *buffer, lfs2_size_t size) { 92 | LFS2_RAMBD_TRACE("lfs2_rambd_prog(%p, " 93 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 94 | (void*)cfg, block, off, buffer, size); 95 | lfs2_rambd_t *bd = cfg->context; 96 | 97 | // check if write is valid 98 | LFS2_ASSERT(off % cfg->prog_size == 0); 99 | LFS2_ASSERT(size % cfg->prog_size == 0); 100 | LFS2_ASSERT(block < cfg->block_count); 101 | 102 | // check that data was erased? only needed for testing 103 | if (bd->cfg->erase_value != -1) { 104 | for (lfs2_off_t i = 0; i < size; i++) { 105 | LFS2_ASSERT(bd->buffer[block*cfg->block_size + off + i] == 106 | bd->cfg->erase_value); 107 | } 108 | } 109 | 110 | // program data 111 | memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size); 112 | 113 | LFS2_RAMBD_TRACE("lfs2_rambd_prog -> %d", 0); 114 | return 0; 115 | } 116 | 117 | int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { 118 | LFS2_RAMBD_TRACE("lfs2_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); 119 | lfs2_rambd_t *bd = cfg->context; 120 | 121 | // check if erase is valid 122 | LFS2_ASSERT(block < cfg->block_count); 123 | 124 | // erase, only needed for testing 125 | if (bd->cfg->erase_value != -1) { 126 | memset(&bd->buffer[block*cfg->block_size], 127 | bd->cfg->erase_value, cfg->block_size); 128 | } 129 | 130 | LFS2_RAMBD_TRACE("lfs2_rambd_erase -> %d", 0); 131 | return 0; 132 | } 133 | 134 | int lfs2_rambd_sync(const struct lfs2_config *cfg) { 135 | LFS2_RAMBD_TRACE("lfs2_rambd_sync(%p)", (void*)cfg); 136 | // sync does nothing because we aren't backed by anything real 137 | (void)cfg; 138 | LFS2_RAMBD_TRACE("lfs2_rambd_sync -> %d", 0); 139 | return 0; 140 | } 141 | -------------------------------------------------------------------------------- /littlefs/scripts/readtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import sys 5 | import json 6 | import io 7 | import itertools as it 8 | from readmdir import Tag, MetadataPair 9 | 10 | def main(args): 11 | superblock = None 12 | gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0' 13 | dirs = [] 14 | mdirs = [] 15 | corrupted = [] 16 | cycle = False 17 | with open(args.disk, 'rb') as f: 18 | tail = (args.block1, args.block2) 19 | hard = False 20 | while True: 21 | for m in it.chain((m for d in dirs for m in d), mdirs): 22 | if set(m.blocks) == set(tail): 23 | # cycle detected 24 | cycle = m.blocks 25 | if cycle: 26 | break 27 | 28 | # load mdir 29 | data = [] 30 | blocks = {} 31 | for block in tail: 32 | f.seek(block * args.block_size) 33 | data.append(f.read(args.block_size) 34 | .ljust(args.block_size, b'\xff')) 35 | blocks[id(data[-1])] = block 36 | 37 | mdir = MetadataPair(data) 38 | mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair) 39 | 40 | # fetch some key metadata as a we scan 41 | try: 42 | mdir.tail = mdir[Tag('tail', 0, 0)] 43 | if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff': 44 | mdir.tail = None 45 | except KeyError: 46 | mdir.tail = None 47 | 48 | # have superblock? 49 | try: 50 | nsuperblock = mdir[ 51 | Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)] 52 | superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)] 53 | except KeyError: 54 | pass 55 | 56 | # have gstate? 57 | try: 58 | ngstate = mdir[Tag('movestate', 0, 0)] 59 | gstate = bytes((a or 0) ^ (b or 0) 60 | for a,b in it.zip_longest(gstate, ngstate.data)) 61 | except KeyError: 62 | pass 63 | 64 | # corrupted? 65 | if not mdir: 66 | corrupted.append(mdir) 67 | 68 | # add to directories 69 | mdirs.append(mdir) 70 | if mdir.tail is None or not mdir.tail.is_('hardtail'): 71 | dirs.append(mdirs) 72 | mdirs = [] 73 | 74 | if mdir.tail is None: 75 | break 76 | 77 | tail = struct.unpack('=%d" % max(tag.size, 1)) 117 | if tag.type: 118 | print(" move dir {%#x, %#x} id %d" % ( 119 | blocks[0], blocks[1], tag.id)) 120 | 121 | # print mdir info 122 | for i, dir in enumerate(dirs): 123 | print("dir %s" % (json.dumps(dir[0].path) 124 | if hasattr(dir[0], 'path') else '(orphan)')) 125 | 126 | for j, mdir in enumerate(dir): 127 | print("mdir {%#x, %#x} rev %d (was %d)%s%s" % ( 128 | mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev, 129 | ' (corrupted!)' if not mdir else '', 130 | ' -> {%#x, %#x}' % struct.unpack(' 10 | #include 11 | #include 12 | 13 | int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path, 14 | const struct lfs2_filebd_config *bdcfg) { 15 | LFS2_FILEBD_TRACE("lfs2_filebd_createcfg(%p {.context=%p, " 16 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 17 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 18 | ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " 19 | "\"%s\", " 20 | "%p {.erase_value=%"PRId32"})", 21 | (void*)cfg, cfg->context, 22 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 23 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 24 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, 25 | path, (void*)bdcfg, bdcfg->erase_value); 26 | lfs2_filebd_t *bd = cfg->context; 27 | bd->cfg = bdcfg; 28 | 29 | // open file 30 | bd->fd = open(path, O_RDWR | O_CREAT, 0666); 31 | if (bd->fd < 0) { 32 | int err = -errno; 33 | LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", err); 34 | return err; 35 | } 36 | 37 | LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", 0); 38 | return 0; 39 | } 40 | 41 | int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path) { 42 | LFS2_FILEBD_TRACE("lfs2_filebd_create(%p {.context=%p, " 43 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 44 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 45 | ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " 46 | "\"%s\")", 47 | (void*)cfg, cfg->context, 48 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 49 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 50 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, 51 | path); 52 | static const struct lfs2_filebd_config defaults = {.erase_value=-1}; 53 | int err = lfs2_filebd_createcfg(cfg, path, &defaults); 54 | LFS2_FILEBD_TRACE("lfs2_filebd_create -> %d", err); 55 | return err; 56 | } 57 | 58 | int lfs2_filebd_destroy(const struct lfs2_config *cfg) { 59 | LFS2_FILEBD_TRACE("lfs2_filebd_destroy(%p)", (void*)cfg); 60 | lfs2_filebd_t *bd = cfg->context; 61 | int err = close(bd->fd); 62 | if (err < 0) { 63 | err = -errno; 64 | LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", err); 65 | return err; 66 | } 67 | LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", 0); 68 | return 0; 69 | } 70 | 71 | int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block, 72 | lfs2_off_t off, void *buffer, lfs2_size_t size) { 73 | LFS2_FILEBD_TRACE("lfs2_filebd_read(%p, " 74 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 75 | (void*)cfg, block, off, buffer, size); 76 | lfs2_filebd_t *bd = cfg->context; 77 | 78 | // check if read is valid 79 | LFS2_ASSERT(off % cfg->read_size == 0); 80 | LFS2_ASSERT(size % cfg->read_size == 0); 81 | LFS2_ASSERT(block < cfg->block_count); 82 | 83 | // zero for reproducability (in case file is truncated) 84 | if (bd->cfg->erase_value != -1) { 85 | memset(buffer, bd->cfg->erase_value, size); 86 | } 87 | 88 | // read 89 | off_t res1 = lseek(bd->fd, 90 | (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); 91 | if (res1 < 0) { 92 | int err = -errno; 93 | LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err); 94 | return err; 95 | } 96 | 97 | ssize_t res2 = read(bd->fd, buffer, size); 98 | if (res2 < 0) { 99 | int err = -errno; 100 | LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err); 101 | return err; 102 | } 103 | 104 | LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", 0); 105 | return 0; 106 | } 107 | 108 | int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 109 | lfs2_off_t off, const void *buffer, lfs2_size_t size) { 110 | LFS2_FILEBD_TRACE("lfs2_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 111 | (void*)cfg, block, off, buffer, size); 112 | lfs2_filebd_t *bd = cfg->context; 113 | 114 | // check if write is valid 115 | LFS2_ASSERT(off % cfg->prog_size == 0); 116 | LFS2_ASSERT(size % cfg->prog_size == 0); 117 | LFS2_ASSERT(block < cfg->block_count); 118 | 119 | // check that data was erased? only needed for testing 120 | if (bd->cfg->erase_value != -1) { 121 | off_t res1 = lseek(bd->fd, 122 | (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); 123 | if (res1 < 0) { 124 | int err = -errno; 125 | LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err); 126 | return err; 127 | } 128 | 129 | for (lfs2_off_t i = 0; i < size; i++) { 130 | uint8_t c; 131 | ssize_t res2 = read(bd->fd, &c, 1); 132 | if (res2 < 0) { 133 | int err = -errno; 134 | LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err); 135 | return err; 136 | } 137 | 138 | LFS2_ASSERT(c == bd->cfg->erase_value); 139 | } 140 | } 141 | 142 | // program data 143 | off_t res1 = lseek(bd->fd, 144 | (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); 145 | if (res1 < 0) { 146 | int err = -errno; 147 | LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err); 148 | return err; 149 | } 150 | 151 | ssize_t res2 = write(bd->fd, buffer, size); 152 | if (res2 < 0) { 153 | int err = -errno; 154 | LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err); 155 | return err; 156 | } 157 | 158 | LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", 0); 159 | return 0; 160 | } 161 | 162 | int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { 163 | LFS2_FILEBD_TRACE("lfs2_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); 164 | lfs2_filebd_t *bd = cfg->context; 165 | 166 | // check if erase is valid 167 | LFS2_ASSERT(block < cfg->block_count); 168 | 169 | // erase, only needed for testing 170 | if (bd->cfg->erase_value != -1) { 171 | off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET); 172 | if (res1 < 0) { 173 | int err = -errno; 174 | LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err); 175 | return err; 176 | } 177 | 178 | for (lfs2_off_t i = 0; i < cfg->block_size; i++) { 179 | ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1); 180 | if (res2 < 0) { 181 | int err = -errno; 182 | LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err); 183 | return err; 184 | } 185 | } 186 | } 187 | 188 | LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", 0); 189 | return 0; 190 | } 191 | 192 | int lfs2_filebd_sync(const struct lfs2_config *cfg) { 193 | LFS2_FILEBD_TRACE("lfs2_filebd_sync(%p)", (void*)cfg); 194 | // file sync 195 | lfs2_filebd_t *bd = cfg->context; 196 | int err = fsync(bd->fd); 197 | if (err) { 198 | err = -errno; 199 | LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0); 200 | return err; 201 | } 202 | 203 | LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0); 204 | return 0; 205 | } 206 | -------------------------------------------------------------------------------- /littlefs/tests/test_badblocks.toml: -------------------------------------------------------------------------------- 1 | # bad blocks with block cycles should be tested in test_relocations 2 | if = 'LFS2_BLOCK_CYCLES == -1' 3 | 4 | [[case]] # single bad blocks 5 | define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster 6 | define.LFS2_ERASE_CYCLES = 0xffffffff 7 | define.LFS2_ERASE_VALUE = [0x00, 0xff, -1] 8 | define.LFS2_BADBLOCK_BEHAVIOR = [ 9 | 'LFS2_TESTBD_BADBLOCK_PROGERROR', 10 | 'LFS2_TESTBD_BADBLOCK_ERASEERROR', 11 | 'LFS2_TESTBD_BADBLOCK_READERROR', 12 | 'LFS2_TESTBD_BADBLOCK_PROGNOOP', 13 | 'LFS2_TESTBD_BADBLOCK_ERASENOOP', 14 | ] 15 | define.NAMEMULT = 64 16 | define.FILEMULT = 1 17 | code = ''' 18 | for (lfs2_block_t badblock = 2; badblock < LFS2_BLOCK_COUNT; badblock++) { 19 | lfs2_testbd_setwear(&cfg, badblock-1, 0) => 0; 20 | lfs2_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; 21 | 22 | lfs2_format(&lfs2, &cfg) => 0; 23 | 24 | lfs2_mount(&lfs2, &cfg) => 0; 25 | for (int i = 1; i < 10; i++) { 26 | for (int j = 0; j < NAMEMULT; j++) { 27 | buffer[j] = '0'+i; 28 | } 29 | buffer[NAMEMULT] = '\0'; 30 | lfs2_mkdir(&lfs2, (char*)buffer) => 0; 31 | 32 | buffer[NAMEMULT] = '/'; 33 | for (int j = 0; j < NAMEMULT; j++) { 34 | buffer[j+NAMEMULT+1] = '0'+i; 35 | } 36 | buffer[2*NAMEMULT+1] = '\0'; 37 | lfs2_file_open(&lfs2, &file, (char*)buffer, 38 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 39 | 40 | size = NAMEMULT; 41 | for (int j = 0; j < i*FILEMULT; j++) { 42 | lfs2_file_write(&lfs2, &file, buffer, size) => size; 43 | } 44 | 45 | lfs2_file_close(&lfs2, &file) => 0; 46 | } 47 | lfs2_unmount(&lfs2) => 0; 48 | 49 | lfs2_mount(&lfs2, &cfg) => 0; 50 | for (int i = 1; i < 10; i++) { 51 | for (int j = 0; j < NAMEMULT; j++) { 52 | buffer[j] = '0'+i; 53 | } 54 | buffer[NAMEMULT] = '\0'; 55 | lfs2_stat(&lfs2, (char*)buffer, &info) => 0; 56 | info.type => LFS2_TYPE_DIR; 57 | 58 | buffer[NAMEMULT] = '/'; 59 | for (int j = 0; j < NAMEMULT; j++) { 60 | buffer[j+NAMEMULT+1] = '0'+i; 61 | } 62 | buffer[2*NAMEMULT+1] = '\0'; 63 | lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0; 64 | 65 | size = NAMEMULT; 66 | for (int j = 0; j < i*FILEMULT; j++) { 67 | uint8_t rbuffer[1024]; 68 | lfs2_file_read(&lfs2, &file, rbuffer, size) => size; 69 | memcmp(buffer, rbuffer, size) => 0; 70 | } 71 | 72 | lfs2_file_close(&lfs2, &file) => 0; 73 | } 74 | lfs2_unmount(&lfs2) => 0; 75 | } 76 | ''' 77 | 78 | [[case]] # region corruption (causes cascading failures) 79 | define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster 80 | define.LFS2_ERASE_CYCLES = 0xffffffff 81 | define.LFS2_ERASE_VALUE = [0x00, 0xff, -1] 82 | define.LFS2_BADBLOCK_BEHAVIOR = [ 83 | 'LFS2_TESTBD_BADBLOCK_PROGERROR', 84 | 'LFS2_TESTBD_BADBLOCK_ERASEERROR', 85 | 'LFS2_TESTBD_BADBLOCK_READERROR', 86 | 'LFS2_TESTBD_BADBLOCK_PROGNOOP', 87 | 'LFS2_TESTBD_BADBLOCK_ERASENOOP', 88 | ] 89 | define.NAMEMULT = 64 90 | define.FILEMULT = 1 91 | code = ''' 92 | for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) { 93 | lfs2_testbd_setwear(&cfg, i+2, 0xffffffff) => 0; 94 | } 95 | 96 | lfs2_format(&lfs2, &cfg) => 0; 97 | 98 | lfs2_mount(&lfs2, &cfg) => 0; 99 | for (int i = 1; i < 10; i++) { 100 | for (int j = 0; j < NAMEMULT; j++) { 101 | buffer[j] = '0'+i; 102 | } 103 | buffer[NAMEMULT] = '\0'; 104 | lfs2_mkdir(&lfs2, (char*)buffer) => 0; 105 | 106 | buffer[NAMEMULT] = '/'; 107 | for (int j = 0; j < NAMEMULT; j++) { 108 | buffer[j+NAMEMULT+1] = '0'+i; 109 | } 110 | buffer[2*NAMEMULT+1] = '\0'; 111 | lfs2_file_open(&lfs2, &file, (char*)buffer, 112 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 113 | 114 | size = NAMEMULT; 115 | for (int j = 0; j < i*FILEMULT; j++) { 116 | lfs2_file_write(&lfs2, &file, buffer, size) => size; 117 | } 118 | 119 | lfs2_file_close(&lfs2, &file) => 0; 120 | } 121 | lfs2_unmount(&lfs2) => 0; 122 | 123 | lfs2_mount(&lfs2, &cfg) => 0; 124 | for (int i = 1; i < 10; i++) { 125 | for (int j = 0; j < NAMEMULT; j++) { 126 | buffer[j] = '0'+i; 127 | } 128 | buffer[NAMEMULT] = '\0'; 129 | lfs2_stat(&lfs2, (char*)buffer, &info) => 0; 130 | info.type => LFS2_TYPE_DIR; 131 | 132 | buffer[NAMEMULT] = '/'; 133 | for (int j = 0; j < NAMEMULT; j++) { 134 | buffer[j+NAMEMULT+1] = '0'+i; 135 | } 136 | buffer[2*NAMEMULT+1] = '\0'; 137 | lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0; 138 | 139 | size = NAMEMULT; 140 | for (int j = 0; j < i*FILEMULT; j++) { 141 | uint8_t rbuffer[1024]; 142 | lfs2_file_read(&lfs2, &file, rbuffer, size) => size; 143 | memcmp(buffer, rbuffer, size) => 0; 144 | } 145 | 146 | lfs2_file_close(&lfs2, &file) => 0; 147 | } 148 | lfs2_unmount(&lfs2) => 0; 149 | ''' 150 | 151 | [[case]] # alternating corruption (causes cascading failures) 152 | define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster 153 | define.LFS2_ERASE_CYCLES = 0xffffffff 154 | define.LFS2_ERASE_VALUE = [0x00, 0xff, -1] 155 | define.LFS2_BADBLOCK_BEHAVIOR = [ 156 | 'LFS2_TESTBD_BADBLOCK_PROGERROR', 157 | 'LFS2_TESTBD_BADBLOCK_ERASEERROR', 158 | 'LFS2_TESTBD_BADBLOCK_READERROR', 159 | 'LFS2_TESTBD_BADBLOCK_PROGNOOP', 160 | 'LFS2_TESTBD_BADBLOCK_ERASENOOP', 161 | ] 162 | define.NAMEMULT = 64 163 | define.FILEMULT = 1 164 | code = ''' 165 | for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) { 166 | lfs2_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0; 167 | } 168 | 169 | lfs2_format(&lfs2, &cfg) => 0; 170 | 171 | lfs2_mount(&lfs2, &cfg) => 0; 172 | for (int i = 1; i < 10; i++) { 173 | for (int j = 0; j < NAMEMULT; j++) { 174 | buffer[j] = '0'+i; 175 | } 176 | buffer[NAMEMULT] = '\0'; 177 | lfs2_mkdir(&lfs2, (char*)buffer) => 0; 178 | 179 | buffer[NAMEMULT] = '/'; 180 | for (int j = 0; j < NAMEMULT; j++) { 181 | buffer[j+NAMEMULT+1] = '0'+i; 182 | } 183 | buffer[2*NAMEMULT+1] = '\0'; 184 | lfs2_file_open(&lfs2, &file, (char*)buffer, 185 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 186 | 187 | size = NAMEMULT; 188 | for (int j = 0; j < i*FILEMULT; j++) { 189 | lfs2_file_write(&lfs2, &file, buffer, size) => size; 190 | } 191 | 192 | lfs2_file_close(&lfs2, &file) => 0; 193 | } 194 | lfs2_unmount(&lfs2) => 0; 195 | 196 | lfs2_mount(&lfs2, &cfg) => 0; 197 | for (int i = 1; i < 10; i++) { 198 | for (int j = 0; j < NAMEMULT; j++) { 199 | buffer[j] = '0'+i; 200 | } 201 | buffer[NAMEMULT] = '\0'; 202 | lfs2_stat(&lfs2, (char*)buffer, &info) => 0; 203 | info.type => LFS2_TYPE_DIR; 204 | 205 | buffer[NAMEMULT] = '/'; 206 | for (int j = 0; j < NAMEMULT; j++) { 207 | buffer[j+NAMEMULT+1] = '0'+i; 208 | } 209 | buffer[2*NAMEMULT+1] = '\0'; 210 | lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0; 211 | 212 | size = NAMEMULT; 213 | for (int j = 0; j < i*FILEMULT; j++) { 214 | uint8_t rbuffer[1024]; 215 | lfs2_file_read(&lfs2, &file, rbuffer, size) => size; 216 | memcmp(buffer, rbuffer, size) => 0; 217 | } 218 | 219 | lfs2_file_close(&lfs2, &file) => 0; 220 | } 221 | lfs2_unmount(&lfs2) => 0; 222 | ''' 223 | 224 | # other corner cases 225 | [[case]] # bad superblocks (corrupt 1 or 0) 226 | define.LFS2_ERASE_CYCLES = 0xffffffff 227 | define.LFS2_ERASE_VALUE = [0x00, 0xff, -1] 228 | define.LFS2_BADBLOCK_BEHAVIOR = [ 229 | 'LFS2_TESTBD_BADBLOCK_PROGERROR', 230 | 'LFS2_TESTBD_BADBLOCK_ERASEERROR', 231 | 'LFS2_TESTBD_BADBLOCK_READERROR', 232 | 'LFS2_TESTBD_BADBLOCK_PROGNOOP', 233 | 'LFS2_TESTBD_BADBLOCK_ERASENOOP', 234 | ] 235 | code = ''' 236 | lfs2_testbd_setwear(&cfg, 0, 0xffffffff) => 0; 237 | lfs2_testbd_setwear(&cfg, 1, 0xffffffff) => 0; 238 | 239 | lfs2_format(&lfs2, &cfg) => LFS2_ERR_NOSPC; 240 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 241 | ''' 242 | -------------------------------------------------------------------------------- /littlefs/tests/test_interspersed.toml: -------------------------------------------------------------------------------- 1 | 2 | [[case]] # interspersed file test 3 | define.SIZE = [10, 100] 4 | define.FILES = [4, 10, 26] 5 | code = ''' 6 | lfs2_file_t files[FILES]; 7 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 8 | lfs2_format(&lfs2, &cfg) => 0; 9 | lfs2_mount(&lfs2, &cfg) => 0; 10 | for (int j = 0; j < FILES; j++) { 11 | sprintf(path, "%c", alphas[j]); 12 | lfs2_file_open(&lfs2, &files[j], path, 13 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 14 | } 15 | 16 | for (int i = 0; i < SIZE; i++) { 17 | for (int j = 0; j < FILES; j++) { 18 | lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1; 19 | } 20 | } 21 | 22 | for (int j = 0; j < FILES; j++) { 23 | lfs2_file_close(&lfs2, &files[j]); 24 | } 25 | 26 | lfs2_dir_open(&lfs2, &dir, "/") => 0; 27 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 28 | assert(strcmp(info.name, ".") == 0); 29 | assert(info.type == LFS2_TYPE_DIR); 30 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 31 | assert(strcmp(info.name, "..") == 0); 32 | assert(info.type == LFS2_TYPE_DIR); 33 | for (int j = 0; j < FILES; j++) { 34 | sprintf(path, "%c", alphas[j]); 35 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 36 | assert(strcmp(info.name, path) == 0); 37 | assert(info.type == LFS2_TYPE_REG); 38 | assert(info.size == SIZE); 39 | } 40 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 41 | lfs2_dir_close(&lfs2, &dir) => 0; 42 | 43 | for (int j = 0; j < FILES; j++) { 44 | sprintf(path, "%c", alphas[j]); 45 | lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0; 46 | } 47 | 48 | for (int i = 0; i < 10; i++) { 49 | for (int j = 0; j < FILES; j++) { 50 | lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1; 51 | assert(buffer[0] == alphas[j]); 52 | } 53 | } 54 | 55 | for (int j = 0; j < FILES; j++) { 56 | lfs2_file_close(&lfs2, &files[j]); 57 | } 58 | 59 | lfs2_unmount(&lfs2) => 0; 60 | ''' 61 | 62 | [[case]] # interspersed remove file test 63 | define.SIZE = [10, 100] 64 | define.FILES = [4, 10, 26] 65 | code = ''' 66 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 67 | lfs2_format(&lfs2, &cfg) => 0; 68 | lfs2_mount(&lfs2, &cfg) => 0; 69 | for (int j = 0; j < FILES; j++) { 70 | sprintf(path, "%c", alphas[j]); 71 | lfs2_file_open(&lfs2, &file, path, 72 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0; 73 | for (int i = 0; i < SIZE; i++) { 74 | lfs2_file_write(&lfs2, &file, &alphas[j], 1) => 1; 75 | } 76 | lfs2_file_close(&lfs2, &file); 77 | } 78 | lfs2_unmount(&lfs2) => 0; 79 | 80 | lfs2_mount(&lfs2, &cfg) => 0; 81 | lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 82 | for (int j = 0; j < FILES; j++) { 83 | lfs2_file_write(&lfs2, &file, (const void*)"~", 1) => 1; 84 | lfs2_file_sync(&lfs2, &file) => 0; 85 | 86 | sprintf(path, "%c", alphas[j]); 87 | lfs2_remove(&lfs2, path) => 0; 88 | } 89 | lfs2_file_close(&lfs2, &file); 90 | 91 | lfs2_dir_open(&lfs2, &dir, "/") => 0; 92 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 93 | assert(strcmp(info.name, ".") == 0); 94 | assert(info.type == LFS2_TYPE_DIR); 95 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 96 | assert(strcmp(info.name, "..") == 0); 97 | assert(info.type == LFS2_TYPE_DIR); 98 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 99 | assert(strcmp(info.name, "zzz") == 0); 100 | assert(info.type == LFS2_TYPE_REG); 101 | assert(info.size == FILES); 102 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 103 | lfs2_dir_close(&lfs2, &dir) => 0; 104 | 105 | lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_RDONLY) => 0; 106 | for (int i = 0; i < FILES; i++) { 107 | lfs2_file_read(&lfs2, &file, buffer, 1) => 1; 108 | assert(buffer[0] == '~'); 109 | } 110 | lfs2_file_close(&lfs2, &file); 111 | 112 | lfs2_unmount(&lfs2) => 0; 113 | ''' 114 | 115 | [[case]] # remove inconveniently test 116 | define.SIZE = [10, 100] 117 | code = ''' 118 | lfs2_format(&lfs2, &cfg) => 0; 119 | lfs2_mount(&lfs2, &cfg) => 0; 120 | lfs2_file_t files[3]; 121 | lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 122 | lfs2_file_open(&lfs2, &files[1], "f", LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 123 | lfs2_file_open(&lfs2, &files[2], "g", LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 124 | 125 | for (int i = 0; i < SIZE/2; i++) { 126 | lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1; 127 | lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1; 128 | lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1; 129 | } 130 | 131 | lfs2_remove(&lfs2, "f") => 0; 132 | 133 | for (int i = 0; i < SIZE/2; i++) { 134 | lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1; 135 | lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1; 136 | lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1; 137 | } 138 | 139 | lfs2_file_close(&lfs2, &files[0]); 140 | lfs2_file_close(&lfs2, &files[1]); 141 | lfs2_file_close(&lfs2, &files[2]); 142 | 143 | lfs2_dir_open(&lfs2, &dir, "/") => 0; 144 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 145 | assert(strcmp(info.name, ".") == 0); 146 | assert(info.type == LFS2_TYPE_DIR); 147 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 148 | assert(strcmp(info.name, "..") == 0); 149 | assert(info.type == LFS2_TYPE_DIR); 150 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 151 | assert(strcmp(info.name, "e") == 0); 152 | assert(info.type == LFS2_TYPE_REG); 153 | assert(info.size == SIZE); 154 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 155 | assert(strcmp(info.name, "g") == 0); 156 | assert(info.type == LFS2_TYPE_REG); 157 | assert(info.size == SIZE); 158 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 159 | lfs2_dir_close(&lfs2, &dir) => 0; 160 | 161 | lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_RDONLY) => 0; 162 | lfs2_file_open(&lfs2, &files[1], "g", LFS2_O_RDONLY) => 0; 163 | for (int i = 0; i < SIZE; i++) { 164 | lfs2_file_read(&lfs2, &files[0], buffer, 1) => 1; 165 | assert(buffer[0] == 'e'); 166 | lfs2_file_read(&lfs2, &files[1], buffer, 1) => 1; 167 | assert(buffer[0] == 'g'); 168 | } 169 | lfs2_file_close(&lfs2, &files[0]); 170 | lfs2_file_close(&lfs2, &files[1]); 171 | 172 | lfs2_unmount(&lfs2) => 0; 173 | ''' 174 | 175 | [[case]] # reentrant interspersed file test 176 | define.SIZE = [10, 100] 177 | define.FILES = [4, 10, 26] 178 | reentrant = true 179 | code = ''' 180 | lfs2_file_t files[FILES]; 181 | const char alphas[] = "abcdefghijklmnopqrstuvwxyz"; 182 | 183 | err = lfs2_mount(&lfs2, &cfg); 184 | if (err) { 185 | lfs2_format(&lfs2, &cfg) => 0; 186 | lfs2_mount(&lfs2, &cfg) => 0; 187 | } 188 | 189 | for (int j = 0; j < FILES; j++) { 190 | sprintf(path, "%c", alphas[j]); 191 | lfs2_file_open(&lfs2, &files[j], path, 192 | LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0; 193 | } 194 | 195 | for (int i = 0; i < SIZE; i++) { 196 | for (int j = 0; j < FILES; j++) { 197 | size = lfs2_file_size(&lfs2, &files[j]); 198 | assert((int)size >= 0); 199 | if ((int)size <= i) { 200 | lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1; 201 | lfs2_file_sync(&lfs2, &files[j]) => 0; 202 | } 203 | } 204 | } 205 | 206 | for (int j = 0; j < FILES; j++) { 207 | lfs2_file_close(&lfs2, &files[j]); 208 | } 209 | 210 | lfs2_dir_open(&lfs2, &dir, "/") => 0; 211 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 212 | assert(strcmp(info.name, ".") == 0); 213 | assert(info.type == LFS2_TYPE_DIR); 214 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 215 | assert(strcmp(info.name, "..") == 0); 216 | assert(info.type == LFS2_TYPE_DIR); 217 | for (int j = 0; j < FILES; j++) { 218 | sprintf(path, "%c", alphas[j]); 219 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 220 | assert(strcmp(info.name, path) == 0); 221 | assert(info.type == LFS2_TYPE_REG); 222 | assert(info.size == SIZE); 223 | } 224 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 225 | lfs2_dir_close(&lfs2, &dir) => 0; 226 | 227 | for (int j = 0; j < FILES; j++) { 228 | sprintf(path, "%c", alphas[j]); 229 | lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0; 230 | } 231 | 232 | for (int i = 0; i < 10; i++) { 233 | for (int j = 0; j < FILES; j++) { 234 | lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1; 235 | assert(buffer[0] == alphas[j]); 236 | } 237 | } 238 | 239 | for (int j = 0; j < FILES; j++) { 240 | lfs2_file_close(&lfs2, &files[j]); 241 | } 242 | 243 | lfs2_unmount(&lfs2) => 0; 244 | ''' 245 | -------------------------------------------------------------------------------- /littlefs/lfs2_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs2 utility functions 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS2_UTIL_H 8 | #define LFS2_UTIL_H 9 | 10 | // Users can override lfs2_util.h with their own configuration by defining 11 | // LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h). 12 | // 13 | // If LFS2_CONFIG is used, none of the default utils will be emitted and must be 14 | // provided by the config file. To start, I would suggest copying lfs2_util.h 15 | // and modifying as needed. 16 | #ifdef LFS2_CONFIG 17 | #define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x) 18 | #define LFS2_STRINGIZE2(x) #x 19 | #include LFS2_STRINGIZE(LFS2_CONFIG) 20 | #else 21 | 22 | // System includes 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifndef LFS2_NO_MALLOC 29 | #include 30 | #endif 31 | #ifndef LFS2_NO_ASSERT 32 | #include 33 | #endif 34 | #if !defined(LFS2_NO_DEBUG) || \ 35 | !defined(LFS2_NO_WARN) || \ 36 | !defined(LFS2_NO_ERROR) || \ 37 | defined(LFS2_YES_TRACE) 38 | #include 39 | #endif 40 | 41 | #ifdef __cplusplus 42 | extern "C" 43 | { 44 | #endif 45 | 46 | 47 | // Macros, may be replaced by system specific wrappers. Arguments to these 48 | // macros must not have side-effects as the macros can be removed for a smaller 49 | // code footprint 50 | 51 | #ifdef __MBED__ 52 | #include "mbed_debug.h" 53 | #include "mbed_assert.h" 54 | #include "cmsis_compiler.h" 55 | #else 56 | #define MBED_LFS2_ENABLE_INFO false 57 | #define MBED_LFS2_ENABLE_DEBUG true 58 | #define MBED_LFS2_ENABLE_WARN true 59 | #define MBED_LFS2_ENABLE_ERROR true 60 | #define MBED_LFS2_ENABLE_ASSERT true 61 | #define MBED_LFS2_INTRINSICS true 62 | #endif 63 | 64 | // Logging functions 65 | #if defined(LFS2_YES_TRACE) && MBED_LFS2_ENABLE_TRACE 66 | #define LFS2_TRACE_(fmt, ...) \ 67 | printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 68 | #define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "") 69 | #elif defined(LFS2_YES_TRACE) && !defined(MBED_LFS2_ENABLE_TRACE) 70 | #define LFS2_TRACE_(fmt, ...) \ 71 | debug("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 72 | #define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "") 73 | #else 74 | #define LFS2_TRACE(...) 75 | #endif 76 | 77 | #if !defined(LFS2_NO_DEBUG) && MBED_LFS2_ENABLE_DEBUG 78 | #define LFS2_DEBUG_(fmt, ...) \ 79 | printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 80 | #define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "") 81 | #elif !defined(LFS2_NO_DEBUG) && !defined(MBED_LFS2_ENABLE_DEBUG) 82 | #define LFS2_DEBUG_(fmt, ...) \ 83 | debug("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 84 | #define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "") 85 | #else 86 | #define LFS2_DEBUG(...) 87 | #endif 88 | 89 | #if !defined(LFS2_NO_WARN) && MBED_LFS2_ENABLE_WARN 90 | #define LFS2_WARN_(fmt, ...) \ 91 | printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 92 | #define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "") 93 | #elif !defined(LFS2_NO_WARN) && !defined(MBED_LFS2_ENABLE_WARN) 94 | #define LFS2_WARN_(fmt, ...) \ 95 | debug("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 96 | #define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "") 97 | #else 98 | #define LFS2_WARN(...) 99 | #endif 100 | 101 | #if !defined(LFS2_NO_ERROR) && MBED_LFS2_ENABLE_ERROR 102 | #define LFS2_ERROR_(fmt, ...) \ 103 | printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 104 | #define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "") 105 | #elif !defined(LFS2_NO_ERROR) && !defined(MBED_LFS2_ENABLE_ERROR) 106 | #define LFS2_ERROR_(fmt, ...) \ 107 | debug("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 108 | #define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "") 109 | #else 110 | #define LFS2_ERROR(...) 111 | #endif 112 | 113 | // Runtime assertions 114 | #if !defined(LFS2_NO_ASSERT) && MBED_LFS2_ENABLE_ASSERT 115 | #define LFS2_ASSERT(test) assert(test) 116 | #elif !defined(LFS2_NO_ASSERT) && !defined(MBED_LFS2_ENABLE_ASSERT) 117 | #define LFS2_ASSERT(test) MBED_ASSERT(test) 118 | #else 119 | #define LFS2_ASSERT(test) 120 | #endif 121 | 122 | 123 | // Builtin functions, these may be replaced by more efficient 124 | // toolchain-specific implementations. LFS2_NO_INTRINSICS falls back to a more 125 | // expensive basic C implementation for debugging purposes 126 | 127 | // Min/max functions for unsigned 32-bit numbers 128 | static inline uint32_t lfs2_max(uint32_t a, uint32_t b) { 129 | return (a > b) ? a : b; 130 | } 131 | 132 | static inline uint32_t lfs2_min(uint32_t a, uint32_t b) { 133 | return (a < b) ? a : b; 134 | } 135 | 136 | // Align to nearest multiple of a size 137 | static inline uint32_t lfs2_aligndown(uint32_t a, uint32_t alignment) { 138 | return a - (a % alignment); 139 | } 140 | 141 | static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) { 142 | return lfs2_aligndown(a + alignment-1, alignment); 143 | } 144 | 145 | // Find the smallest power of 2 greater than or equal to a 146 | static inline uint32_t lfs2_npw2(uint32_t a) { 147 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \ 148 | (defined(__GNUC__) || defined(__CC_ARM)) 149 | return 32 - __builtin_clz(a-1); 150 | #else 151 | uint32_t r = 0; 152 | uint32_t s; 153 | a -= 1; 154 | s = (a > 0xffff) << 4; a >>= s; r |= s; 155 | s = (a > 0xff ) << 3; a >>= s; r |= s; 156 | s = (a > 0xf ) << 2; a >>= s; r |= s; 157 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 158 | return (r | (a >> 1)) + 1; 159 | #endif 160 | } 161 | 162 | // Count the number of trailing binary zeros in a 163 | // lfs2_ctz(0) may be undefined 164 | static inline uint32_t lfs2_ctz(uint32_t a) { 165 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \ 166 | defined(__GNUC__) 167 | return __builtin_ctz(a); 168 | #else 169 | return lfs2_npw2((a & -a) + 1) - 1; 170 | #endif 171 | } 172 | 173 | // Count the number of binary ones in a 174 | static inline uint32_t lfs2_popc(uint32_t a) { 175 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \ 176 | (defined(__GNUC__) || defined(__CC_ARM)) 177 | return __builtin_popcount(a); 178 | #else 179 | a = a - ((a >> 1) & 0x55555555); 180 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 181 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 182 | #endif 183 | } 184 | 185 | // Find the sequence comparison of a and b, this is the distance 186 | // between a and b ignoring overflow 187 | static inline int lfs2_scmp(uint32_t a, uint32_t b) { 188 | return (int)(unsigned)(a - b); 189 | } 190 | 191 | // Convert between 32-bit little-endian and native order 192 | static inline uint32_t lfs2_fromle32(uint32_t a) { 193 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \ 194 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 195 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 196 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 197 | return a; 198 | #elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \ 199 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 200 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 201 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 202 | return __builtin_bswap32(a); 203 | #else 204 | return (((uint8_t*)&a)[0] << 0) | 205 | (((uint8_t*)&a)[1] << 8) | 206 | (((uint8_t*)&a)[2] << 16) | 207 | (((uint8_t*)&a)[3] << 24); 208 | #endif 209 | } 210 | 211 | static inline uint32_t lfs2_tole32(uint32_t a) { 212 | return lfs2_fromle32(a); 213 | } 214 | 215 | // Reverse the bits in a 216 | static inline uint32_t lfs2_rbit(uint32_t a) { 217 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \ 218 | defined(__MBED__) 219 | return __RBIT(a); 220 | #else 221 | a = ((a & 0xaaaaaaaa) >> 1) | ((a & 0x55555555) << 1); 222 | a = ((a & 0xcccccccc) >> 2) | ((a & 0x33333333) << 2); 223 | a = ((a & 0xf0f0f0f0) >> 4) | ((a & 0x0f0f0f0f) << 4); 224 | a = ((a & 0xff00ff00) >> 8) | ((a & 0x00ff00ff) << 8); 225 | a = (a >> 16) | (a << 16); 226 | return a; 227 | #endif 228 | } 229 | 230 | // Convert between 32-bit big-endian and native order 231 | static inline uint32_t lfs2_frombe32(uint32_t a) { 232 | #if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \ 233 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 234 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 235 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 236 | return __builtin_bswap32(a); 237 | #elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \ 238 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 239 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 240 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 241 | return a; 242 | #else 243 | return (((uint8_t*)&a)[0] << 24) | 244 | (((uint8_t*)&a)[1] << 16) | 245 | (((uint8_t*)&a)[2] << 8) | 246 | (((uint8_t*)&a)[3] << 0); 247 | #endif 248 | } 249 | 250 | static inline uint32_t lfs2_tobe32(uint32_t a) { 251 | return lfs2_frombe32(a); 252 | } 253 | 254 | // Calculate CRC-32 with polynomial = 0x04c11db7 255 | uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size); 256 | 257 | // Allocate memory, only used if buffers are not provided to littlefs 258 | // Note, memory must be 64-bit aligned 259 | static inline void *lfs2_malloc(size_t size) { 260 | #ifndef LFS2_NO_MALLOC 261 | return malloc(size); 262 | #else 263 | (void)size; 264 | return NULL; 265 | #endif 266 | } 267 | 268 | // Deallocate memory, only used if buffers are not provided to littlefs 269 | static inline void lfs2_free(void *p) { 270 | #ifndef LFS2_NO_MALLOC 271 | free(p); 272 | #else 273 | (void)p; 274 | #endif 275 | } 276 | 277 | 278 | #ifdef __cplusplus 279 | } /* extern "C" */ 280 | #endif 281 | 282 | #endif 283 | #endif 284 | -------------------------------------------------------------------------------- /littlefs/README.md: -------------------------------------------------------------------------------- 1 | ## littlefs 2 | 3 | A little fail-safe filesystem designed for microcontrollers. 4 | 5 | ``` 6 | | | | .---._____ 7 | .-----. | | 8 | --|o |---| littlefs | 9 | --| |---| | 10 | '-----' '----------' 11 | | | | 12 | ``` 13 | 14 | **Power-loss resilience** - littlefs is designed to handle random power 15 | failures. All file operations have strong copy-on-write guarantees and if 16 | power is lost the filesystem will fall back to the last known good state. 17 | 18 | **Dynamic wear leveling** - littlefs is designed with flash in mind, and 19 | provides wear leveling over dynamic blocks. Additionally, littlefs can 20 | detect bad blocks and work around them. 21 | 22 | **Bounded RAM/ROM** - littlefs is designed to work with a small amount of 23 | memory. RAM usage is strictly bounded, which means RAM consumption does not 24 | change as the filesystem grows. The filesystem contains no unbounded 25 | recursion and dynamic memory is limited to configurable buffers that can be 26 | provided statically. 27 | 28 | ## Example 29 | 30 | Here's a simple example that updates a file named `boot_count` every time 31 | main runs. The program can be interrupted at any time without losing track 32 | of how many times it has been booted and without corrupting the filesystem: 33 | 34 | ``` c 35 | #include "lfs2.h" 36 | 37 | // variables used by the filesystem 38 | lfs2_t lfs2; 39 | lfs2_file_t file; 40 | 41 | // configuration of the filesystem is provided by this struct 42 | const struct lfs2_config cfg = { 43 | // block device operations 44 | .read = user_provided_block_device_read, 45 | .prog = user_provided_block_device_prog, 46 | .erase = user_provided_block_device_erase, 47 | .sync = user_provided_block_device_sync, 48 | 49 | // block device configuration 50 | .read_size = 16, 51 | .prog_size = 16, 52 | .block_size = 4096, 53 | .block_count = 128, 54 | .cache_size = 16, 55 | .lookahead_size = 16, 56 | .block_cycles = 500, 57 | }; 58 | 59 | // entry point 60 | int main(void) { 61 | // mount the filesystem 62 | int err = lfs2_mount(&lfs2, &cfg); 63 | 64 | // reformat if we can't mount the filesystem 65 | // this should only happen on the first boot 66 | if (err) { 67 | lfs2_format(&lfs2, &cfg); 68 | lfs2_mount(&lfs2, &cfg); 69 | } 70 | 71 | // read current count 72 | uint32_t boot_count = 0; 73 | lfs2_file_open(&lfs2, &file, "boot_count", LFS2_O_RDWR | LFS2_O_CREAT); 74 | lfs2_file_read(&lfs2, &file, &boot_count, sizeof(boot_count)); 75 | 76 | // update boot count 77 | boot_count += 1; 78 | lfs2_file_rewind(&lfs2, &file); 79 | lfs2_file_write(&lfs2, &file, &boot_count, sizeof(boot_count)); 80 | 81 | // remember the storage is not updated until the file is closed successfully 82 | lfs2_file_close(&lfs2, &file); 83 | 84 | // release any resources we were using 85 | lfs2_unmount(&lfs2); 86 | 87 | // print the boot count 88 | printf("boot_count: %d\n", boot_count); 89 | } 90 | ``` 91 | 92 | ## Usage 93 | 94 | Detailed documentation (or at least as much detail as is currently available) 95 | can be found in the comments in [lfs2.h](lfs2.h). 96 | 97 | littlefs takes in a configuration structure that defines how the filesystem 98 | operates. The configuration struct provides the filesystem with the block 99 | device operations and dimensions, tweakable parameters that tradeoff memory 100 | usage for performance, and optional static buffers if the user wants to avoid 101 | dynamic memory. 102 | 103 | The state of the littlefs is stored in the `lfs2_t` type which is left up 104 | to the user to allocate, allowing multiple filesystems to be in use 105 | simultaneously. With the `lfs2_t` and configuration struct, a user can 106 | format a block device or mount the filesystem. 107 | 108 | Once mounted, the littlefs provides a full set of POSIX-like file and 109 | directory functions, with the deviation that the allocation of filesystem 110 | structures must be provided by the user. 111 | 112 | All POSIX operations, such as remove and rename, are atomic, even in event 113 | of power-loss. Additionally, file updates are not actually committed to 114 | the filesystem until sync or close is called on the file. 115 | 116 | ## Other notes 117 | 118 | Littlefs is written in C, and specifically should compile with any compiler 119 | that conforms to the `C99` standard. 120 | 121 | All littlefs calls have the potential to return a negative error code. The 122 | errors can be either one of those found in the `enum lfs2_error` in 123 | [lfs2.h](lfs2.h), or an error returned by the user's block device operations. 124 | 125 | In the configuration struct, the `prog` and `erase` function provided by the 126 | user may return a `LFS2_ERR_CORRUPT` error if the implementation already can 127 | detect corrupt blocks. However, the wear leveling does not depend on the return 128 | code of these functions, instead all data is read back and checked for 129 | integrity. 130 | 131 | If your storage caches writes, make sure that the provided `sync` function 132 | flushes all the data to memory and ensures that the next read fetches the data 133 | from memory, otherwise data integrity can not be guaranteed. If the `write` 134 | function does not perform caching, and therefore each `read` or `write` call 135 | hits the memory, the `sync` function can simply return 0. 136 | 137 | ## Design 138 | 139 | At a high level, littlefs is a block based filesystem that uses small logs to 140 | store metadata and larger copy-on-write (COW) structures to store file data. 141 | 142 | In littlefs, these ingredients form a sort of two-layered cake, with the small 143 | logs (called metadata pairs) providing fast updates to metadata anywhere on 144 | storage, while the COW structures store file data compactly and without any 145 | wear amplification cost. 146 | 147 | Both of these data structures are built out of blocks, which are fed by a 148 | common block allocator. By limiting the number of erases allowed on a block 149 | per allocation, the allocator provides dynamic wear leveling over the entire 150 | filesystem. 151 | 152 | ``` 153 | root 154 | .--------.--------. 155 | | A'| B'| | 156 | | | |-> | 157 | | | | | 158 | '--------'--------' 159 | .----' '--------------. 160 | A v B v 161 | .--------.--------. .--------.--------. 162 | | C'| D'| | | E'|new| | 163 | | | |-> | | | E'|-> | 164 | | | | | | | | | 165 | '--------'--------' '--------'--------' 166 | .-' '--. | '------------------. 167 | v v .-' v 168 | .--------. .--------. v .--------. 169 | | C | | D | .--------. write | new E | 170 | | | | | | E | ==> | | 171 | | | | | | | | | 172 | '--------' '--------' | | '--------' 173 | '--------' .-' | 174 | .-' '-. .-------------|------' 175 | v v v v 176 | .--------. .--------. .--------. 177 | | F | | G | | new F | 178 | | | | | | | 179 | | | | | | | 180 | '--------' '--------' '--------' 181 | ``` 182 | 183 | More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and 184 | [SPEC.md](SPEC.md). 185 | 186 | - [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works. 187 | I would suggest reading it as the tradeoffs at work are quite interesting. 188 | 189 | - [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the 190 | nitty-gritty details. May be useful for tooling development. 191 | 192 | ## Testing 193 | 194 | The littlefs comes with a test suite designed to run on a PC using the 195 | [emulated block device](emubd/lfs2_emubd.h) found in the emubd directory. 196 | The tests assume a Linux environment and can be started with make: 197 | 198 | ``` bash 199 | make test 200 | ``` 201 | 202 | ## License 203 | 204 | The littlefs is provided under the [BSD-3-Clause] license. See 205 | [LICENSE.md](LICENSE.md) for more information. Contributions to this project 206 | are accepted under the same license. 207 | 208 | Individual files contain the following tag instead of the full license text. 209 | 210 | SPDX-License-Identifier: BSD-3-Clause 211 | 212 | This enables machine processing of license information based on the SPDX 213 | License Identifiers that are here available: http://spdx.org/licenses/ 214 | 215 | ## Related projects 216 | 217 | - [littlefs-fuse] - A [FUSE] wrapper for littlefs. The project allows you to 218 | mount littlefs directly on a Linux machine. Can be useful for debugging 219 | littlefs if you have an SD card handy. 220 | 221 | - [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would 222 | want this, but it is handy for demos. You can see it in action 223 | [here][littlefs-js-demo]. 224 | 225 | - [mklfs] - A command line tool built by the [Lua RTOS] guys for making 226 | littlefs images from a host PC. Supports Windows, Mac OS, and Linux. 227 | 228 | - [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed 229 | which already has block device drivers for most forms of embedded storage. 230 | littlefs is available in Mbed OS as the [LittleFileSystem] class. 231 | 232 | - [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more 233 | traditional logging filesystem with full static wear-leveling, SPIFFS will 234 | likely outperform littlefs on small memories such as the internal flash on 235 | microcontrollers. 236 | 237 | - [Dhara] - An interesting NAND flash translation layer designed for small 238 | MCUs. It offers static wear-leveling and power-resilience with only a fixed 239 | _O(|address|)_ pointer structure stored on each block and in RAM. 240 | 241 | 242 | [BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html 243 | [littlefs-fuse]: https://github.com/geky/littlefs-fuse 244 | [FUSE]: https://github.com/libfuse/libfuse 245 | [littlefs-js]: https://github.com/geky/littlefs-js 246 | [littlefs-js-demo]:http://littlefs.geky.net/demo.html 247 | [mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src 248 | [Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32 249 | [Mbed OS]: https://github.com/armmbed/mbed-os 250 | [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html 251 | [SPIFFS]: https://github.com/pellepl/spiffs 252 | [Dhara]: https://github.com/dlbeer/dhara 253 | -------------------------------------------------------------------------------- /littlefs/tests/test_paths.toml: -------------------------------------------------------------------------------- 1 | 2 | [[case]] # simple path test 3 | code = ''' 4 | lfs2_format(&lfs2, &cfg) => 0; 5 | lfs2_mount(&lfs2, &cfg) => 0; 6 | lfs2_mkdir(&lfs2, "tea") => 0; 7 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 8 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 9 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 10 | 11 | lfs2_stat(&lfs2, "tea/hottea", &info) => 0; 12 | assert(strcmp(info.name, "hottea") == 0); 13 | lfs2_stat(&lfs2, "/tea/hottea", &info) => 0; 14 | assert(strcmp(info.name, "hottea") == 0); 15 | 16 | lfs2_mkdir(&lfs2, "/milk") => 0; 17 | lfs2_stat(&lfs2, "/milk", &info) => 0; 18 | assert(strcmp(info.name, "milk") == 0); 19 | lfs2_stat(&lfs2, "milk", &info) => 0; 20 | assert(strcmp(info.name, "milk") == 0); 21 | lfs2_unmount(&lfs2) => 0; 22 | ''' 23 | 24 | [[case]] # redundant slashes 25 | code = ''' 26 | lfs2_format(&lfs2, &cfg) => 0; 27 | lfs2_mount(&lfs2, &cfg) => 0; 28 | lfs2_mkdir(&lfs2, "tea") => 0; 29 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 30 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 31 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 32 | 33 | lfs2_stat(&lfs2, "/tea/hottea", &info) => 0; 34 | assert(strcmp(info.name, "hottea") == 0); 35 | lfs2_stat(&lfs2, "//tea//hottea", &info) => 0; 36 | assert(strcmp(info.name, "hottea") == 0); 37 | lfs2_stat(&lfs2, "///tea///hottea", &info) => 0; 38 | assert(strcmp(info.name, "hottea") == 0); 39 | 40 | lfs2_mkdir(&lfs2, "////milk") => 0; 41 | lfs2_stat(&lfs2, "////milk", &info) => 0; 42 | assert(strcmp(info.name, "milk") == 0); 43 | lfs2_stat(&lfs2, "milk", &info) => 0; 44 | assert(strcmp(info.name, "milk") == 0); 45 | lfs2_unmount(&lfs2) => 0; 46 | ''' 47 | 48 | [[case]] # dot path test 49 | code = ''' 50 | lfs2_format(&lfs2, &cfg) => 0; 51 | lfs2_mount(&lfs2, &cfg) => 0; 52 | lfs2_mkdir(&lfs2, "tea") => 0; 53 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 54 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 55 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 56 | 57 | lfs2_stat(&lfs2, "./tea/hottea", &info) => 0; 58 | assert(strcmp(info.name, "hottea") == 0); 59 | lfs2_stat(&lfs2, "/./tea/hottea", &info) => 0; 60 | assert(strcmp(info.name, "hottea") == 0); 61 | lfs2_stat(&lfs2, "/././tea/hottea", &info) => 0; 62 | assert(strcmp(info.name, "hottea") == 0); 63 | lfs2_stat(&lfs2, "/./tea/./hottea", &info) => 0; 64 | assert(strcmp(info.name, "hottea") == 0); 65 | 66 | lfs2_mkdir(&lfs2, "/./milk") => 0; 67 | lfs2_stat(&lfs2, "/./milk", &info) => 0; 68 | assert(strcmp(info.name, "milk") == 0); 69 | lfs2_stat(&lfs2, "milk", &info) => 0; 70 | assert(strcmp(info.name, "milk") == 0); 71 | lfs2_unmount(&lfs2) => 0; 72 | ''' 73 | 74 | [[case]] # dot dot path test 75 | code = ''' 76 | lfs2_format(&lfs2, &cfg) => 0; 77 | lfs2_mount(&lfs2, &cfg) => 0; 78 | lfs2_mkdir(&lfs2, "tea") => 0; 79 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 80 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 81 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 82 | lfs2_mkdir(&lfs2, "coffee") => 0; 83 | lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0; 84 | lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0; 85 | lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0; 86 | 87 | lfs2_stat(&lfs2, "coffee/../tea/hottea", &info) => 0; 88 | assert(strcmp(info.name, "hottea") == 0); 89 | lfs2_stat(&lfs2, "tea/coldtea/../hottea", &info) => 0; 90 | assert(strcmp(info.name, "hottea") == 0); 91 | lfs2_stat(&lfs2, "coffee/coldcoffee/../../tea/hottea", &info) => 0; 92 | assert(strcmp(info.name, "hottea") == 0); 93 | lfs2_stat(&lfs2, "coffee/../coffee/../tea/hottea", &info) => 0; 94 | assert(strcmp(info.name, "hottea") == 0); 95 | 96 | lfs2_mkdir(&lfs2, "coffee/../milk") => 0; 97 | lfs2_stat(&lfs2, "coffee/../milk", &info) => 0; 98 | strcmp(info.name, "milk") => 0; 99 | lfs2_stat(&lfs2, "milk", &info) => 0; 100 | strcmp(info.name, "milk") => 0; 101 | lfs2_unmount(&lfs2) => 0; 102 | ''' 103 | 104 | [[case]] # trailing dot path test 105 | code = ''' 106 | lfs2_format(&lfs2, &cfg) => 0; 107 | lfs2_mount(&lfs2, &cfg) => 0; 108 | lfs2_mkdir(&lfs2, "tea") => 0; 109 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 110 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 111 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 112 | 113 | lfs2_stat(&lfs2, "tea/hottea/", &info) => 0; 114 | assert(strcmp(info.name, "hottea") == 0); 115 | lfs2_stat(&lfs2, "tea/hottea/.", &info) => 0; 116 | assert(strcmp(info.name, "hottea") == 0); 117 | lfs2_stat(&lfs2, "tea/hottea/./.", &info) => 0; 118 | assert(strcmp(info.name, "hottea") == 0); 119 | lfs2_stat(&lfs2, "tea/hottea/..", &info) => 0; 120 | assert(strcmp(info.name, "tea") == 0); 121 | lfs2_stat(&lfs2, "tea/hottea/../.", &info) => 0; 122 | assert(strcmp(info.name, "tea") == 0); 123 | lfs2_unmount(&lfs2) => 0; 124 | ''' 125 | 126 | [[case]] # leading dot path test 127 | code = ''' 128 | lfs2_format(&lfs2, &cfg) => 0; 129 | lfs2_mount(&lfs2, &cfg) => 0; 130 | lfs2_mkdir(&lfs2, ".milk") => 0; 131 | lfs2_stat(&lfs2, ".milk", &info) => 0; 132 | strcmp(info.name, ".milk") => 0; 133 | lfs2_stat(&lfs2, "tea/.././.milk", &info) => 0; 134 | strcmp(info.name, ".milk") => 0; 135 | lfs2_unmount(&lfs2) => 0; 136 | ''' 137 | 138 | [[case]] # root dot dot path test 139 | code = ''' 140 | lfs2_format(&lfs2, &cfg) => 0; 141 | lfs2_mount(&lfs2, &cfg) => 0; 142 | lfs2_mkdir(&lfs2, "tea") => 0; 143 | lfs2_mkdir(&lfs2, "tea/hottea") => 0; 144 | lfs2_mkdir(&lfs2, "tea/warmtea") => 0; 145 | lfs2_mkdir(&lfs2, "tea/coldtea") => 0; 146 | lfs2_mkdir(&lfs2, "coffee") => 0; 147 | lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0; 148 | lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0; 149 | lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0; 150 | 151 | lfs2_stat(&lfs2, "coffee/../../../../../../tea/hottea", &info) => 0; 152 | strcmp(info.name, "hottea") => 0; 153 | 154 | lfs2_mkdir(&lfs2, "coffee/../../../../../../milk") => 0; 155 | lfs2_stat(&lfs2, "coffee/../../../../../../milk", &info) => 0; 156 | strcmp(info.name, "milk") => 0; 157 | lfs2_stat(&lfs2, "milk", &info) => 0; 158 | strcmp(info.name, "milk") => 0; 159 | lfs2_unmount(&lfs2) => 0; 160 | ''' 161 | 162 | [[case]] # invalid path tests 163 | code = ''' 164 | lfs2_format(&lfs2, &cfg); 165 | lfs2_mount(&lfs2, &cfg) => 0; 166 | lfs2_stat(&lfs2, "dirt", &info) => LFS2_ERR_NOENT; 167 | lfs2_stat(&lfs2, "dirt/ground", &info) => LFS2_ERR_NOENT; 168 | lfs2_stat(&lfs2, "dirt/ground/earth", &info) => LFS2_ERR_NOENT; 169 | 170 | lfs2_remove(&lfs2, "dirt") => LFS2_ERR_NOENT; 171 | lfs2_remove(&lfs2, "dirt/ground") => LFS2_ERR_NOENT; 172 | lfs2_remove(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT; 173 | 174 | lfs2_mkdir(&lfs2, "dirt/ground") => LFS2_ERR_NOENT; 175 | lfs2_file_open(&lfs2, &file, "dirt/ground", LFS2_O_WRONLY | LFS2_O_CREAT) 176 | => LFS2_ERR_NOENT; 177 | lfs2_mkdir(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT; 178 | lfs2_file_open(&lfs2, &file, "dirt/ground/earth", LFS2_O_WRONLY | LFS2_O_CREAT) 179 | => LFS2_ERR_NOENT; 180 | lfs2_unmount(&lfs2) => 0; 181 | ''' 182 | 183 | [[case]] # root operations 184 | code = ''' 185 | lfs2_format(&lfs2, &cfg) => 0; 186 | lfs2_mount(&lfs2, &cfg) => 0; 187 | lfs2_stat(&lfs2, "/", &info) => 0; 188 | assert(strcmp(info.name, "/") == 0); 189 | assert(info.type == LFS2_TYPE_DIR); 190 | 191 | lfs2_mkdir(&lfs2, "/") => LFS2_ERR_EXIST; 192 | lfs2_file_open(&lfs2, &file, "/", LFS2_O_WRONLY | LFS2_O_CREAT) 193 | => LFS2_ERR_ISDIR; 194 | 195 | lfs2_remove(&lfs2, "/") => LFS2_ERR_INVAL; 196 | lfs2_unmount(&lfs2) => 0; 197 | ''' 198 | 199 | [[case]] # root representations 200 | code = ''' 201 | lfs2_format(&lfs2, &cfg) => 0; 202 | lfs2_mount(&lfs2, &cfg) => 0; 203 | lfs2_stat(&lfs2, "/", &info) => 0; 204 | assert(strcmp(info.name, "/") == 0); 205 | assert(info.type == LFS2_TYPE_DIR); 206 | lfs2_stat(&lfs2, "", &info) => 0; 207 | assert(strcmp(info.name, "/") == 0); 208 | assert(info.type == LFS2_TYPE_DIR); 209 | lfs2_stat(&lfs2, ".", &info) => 0; 210 | assert(strcmp(info.name, "/") == 0); 211 | assert(info.type == LFS2_TYPE_DIR); 212 | lfs2_stat(&lfs2, "..", &info) => 0; 213 | assert(strcmp(info.name, "/") == 0); 214 | assert(info.type == LFS2_TYPE_DIR); 215 | lfs2_stat(&lfs2, "//", &info) => 0; 216 | assert(strcmp(info.name, "/") == 0); 217 | assert(info.type == LFS2_TYPE_DIR); 218 | lfs2_stat(&lfs2, "./", &info) => 0; 219 | assert(strcmp(info.name, "/") == 0); 220 | assert(info.type == LFS2_TYPE_DIR); 221 | lfs2_unmount(&lfs2) => 0; 222 | ''' 223 | 224 | [[case]] # superblock conflict test 225 | code = ''' 226 | lfs2_format(&lfs2, &cfg) => 0; 227 | lfs2_mount(&lfs2, &cfg) => 0; 228 | lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT; 229 | lfs2_remove(&lfs2, "littlefs") => LFS2_ERR_NOENT; 230 | 231 | lfs2_mkdir(&lfs2, "littlefs") => 0; 232 | lfs2_stat(&lfs2, "littlefs", &info) => 0; 233 | assert(strcmp(info.name, "littlefs") == 0); 234 | assert(info.type == LFS2_TYPE_DIR); 235 | lfs2_remove(&lfs2, "littlefs") => 0; 236 | lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT; 237 | lfs2_unmount(&lfs2) => 0; 238 | ''' 239 | 240 | [[case]] # max path test 241 | code = ''' 242 | lfs2_format(&lfs2, &cfg) => 0; 243 | lfs2_mount(&lfs2, &cfg) => 0; 244 | lfs2_mkdir(&lfs2, "coffee") => 0; 245 | lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0; 246 | lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0; 247 | lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0; 248 | 249 | memset(path, 'w', LFS2_NAME_MAX+1); 250 | path[LFS2_NAME_MAX+1] = '\0'; 251 | lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG; 252 | lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT) 253 | => LFS2_ERR_NAMETOOLONG; 254 | 255 | memcpy(path, "coffee/", strlen("coffee/")); 256 | memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX+1); 257 | path[strlen("coffee/")+LFS2_NAME_MAX+1] = '\0'; 258 | lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG; 259 | lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT) 260 | => LFS2_ERR_NAMETOOLONG; 261 | lfs2_unmount(&lfs2) => 0; 262 | ''' 263 | 264 | [[case]] # really big path test 265 | code = ''' 266 | lfs2_format(&lfs2, &cfg) => 0; 267 | lfs2_mount(&lfs2, &cfg) => 0; 268 | lfs2_mkdir(&lfs2, "coffee") => 0; 269 | lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0; 270 | lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0; 271 | lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0; 272 | 273 | memset(path, 'w', LFS2_NAME_MAX); 274 | path[LFS2_NAME_MAX] = '\0'; 275 | lfs2_mkdir(&lfs2, path) => 0; 276 | lfs2_remove(&lfs2, path) => 0; 277 | lfs2_file_open(&lfs2, &file, path, 278 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 279 | lfs2_file_close(&lfs2, &file) => 0; 280 | lfs2_remove(&lfs2, path) => 0; 281 | 282 | memcpy(path, "coffee/", strlen("coffee/")); 283 | memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX); 284 | path[strlen("coffee/")+LFS2_NAME_MAX] = '\0'; 285 | lfs2_mkdir(&lfs2, path) => 0; 286 | lfs2_remove(&lfs2, path) => 0; 287 | lfs2_file_open(&lfs2, &file, path, 288 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 289 | lfs2_file_close(&lfs2, &file) => 0; 290 | lfs2_remove(&lfs2, path) => 0; 291 | lfs2_unmount(&lfs2) => 0; 292 | ''' 293 | 294 | -------------------------------------------------------------------------------- /littlefs/bd/lfs2_testbd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Testing block device, wraps filebd and rambd while providing a bunch 3 | * of hooks for testing littlefs in various conditions. 4 | * 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "bd/lfs2_testbd.h" 9 | 10 | #include 11 | 12 | 13 | int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path, 14 | const struct lfs2_testbd_config *bdcfg) { 15 | LFS2_TESTBD_TRACE("lfs2_testbd_createcfg(%p {.context=%p, " 16 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 17 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 18 | ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " 19 | "\"%s\", " 20 | "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", " 21 | ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", " 22 | ".buffer=%p, .wear_buffer=%p})", 23 | (void*)cfg, cfg->context, 24 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 25 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 26 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, 27 | path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles, 28 | bdcfg->badblock_behavior, bdcfg->power_cycles, 29 | bdcfg->buffer, bdcfg->wear_buffer); 30 | lfs2_testbd_t *bd = cfg->context; 31 | bd->cfg = bdcfg; 32 | 33 | // setup testing things 34 | bd->persist = path; 35 | bd->power_cycles = bd->cfg->power_cycles; 36 | 37 | if (bd->cfg->erase_cycles) { 38 | if (bd->cfg->wear_buffer) { 39 | bd->wear = bd->cfg->wear_buffer; 40 | } else { 41 | bd->wear = lfs2_malloc(sizeof(lfs2_testbd_wear_t)*cfg->block_count); 42 | if (!bd->wear) { 43 | LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", LFS2_ERR_NOMEM); 44 | return LFS2_ERR_NOMEM; 45 | } 46 | } 47 | 48 | memset(bd->wear, 0, sizeof(lfs2_testbd_wear_t) * cfg->block_count); 49 | } 50 | 51 | // create underlying block device 52 | if (bd->persist) { 53 | bd->u.file.cfg = (struct lfs2_filebd_config){ 54 | .erase_value = bd->cfg->erase_value, 55 | }; 56 | int err = lfs2_filebd_createcfg(cfg, path, &bd->u.file.cfg); 57 | LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err); 58 | return err; 59 | } else { 60 | bd->u.ram.cfg = (struct lfs2_rambd_config){ 61 | .erase_value = bd->cfg->erase_value, 62 | .buffer = bd->cfg->buffer, 63 | }; 64 | int err = lfs2_rambd_createcfg(cfg, &bd->u.ram.cfg); 65 | LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err); 66 | return err; 67 | } 68 | } 69 | 70 | int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path) { 71 | LFS2_TESTBD_TRACE("lfs2_testbd_create(%p {.context=%p, " 72 | ".read=%p, .prog=%p, .erase=%p, .sync=%p, " 73 | ".read_size=%"PRIu32", .prog_size=%"PRIu32", " 74 | ".block_size=%"PRIu32", .block_count=%"PRIu32"}, " 75 | "\"%s\")", 76 | (void*)cfg, cfg->context, 77 | (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, 78 | (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, 79 | cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, 80 | path); 81 | static const struct lfs2_testbd_config defaults = {.erase_value=-1}; 82 | int err = lfs2_testbd_createcfg(cfg, path, &defaults); 83 | LFS2_TESTBD_TRACE("lfs2_testbd_create -> %d", err); 84 | return err; 85 | } 86 | 87 | int lfs2_testbd_destroy(const struct lfs2_config *cfg) { 88 | LFS2_TESTBD_TRACE("lfs2_testbd_destroy(%p)", (void*)cfg); 89 | lfs2_testbd_t *bd = cfg->context; 90 | if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { 91 | lfs2_free(bd->wear); 92 | } 93 | 94 | if (bd->persist) { 95 | int err = lfs2_filebd_destroy(cfg); 96 | LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err); 97 | return err; 98 | } else { 99 | int err = lfs2_rambd_destroy(cfg); 100 | LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err); 101 | return err; 102 | } 103 | } 104 | 105 | /// Internal mapping to block devices /// 106 | static int lfs2_testbd_rawread(const struct lfs2_config *cfg, lfs2_block_t block, 107 | lfs2_off_t off, void *buffer, lfs2_size_t size) { 108 | lfs2_testbd_t *bd = cfg->context; 109 | if (bd->persist) { 110 | return lfs2_filebd_read(cfg, block, off, buffer, size); 111 | } else { 112 | return lfs2_rambd_read(cfg, block, off, buffer, size); 113 | } 114 | } 115 | 116 | static int lfs2_testbd_rawprog(const struct lfs2_config *cfg, lfs2_block_t block, 117 | lfs2_off_t off, const void *buffer, lfs2_size_t size) { 118 | lfs2_testbd_t *bd = cfg->context; 119 | if (bd->persist) { 120 | return lfs2_filebd_prog(cfg, block, off, buffer, size); 121 | } else { 122 | return lfs2_rambd_prog(cfg, block, off, buffer, size); 123 | } 124 | } 125 | 126 | static int lfs2_testbd_rawerase(const struct lfs2_config *cfg, 127 | lfs2_block_t block) { 128 | lfs2_testbd_t *bd = cfg->context; 129 | if (bd->persist) { 130 | return lfs2_filebd_erase(cfg, block); 131 | } else { 132 | return lfs2_rambd_erase(cfg, block); 133 | } 134 | } 135 | 136 | static int lfs2_testbd_rawsync(const struct lfs2_config *cfg) { 137 | lfs2_testbd_t *bd = cfg->context; 138 | if (bd->persist) { 139 | return lfs2_filebd_sync(cfg); 140 | } else { 141 | return lfs2_rambd_sync(cfg); 142 | } 143 | } 144 | 145 | /// block device API /// 146 | int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block, 147 | lfs2_off_t off, void *buffer, lfs2_size_t size) { 148 | LFS2_TESTBD_TRACE("lfs2_testbd_read(%p, " 149 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 150 | (void*)cfg, block, off, buffer, size); 151 | lfs2_testbd_t *bd = cfg->context; 152 | 153 | // check if read is valid 154 | LFS2_ASSERT(off % cfg->read_size == 0); 155 | LFS2_ASSERT(size % cfg->read_size == 0); 156 | LFS2_ASSERT(block < cfg->block_count); 157 | 158 | // block bad? 159 | if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles && 160 | bd->cfg->badblock_behavior == LFS2_TESTBD_BADBLOCK_READERROR) { 161 | LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", LFS2_ERR_CORRUPT); 162 | return LFS2_ERR_CORRUPT; 163 | } 164 | 165 | // read 166 | int err = lfs2_testbd_rawread(cfg, block, off, buffer, size); 167 | LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", err); 168 | return err; 169 | } 170 | 171 | int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block, 172 | lfs2_off_t off, const void *buffer, lfs2_size_t size) { 173 | LFS2_TESTBD_TRACE("lfs2_testbd_prog(%p, " 174 | "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", 175 | (void*)cfg, block, off, buffer, size); 176 | lfs2_testbd_t *bd = cfg->context; 177 | 178 | // check if write is valid 179 | LFS2_ASSERT(off % cfg->prog_size == 0); 180 | LFS2_ASSERT(size % cfg->prog_size == 0); 181 | LFS2_ASSERT(block < cfg->block_count); 182 | 183 | // block bad? 184 | if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) { 185 | if (bd->cfg->badblock_behavior == 186 | LFS2_TESTBD_BADBLOCK_PROGERROR) { 187 | LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", LFS2_ERR_CORRUPT); 188 | return LFS2_ERR_CORRUPT; 189 | } else if (bd->cfg->badblock_behavior == 190 | LFS2_TESTBD_BADBLOCK_PROGNOOP || 191 | bd->cfg->badblock_behavior == 192 | LFS2_TESTBD_BADBLOCK_ERASENOOP) { 193 | LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0); 194 | return 0; 195 | } 196 | } 197 | 198 | // prog 199 | int err = lfs2_testbd_rawprog(cfg, block, off, buffer, size); 200 | if (err) { 201 | LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", err); 202 | return err; 203 | } 204 | 205 | // lose power? 206 | if (bd->power_cycles > 0) { 207 | bd->power_cycles -= 1; 208 | if (bd->power_cycles == 0) { 209 | // sync to make sure we persist the last changes 210 | assert(lfs2_testbd_rawsync(cfg) == 0); 211 | // simulate power loss 212 | exit(33); 213 | } 214 | } 215 | 216 | LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0); 217 | return 0; 218 | } 219 | 220 | int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block) { 221 | LFS2_TESTBD_TRACE("lfs2_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); 222 | lfs2_testbd_t *bd = cfg->context; 223 | 224 | // check if erase is valid 225 | LFS2_ASSERT(block < cfg->block_count); 226 | 227 | // block bad? 228 | if (bd->cfg->erase_cycles) { 229 | if (bd->wear[block] >= bd->cfg->erase_cycles) { 230 | if (bd->cfg->badblock_behavior == 231 | LFS2_TESTBD_BADBLOCK_ERASEERROR) { 232 | LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", LFS2_ERR_CORRUPT); 233 | return LFS2_ERR_CORRUPT; 234 | } else if (bd->cfg->badblock_behavior == 235 | LFS2_TESTBD_BADBLOCK_ERASENOOP) { 236 | LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", 0); 237 | return 0; 238 | } 239 | } else { 240 | // mark wear 241 | bd->wear[block] += 1; 242 | } 243 | } 244 | 245 | // erase 246 | int err = lfs2_testbd_rawerase(cfg, block); 247 | if (err) { 248 | LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", err); 249 | return err; 250 | } 251 | 252 | // lose power? 253 | if (bd->power_cycles > 0) { 254 | bd->power_cycles -= 1; 255 | if (bd->power_cycles == 0) { 256 | // sync to make sure we persist the last changes 257 | assert(lfs2_testbd_rawsync(cfg) == 0); 258 | // simulate power loss 259 | exit(33); 260 | } 261 | } 262 | 263 | LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0); 264 | return 0; 265 | } 266 | 267 | int lfs2_testbd_sync(const struct lfs2_config *cfg) { 268 | LFS2_TESTBD_TRACE("lfs2_testbd_sync(%p)", (void*)cfg); 269 | int err = lfs2_testbd_rawsync(cfg); 270 | LFS2_TESTBD_TRACE("lfs2_testbd_sync -> %d", err); 271 | return err; 272 | } 273 | 274 | 275 | /// simulated wear operations /// 276 | lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg, 277 | lfs2_block_t block) { 278 | LFS2_TESTBD_TRACE("lfs2_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block); 279 | lfs2_testbd_t *bd = cfg->context; 280 | 281 | // check if block is valid 282 | LFS2_ASSERT(bd->cfg->erase_cycles); 283 | LFS2_ASSERT(block < cfg->block_count); 284 | 285 | LFS2_TESTBD_TRACE("lfs2_testbd_getwear -> %"PRIu32, bd->wear[block]); 286 | return bd->wear[block]; 287 | } 288 | 289 | int lfs2_testbd_setwear(const struct lfs2_config *cfg, 290 | lfs2_block_t block, lfs2_testbd_wear_t wear) { 291 | LFS2_TESTBD_TRACE("lfs2_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block); 292 | lfs2_testbd_t *bd = cfg->context; 293 | 294 | // check if block is valid 295 | LFS2_ASSERT(bd->cfg->erase_cycles); 296 | LFS2_ASSERT(block < cfg->block_count); 297 | 298 | bd->wear[block] = wear; 299 | 300 | LFS2_TESTBD_TRACE("lfs2_testbd_setwear -> %d", 0); 301 | return 0; 302 | } 303 | -------------------------------------------------------------------------------- /littlefs/tests/test_evil.toml: -------------------------------------------------------------------------------- 1 | # Tests for recovering from conditions which shouldn't normally 2 | # happen during normal operation of littlefs 3 | 4 | # invalid pointer tests (outside of block_count) 5 | 6 | [[case]] # invalid tail-pointer test 7 | define.TAIL_TYPE = ['LFS2_TYPE_HARDTAIL', 'LFS2_TYPE_SOFTTAIL'] 8 | define.INVALSET = [0x3, 0x1, 0x2] 9 | in = "lfs2.c" 10 | code = ''' 11 | // create littlefs 12 | lfs2_format(&lfs2, &cfg) => 0; 13 | 14 | // change tail-pointer to invalid pointers 15 | lfs2_init(&lfs2, &cfg) => 0; 16 | lfs2_mdir_t mdir; 17 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 18 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 19 | {LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), 20 | (lfs2_block_t[2]){ 21 | (INVALSET & 0x1) ? 0xcccccccc : 0, 22 | (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; 23 | lfs2_deinit(&lfs2) => 0; 24 | 25 | // test that mount fails gracefully 26 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 27 | ''' 28 | 29 | [[case]] # invalid dir pointer test 30 | define.INVALSET = [0x3, 0x1, 0x2] 31 | in = "lfs2.c" 32 | code = ''' 33 | // create littlefs 34 | lfs2_format(&lfs2, &cfg) => 0; 35 | // make a dir 36 | lfs2_mount(&lfs2, &cfg) => 0; 37 | lfs2_mkdir(&lfs2, "dir_here") => 0; 38 | lfs2_unmount(&lfs2) => 0; 39 | 40 | // change the dir pointer to be invalid 41 | lfs2_init(&lfs2, &cfg) => 0; 42 | lfs2_mdir_t mdir; 43 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 44 | // make sure id 1 == our directory 45 | lfs2_dir_get(&lfs2, &mdir, 46 | LFS2_MKTAG(0x700, 0x3ff, 0), 47 | LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("dir_here")), buffer) 48 | => LFS2_MKTAG(LFS2_TYPE_DIR, 1, strlen("dir_here")); 49 | assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0); 50 | // change dir pointer 51 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 52 | {LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, 8), 53 | (lfs2_block_t[2]){ 54 | (INVALSET & 0x1) ? 0xcccccccc : 0, 55 | (INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0; 56 | lfs2_deinit(&lfs2) => 0; 57 | 58 | // test that accessing our bad dir fails, note there's a number 59 | // of ways to access the dir, some can fail, but some don't 60 | lfs2_mount(&lfs2, &cfg) => 0; 61 | lfs2_stat(&lfs2, "dir_here", &info) => 0; 62 | assert(strcmp(info.name, "dir_here") == 0); 63 | assert(info.type == LFS2_TYPE_DIR); 64 | 65 | lfs2_dir_open(&lfs2, &dir, "dir_here") => LFS2_ERR_CORRUPT; 66 | lfs2_stat(&lfs2, "dir_here/file_here", &info) => LFS2_ERR_CORRUPT; 67 | lfs2_dir_open(&lfs2, &dir, "dir_here/dir_here") => LFS2_ERR_CORRUPT; 68 | lfs2_file_open(&lfs2, &file, "dir_here/file_here", 69 | LFS2_O_RDONLY) => LFS2_ERR_CORRUPT; 70 | lfs2_file_open(&lfs2, &file, "dir_here/file_here", 71 | LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_CORRUPT; 72 | lfs2_unmount(&lfs2) => 0; 73 | ''' 74 | 75 | [[case]] # invalid file pointer test 76 | in = "lfs2.c" 77 | define.SIZE = [10, 1000, 100000] # faked file size 78 | code = ''' 79 | // create littlefs 80 | lfs2_format(&lfs2, &cfg) => 0; 81 | // make a file 82 | lfs2_mount(&lfs2, &cfg) => 0; 83 | lfs2_file_open(&lfs2, &file, "file_here", 84 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 85 | lfs2_file_close(&lfs2, &file) => 0; 86 | lfs2_unmount(&lfs2) => 0; 87 | 88 | // change the file pointer to be invalid 89 | lfs2_init(&lfs2, &cfg) => 0; 90 | lfs2_mdir_t mdir; 91 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 92 | // make sure id 1 == our file 93 | lfs2_dir_get(&lfs2, &mdir, 94 | LFS2_MKTAG(0x700, 0x3ff, 0), 95 | LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer) 96 | => LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here")); 97 | assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); 98 | // change file pointer 99 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 100 | {LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz)), 101 | &(struct lfs2_ctz){0xcccccccc, lfs2_tole32(SIZE)}})) => 0; 102 | lfs2_deinit(&lfs2) => 0; 103 | 104 | // test that accessing our bad file fails, note there's a number 105 | // of ways to access the dir, some can fail, but some don't 106 | lfs2_mount(&lfs2, &cfg) => 0; 107 | lfs2_stat(&lfs2, "file_here", &info) => 0; 108 | assert(strcmp(info.name, "file_here") == 0); 109 | assert(info.type == LFS2_TYPE_REG); 110 | assert(info.size == SIZE); 111 | 112 | lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0; 113 | lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT; 114 | lfs2_file_close(&lfs2, &file) => 0; 115 | 116 | // any allocs that traverse CTZ must unfortunately must fail 117 | if (SIZE > 2*LFS2_BLOCK_SIZE) { 118 | lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT; 119 | } 120 | lfs2_unmount(&lfs2) => 0; 121 | ''' 122 | 123 | [[case]] # invalid pointer in CTZ skip-list test 124 | define.SIZE = ['2*LFS2_BLOCK_SIZE', '3*LFS2_BLOCK_SIZE', '4*LFS2_BLOCK_SIZE'] 125 | in = "lfs2.c" 126 | code = ''' 127 | // create littlefs 128 | lfs2_format(&lfs2, &cfg) => 0; 129 | // make a file 130 | lfs2_mount(&lfs2, &cfg) => 0; 131 | lfs2_file_open(&lfs2, &file, "file_here", 132 | LFS2_O_WRONLY | LFS2_O_CREAT) => 0; 133 | for (int i = 0; i < SIZE; i++) { 134 | char c = 'c'; 135 | lfs2_file_write(&lfs2, &file, &c, 1) => 1; 136 | } 137 | lfs2_file_close(&lfs2, &file) => 0; 138 | lfs2_unmount(&lfs2) => 0; 139 | // change pointer in CTZ skip-list to be invalid 140 | lfs2_init(&lfs2, &cfg) => 0; 141 | lfs2_mdir_t mdir; 142 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 143 | // make sure id 1 == our file and get our CTZ structure 144 | lfs2_dir_get(&lfs2, &mdir, 145 | LFS2_MKTAG(0x700, 0x3ff, 0), 146 | LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer) 147 | => LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here")); 148 | assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0); 149 | struct lfs2_ctz ctz; 150 | lfs2_dir_get(&lfs2, &mdir, 151 | LFS2_MKTAG(0x700, 0x3ff, 0), 152 | LFS2_MKTAG(LFS2_TYPE_STRUCT, 1, sizeof(struct lfs2_ctz)), &ctz) 153 | => LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz)); 154 | lfs2_ctz_fromle32(&ctz); 155 | // rewrite block to contain bad pointer 156 | uint8_t bbuffer[LFS2_BLOCK_SIZE]; 157 | cfg.read(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0; 158 | uint32_t bad = lfs2_tole32(0xcccccccc); 159 | memcpy(&bbuffer[0], &bad, sizeof(bad)); 160 | memcpy(&bbuffer[4], &bad, sizeof(bad)); 161 | cfg.erase(&cfg, ctz.head) => 0; 162 | cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0; 163 | lfs2_deinit(&lfs2) => 0; 164 | 165 | // test that accessing our bad file fails, note there's a number 166 | // of ways to access the dir, some can fail, but some don't 167 | lfs2_mount(&lfs2, &cfg) => 0; 168 | lfs2_stat(&lfs2, "file_here", &info) => 0; 169 | assert(strcmp(info.name, "file_here") == 0); 170 | assert(info.type == LFS2_TYPE_REG); 171 | assert(info.size == SIZE); 172 | 173 | lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0; 174 | lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT; 175 | lfs2_file_close(&lfs2, &file) => 0; 176 | 177 | // any allocs that traverse CTZ must unfortunately must fail 178 | if (SIZE > 2*LFS2_BLOCK_SIZE) { 179 | lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT; 180 | } 181 | lfs2_unmount(&lfs2) => 0; 182 | ''' 183 | 184 | 185 | [[case]] # invalid gstate pointer 186 | define.INVALSET = [0x3, 0x1, 0x2] 187 | in = "lfs2.c" 188 | code = ''' 189 | // create littlefs 190 | lfs2_format(&lfs2, &cfg) => 0; 191 | 192 | // create an invalid gstate 193 | lfs2_init(&lfs2, &cfg) => 0; 194 | lfs2_mdir_t mdir; 195 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 196 | lfs2_fs_prepmove(&lfs2, 1, (lfs2_block_t [2]){ 197 | (INVALSET & 0x1) ? 0xcccccccc : 0, 198 | (INVALSET & 0x2) ? 0xcccccccc : 0}); 199 | lfs2_dir_commit(&lfs2, &mdir, NULL, 0) => 0; 200 | lfs2_deinit(&lfs2) => 0; 201 | 202 | // test that mount fails gracefully 203 | // mount may not fail, but our first alloc should fail when 204 | // we try to fix the gstate 205 | lfs2_mount(&lfs2, &cfg) => 0; 206 | lfs2_mkdir(&lfs2, "should_fail") => LFS2_ERR_CORRUPT; 207 | lfs2_unmount(&lfs2) => 0; 208 | ''' 209 | 210 | # cycle detection/recovery tests 211 | 212 | [[case]] # metadata-pair threaded-list loop test 213 | in = "lfs2.c" 214 | code = ''' 215 | // create littlefs 216 | lfs2_format(&lfs2, &cfg) => 0; 217 | 218 | // change tail-pointer to point to ourself 219 | lfs2_init(&lfs2, &cfg) => 0; 220 | lfs2_mdir_t mdir; 221 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 222 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 223 | {LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), 224 | (lfs2_block_t[2]){0, 1}})) => 0; 225 | lfs2_deinit(&lfs2) => 0; 226 | 227 | // test that mount fails gracefully 228 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 229 | ''' 230 | 231 | [[case]] # metadata-pair threaded-list 2-length loop test 232 | in = "lfs2.c" 233 | code = ''' 234 | // create littlefs with child dir 235 | lfs2_format(&lfs2, &cfg) => 0; 236 | lfs2_mount(&lfs2, &cfg) => 0; 237 | lfs2_mkdir(&lfs2, "child") => 0; 238 | lfs2_unmount(&lfs2) => 0; 239 | 240 | // find child 241 | lfs2_init(&lfs2, &cfg) => 0; 242 | lfs2_mdir_t mdir; 243 | lfs2_block_t pair[2]; 244 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 245 | lfs2_dir_get(&lfs2, &mdir, 246 | LFS2_MKTAG(0x7ff, 0x3ff, 0), 247 | LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair) 248 | => LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)); 249 | lfs2_pair_fromle32(pair); 250 | // change tail-pointer to point to root 251 | lfs2_dir_fetch(&lfs2, &mdir, pair) => 0; 252 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 253 | {LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), 254 | (lfs2_block_t[2]){0, 1}})) => 0; 255 | lfs2_deinit(&lfs2) => 0; 256 | 257 | // test that mount fails gracefully 258 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 259 | ''' 260 | 261 | [[case]] # metadata-pair threaded-list 1-length child loop test 262 | in = "lfs2.c" 263 | code = ''' 264 | // create littlefs with child dir 265 | lfs2_format(&lfs2, &cfg) => 0; 266 | lfs2_mount(&lfs2, &cfg) => 0; 267 | lfs2_mkdir(&lfs2, "child") => 0; 268 | lfs2_unmount(&lfs2) => 0; 269 | 270 | // find child 271 | lfs2_init(&lfs2, &cfg) => 0; 272 | lfs2_mdir_t mdir; 273 | lfs2_block_t pair[2]; 274 | lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0; 275 | lfs2_dir_get(&lfs2, &mdir, 276 | LFS2_MKTAG(0x7ff, 0x3ff, 0), 277 | LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair) 278 | => LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)); 279 | lfs2_pair_fromle32(pair); 280 | // change tail-pointer to point to ourself 281 | lfs2_dir_fetch(&lfs2, &mdir, pair) => 0; 282 | lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS( 283 | {LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0; 284 | lfs2_deinit(&lfs2) => 0; 285 | 286 | // test that mount fails gracefully 287 | lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT; 288 | ''' 289 | -------------------------------------------------------------------------------- /littlefs/tests/test_relocations.toml: -------------------------------------------------------------------------------- 1 | # specific corner cases worth explicitly testing for 2 | [[case]] # dangling split dir test 3 | define.ITERATIONS = 20 4 | define.COUNT = 10 5 | define.LFS2_BLOCK_CYCLES = [8, 1] 6 | code = ''' 7 | lfs2_format(&lfs2, &cfg) => 0; 8 | // fill up filesystem so only ~16 blocks are left 9 | lfs2_mount(&lfs2, &cfg) => 0; 10 | lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0; 11 | memset(buffer, 0, 512); 12 | while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) { 13 | lfs2_file_write(&lfs2, &file, buffer, 512) => 512; 14 | } 15 | lfs2_file_close(&lfs2, &file) => 0; 16 | // make a child dir to use in bounded space 17 | lfs2_mkdir(&lfs2, "child") => 0; 18 | lfs2_unmount(&lfs2) => 0; 19 | 20 | lfs2_mount(&lfs2, &cfg) => 0; 21 | for (int j = 0; j < ITERATIONS; j++) { 22 | for (int i = 0; i < COUNT; i++) { 23 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 24 | lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0; 25 | lfs2_file_close(&lfs2, &file) => 0; 26 | } 27 | 28 | lfs2_dir_open(&lfs2, &dir, "child") => 0; 29 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 30 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 31 | for (int i = 0; i < COUNT; i++) { 32 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 33 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 34 | strcmp(info.name, path) => 0; 35 | } 36 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 37 | lfs2_dir_close(&lfs2, &dir) => 0; 38 | 39 | if (j == ITERATIONS-1) { 40 | break; 41 | } 42 | 43 | for (int i = 0; i < COUNT; i++) { 44 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 45 | lfs2_remove(&lfs2, path) => 0; 46 | } 47 | } 48 | lfs2_unmount(&lfs2) => 0; 49 | 50 | lfs2_mount(&lfs2, &cfg) => 0; 51 | lfs2_dir_open(&lfs2, &dir, "child") => 0; 52 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 53 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 54 | for (int i = 0; i < COUNT; i++) { 55 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 56 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 57 | strcmp(info.name, path) => 0; 58 | } 59 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 60 | lfs2_dir_close(&lfs2, &dir) => 0; 61 | for (int i = 0; i < COUNT; i++) { 62 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 63 | lfs2_remove(&lfs2, path) => 0; 64 | } 65 | lfs2_unmount(&lfs2) => 0; 66 | ''' 67 | 68 | [[case]] # outdated head test 69 | define.ITERATIONS = 20 70 | define.COUNT = 10 71 | define.LFS2_BLOCK_CYCLES = [8, 1] 72 | code = ''' 73 | lfs2_format(&lfs2, &cfg) => 0; 74 | // fill up filesystem so only ~16 blocks are left 75 | lfs2_mount(&lfs2, &cfg) => 0; 76 | lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0; 77 | memset(buffer, 0, 512); 78 | while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) { 79 | lfs2_file_write(&lfs2, &file, buffer, 512) => 512; 80 | } 81 | lfs2_file_close(&lfs2, &file) => 0; 82 | // make a child dir to use in bounded space 83 | lfs2_mkdir(&lfs2, "child") => 0; 84 | lfs2_unmount(&lfs2) => 0; 85 | 86 | lfs2_mount(&lfs2, &cfg) => 0; 87 | for (int j = 0; j < ITERATIONS; j++) { 88 | for (int i = 0; i < COUNT; i++) { 89 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 90 | lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0; 91 | lfs2_file_close(&lfs2, &file) => 0; 92 | } 93 | 94 | lfs2_dir_open(&lfs2, &dir, "child") => 0; 95 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 96 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 97 | for (int i = 0; i < COUNT; i++) { 98 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 99 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 100 | strcmp(info.name, path) => 0; 101 | info.size => 0; 102 | 103 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 104 | lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0; 105 | lfs2_file_write(&lfs2, &file, "hi", 2) => 2; 106 | lfs2_file_close(&lfs2, &file) => 0; 107 | } 108 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 109 | 110 | lfs2_dir_rewind(&lfs2, &dir) => 0; 111 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 112 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 113 | for (int i = 0; i < COUNT; i++) { 114 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 115 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 116 | strcmp(info.name, path) => 0; 117 | info.size => 2; 118 | 119 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 120 | lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0; 121 | lfs2_file_write(&lfs2, &file, "hi", 2) => 2; 122 | lfs2_file_close(&lfs2, &file) => 0; 123 | } 124 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 125 | 126 | lfs2_dir_rewind(&lfs2, &dir) => 0; 127 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 128 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 129 | for (int i = 0; i < COUNT; i++) { 130 | sprintf(path, "test%03d_loooooooooooooooooong_name", i); 131 | lfs2_dir_read(&lfs2, &dir, &info) => 1; 132 | strcmp(info.name, path) => 0; 133 | info.size => 2; 134 | } 135 | lfs2_dir_read(&lfs2, &dir, &info) => 0; 136 | lfs2_dir_close(&lfs2, &dir) => 0; 137 | 138 | for (int i = 0; i < COUNT; i++) { 139 | sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); 140 | lfs2_remove(&lfs2, path) => 0; 141 | } 142 | } 143 | lfs2_unmount(&lfs2) => 0; 144 | ''' 145 | 146 | [[case]] # reentrant testing for relocations, this is the same as the 147 | # orphan testing, except here we also set block_cycles so that 148 | # almost every tree operation needs a relocation 149 | reentrant = true 150 | # TODO fix this case, caused by non-DAG trees 151 | if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)' 152 | define = [ 153 | {FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 154 | {FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 155 | {FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 156 | ] 157 | code = ''' 158 | err = lfs2_mount(&lfs2, &cfg); 159 | if (err) { 160 | lfs2_format(&lfs2, &cfg) => 0; 161 | lfs2_mount(&lfs2, &cfg) => 0; 162 | } 163 | 164 | srand(1); 165 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 166 | for (int i = 0; i < CYCLES; i++) { 167 | // create random path 168 | char full_path[256]; 169 | for (int d = 0; d < DEPTH; d++) { 170 | sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); 171 | } 172 | 173 | // if it does not exist, we create it, else we destroy 174 | int res = lfs2_stat(&lfs2, full_path, &info); 175 | if (res == LFS2_ERR_NOENT) { 176 | // create each directory in turn, ignore if dir already exists 177 | for (int d = 0; d < DEPTH; d++) { 178 | strcpy(path, full_path); 179 | path[2*d+2] = '\0'; 180 | err = lfs2_mkdir(&lfs2, path); 181 | assert(!err || err == LFS2_ERR_EXIST); 182 | } 183 | 184 | for (int d = 0; d < DEPTH; d++) { 185 | strcpy(path, full_path); 186 | path[2*d+2] = '\0'; 187 | lfs2_stat(&lfs2, path, &info) => 0; 188 | assert(strcmp(info.name, &path[2*d+1]) == 0); 189 | assert(info.type == LFS2_TYPE_DIR); 190 | } 191 | } else { 192 | // is valid dir? 193 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 194 | assert(info.type == LFS2_TYPE_DIR); 195 | 196 | // try to delete path in reverse order, ignore if dir is not empty 197 | for (int d = DEPTH-1; d >= 0; d--) { 198 | strcpy(path, full_path); 199 | path[2*d+2] = '\0'; 200 | err = lfs2_remove(&lfs2, path); 201 | assert(!err || err == LFS2_ERR_NOTEMPTY); 202 | } 203 | 204 | lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; 205 | } 206 | } 207 | lfs2_unmount(&lfs2) => 0; 208 | ''' 209 | 210 | [[case]] # reentrant testing for relocations, but now with random renames! 211 | reentrant = true 212 | # TODO fix this case, caused by non-DAG trees 213 | if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)' 214 | define = [ 215 | {FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 216 | {FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 217 | {FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1}, 218 | ] 219 | code = ''' 220 | err = lfs2_mount(&lfs2, &cfg); 221 | if (err) { 222 | lfs2_format(&lfs2, &cfg) => 0; 223 | lfs2_mount(&lfs2, &cfg) => 0; 224 | } 225 | 226 | srand(1); 227 | const char alpha[] = "abcdefghijklmnopqrstuvwxyz"; 228 | for (int i = 0; i < CYCLES; i++) { 229 | // create random path 230 | char full_path[256]; 231 | for (int d = 0; d < DEPTH; d++) { 232 | sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]); 233 | } 234 | 235 | // if it does not exist, we create it, else we destroy 236 | int res = lfs2_stat(&lfs2, full_path, &info); 237 | assert(!res || res == LFS2_ERR_NOENT); 238 | if (res == LFS2_ERR_NOENT) { 239 | // create each directory in turn, ignore if dir already exists 240 | for (int d = 0; d < DEPTH; d++) { 241 | strcpy(path, full_path); 242 | path[2*d+2] = '\0'; 243 | err = lfs2_mkdir(&lfs2, path); 244 | assert(!err || err == LFS2_ERR_EXIST); 245 | } 246 | 247 | for (int d = 0; d < DEPTH; d++) { 248 | strcpy(path, full_path); 249 | path[2*d+2] = '\0'; 250 | lfs2_stat(&lfs2, path, &info) => 0; 251 | assert(strcmp(info.name, &path[2*d+1]) == 0); 252 | assert(info.type == LFS2_TYPE_DIR); 253 | } 254 | } else { 255 | assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0); 256 | assert(info.type == LFS2_TYPE_DIR); 257 | 258 | // create new random path 259 | char new_path[256]; 260 | for (int d = 0; d < DEPTH; d++) { 261 | sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]); 262 | } 263 | 264 | // if new path does not exist, rename, otherwise destroy 265 | res = lfs2_stat(&lfs2, new_path, &info); 266 | assert(!res || res == LFS2_ERR_NOENT); 267 | if (res == LFS2_ERR_NOENT) { 268 | // stop once some dir is renamed 269 | for (int d = 0; d < DEPTH; d++) { 270 | strcpy(&path[2*d], &full_path[2*d]); 271 | path[2*d+2] = '\0'; 272 | strcpy(&path[128+2*d], &new_path[2*d]); 273 | path[128+2*d+2] = '\0'; 274 | err = lfs2_rename(&lfs2, path, path+128); 275 | assert(!err || err == LFS2_ERR_NOTEMPTY); 276 | if (!err) { 277 | strcpy(path, path+128); 278 | } 279 | } 280 | 281 | for (int d = 0; d < DEPTH; d++) { 282 | strcpy(path, new_path); 283 | path[2*d+2] = '\0'; 284 | lfs2_stat(&lfs2, path, &info) => 0; 285 | assert(strcmp(info.name, &path[2*d+1]) == 0); 286 | assert(info.type == LFS2_TYPE_DIR); 287 | } 288 | 289 | lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; 290 | } else { 291 | // try to delete path in reverse order, 292 | // ignore if dir is not empty 293 | for (int d = DEPTH-1; d >= 0; d--) { 294 | strcpy(path, full_path); 295 | path[2*d+2] = '\0'; 296 | err = lfs2_remove(&lfs2, path); 297 | assert(!err || err == LFS2_ERR_NOTEMPTY); 298 | } 299 | 300 | lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT; 301 | } 302 | } 303 | } 304 | lfs2_unmount(&lfs2) => 0; 305 | ''' 306 | -------------------------------------------------------------------------------- /LittleFileSystem2.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** \addtogroup storage */ 18 | /** @{*/ 19 | 20 | #ifndef MBED_LFS2FILESYSTEM_H 21 | #define MBED_LFS2FILESYSTEM_H 22 | 23 | #include "FileSystem.h" 24 | #include "BlockDevice.h" 25 | #include "PlatformMutex.h" 26 | #include "lfs2.h" 27 | 28 | namespace mbed { 29 | 30 | /** 31 | * LittleFileSystem2, a little file system 32 | * 33 | * Synchronization level: Thread safe 34 | */ 35 | class LittleFileSystem2 : public mbed::FileSystem { 36 | public: 37 | /** Lifetime of the LittleFileSystem2 38 | * 39 | * @param name Name of the file system in the tree. 40 | * @param bd Block device to mount. Mounted immediately if not NULL. 41 | * @param block_size 42 | * Size of a logical block. This does not impact ram consumption and 43 | * may be larger than the physical erase block. If the physical erase 44 | * block is larger, littlefs will use that instead. Larger values will 45 | * be faster but waste more storage when files are not aligned to a 46 | * block size. 47 | * @param block_cycles 48 | * Number of erase cycles before a block is forcefully evicted. Larger 49 | * values are more efficient but cause less even wear distribution. 0 50 | * disables dynamic wear-leveling. 51 | * @param cache_size 52 | * Size of read/program caches. Each file uses 1 cache, and littlefs 53 | * allocates 2 caches for internal operations. Larger values should be 54 | * faster but uses more RAM. 55 | * @param lookahead_size 56 | * Size of the lookahead buffer. A larger lookahead reduces the 57 | * allocation scans and results in a faster filesystem but uses 58 | * more RAM. 59 | */ 60 | LittleFileSystem2(const char *name = NULL, mbed::BlockDevice *bd = NULL, 61 | lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE, 62 | uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES, 63 | lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE, 64 | lfs2_size_t lookahead = MBED_LFS2_LOOKAHEAD_SIZE); 65 | 66 | virtual ~LittleFileSystem2(); 67 | 68 | /** Format a block device with the LittleFileSystem2. 69 | * 70 | * The block device to format should be mounted when this function is called. 71 | * 72 | * @param bd This is the block device that will be formatted. 73 | * @param block_size 74 | * Size of a logical block. This does not impact ram consumption and 75 | * may be larger than the physical erase block. If the physical erase 76 | * block is larger, littlefs will use that instead. Larger values will 77 | * be faster but waste more storage when files are not aligned to a 78 | * block size. 79 | * @param block_cycles 80 | * Number of erase cycles before a block is forcefully evicted. Larger 81 | * values are more efficient but cause less even wear distribution. 0 82 | * disables dynamic wear-leveling. 83 | * @param cache_size 84 | * Size of read/program caches. Each file uses 1 cache, and littlefs 85 | * allocates 2 caches for internal operations. Larger values should be 86 | * faster but uses more RAM. 87 | * @param lookahead_size 88 | * Size of the lookahead buffer. A larger lookahead reduces the 89 | * allocation scans and results in a faster filesystem but uses 90 | * more RAM. 91 | */ 92 | static int format(mbed::BlockDevice *bd, 93 | lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE, 94 | uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES, 95 | lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE, 96 | lfs2_size_t lookahead_size = MBED_LFS2_LOOKAHEAD_SIZE); 97 | 98 | /** Mount a file system to a block device. 99 | * 100 | * @param bd Block device to mount to. 101 | * @return 0 on success, negative error code on failure. 102 | */ 103 | virtual int mount(mbed::BlockDevice *bd); 104 | 105 | /** Unmount a file system from the underlying block device. 106 | * 107 | * @return 0 on success, negative error code on failure 108 | */ 109 | virtual int unmount(); 110 | 111 | /** Reformat a file system. Results in an empty and mounted file system. 112 | * 113 | * @param bd 114 | * Block device to reformat and mount. If NULL, the mounted 115 | * Block device is used. 116 | * Note: If mount fails, bd must be provided. 117 | * Default: NULL 118 | * 119 | * @return 0 on success, negative error code on failure 120 | */ 121 | virtual int reformat(mbed::BlockDevice *bd); 122 | 123 | /** Remove a file from the file system. 124 | * 125 | * @param path The name of the file to remove. 126 | * @return 0 on success, negative error code on failure 127 | */ 128 | virtual int remove(const char *path); 129 | 130 | /** Rename a file in the file system. 131 | * 132 | * @param path The name of the file to rename. 133 | * @param newpath The name to rename it to. 134 | * @return 0 on success, negative error code on failure 135 | */ 136 | virtual int rename(const char *path, const char *newpath); 137 | 138 | /** Store information about the file in a stat structure 139 | * 140 | * @param path The name of the file to find information about. 141 | * @param st The stat buffer to write to. 142 | * @return 0 on success, negative error code on failure 143 | */ 144 | virtual int stat(const char *path, struct stat *st); 145 | 146 | /** Create a directory in the file system. 147 | * 148 | * @param path The name of the directory to create. 149 | * @param mode The permissions with which to create the directory. 150 | * @return 0 on success, negative error code on failure 151 | */ 152 | virtual int mkdir(const char *path, mode_t mode); 153 | 154 | /** Store information about the mounted file system in a statvfs structure. 155 | * 156 | * @param path The name of the file to find information about. 157 | * @param buf The stat buffer to write to. 158 | * @return 0 on success, negative error code on failure 159 | */ 160 | virtual int statvfs(const char *path, struct statvfs *buf); 161 | 162 | protected: 163 | #if !(DOXYGEN_ONLY) 164 | /** Open a file on the file system. 165 | * 166 | * @param file Destination of the newly created handle to the referenced file. 167 | * @param path The name of the file to open. 168 | * @param flags The flags that trigger opening of the file. These flags are O_RDONLY, O_WRONLY, and O_RDWR, 169 | * with an O_CREAT, O_TRUNC, or O_APPEND bitwise OR operator. 170 | * @return 0 on success, negative error code on failure. 171 | */ 172 | virtual int file_open(mbed::fs_file_t *file, const char *path, int flags); 173 | 174 | /** Close a file 175 | * 176 | * @param file File handle. 177 | * return 0 on success, negative error code on failure 178 | */ 179 | virtual int file_close(mbed::fs_file_t file); 180 | 181 | /** Read the contents of a file into a buffer 182 | * 183 | * @param file File handle. 184 | * @param buffer The buffer to read in to. 185 | * @param size The number of bytes to read. 186 | * @return The number of bytes read, 0 at end of file, negative error on failure 187 | */ 188 | virtual ssize_t file_read(mbed::fs_file_t file, void *buffer, size_t size); 189 | 190 | /** Write the contents of a buffer to a file 191 | * 192 | * @param file File handle. 193 | * @param buffer The buffer to write from. 194 | * @param size The number of bytes to write. 195 | * @return The number of bytes written, negative error on failure 196 | */ 197 | virtual ssize_t file_write(mbed::fs_file_t file, const void *buffer, size_t size); 198 | 199 | /** Flush any buffers associated with the file 200 | * 201 | * @param file File handle. 202 | * @return 0 on success, negative error code on failure 203 | */ 204 | virtual int file_sync(mbed::fs_file_t file); 205 | 206 | /** Move the file position to a given offset from a given location 207 | * 208 | * @param file File handle. 209 | * @param offset The offset from whence to move to. 210 | * @param whence The start of where to seek. 211 | * SEEK_SET to start from beginning of file, 212 | * SEEK_CUR to start from current position in file, 213 | * SEEK_END to start from end of file. 214 | * @return The new offset of the file 215 | */ 216 | virtual off_t file_seek(mbed::fs_file_t file, off_t offset, int whence); 217 | 218 | /** Get the file position of the file 219 | * 220 | * @param file File handle. 221 | * @return The current offset in the file 222 | */ 223 | virtual off_t file_tell(mbed::fs_file_t file); 224 | 225 | /** Get the size of the file 226 | * 227 | * @param file File handle. 228 | * @return Size of the file in bytes 229 | */ 230 | virtual off_t file_size(mbed::fs_file_t file); 231 | 232 | /** Truncate or extend a file. 233 | * 234 | * The file's length is set to the specified value. The seek pointer is 235 | * not changed. If the file is extended, the extended area appears as if 236 | * it were zero-filled. 237 | * 238 | * @param file File handle. 239 | * @param length The requested new length for the file 240 | * 241 | * @return Zero on success, negative error code on failure 242 | */ 243 | virtual int file_truncate(mbed::fs_file_t file, off_t length); 244 | 245 | /** Open a directory on the file system. 246 | * 247 | * @param dir Destination for the handle to the directory. 248 | * @param path Name of the directory to open. 249 | * @return 0 on success, negative error code on failure 250 | */ 251 | virtual int dir_open(mbed::fs_dir_t *dir, const char *path); 252 | 253 | /** Close a directory 254 | * 255 | * @param dir Dir handle. 256 | * return 0 on success, negative error code on failure 257 | */ 258 | virtual int dir_close(mbed::fs_dir_t dir); 259 | 260 | /** Read the next directory entry 261 | * 262 | * @param dir Dir handle. 263 | * @param ent The directory entry to fill out. 264 | * @return 1 on reading a filename, 0 at end of directory, negative error on failure 265 | */ 266 | virtual ssize_t dir_read(mbed::fs_dir_t dir, struct dirent *ent); 267 | 268 | /** Set the current position of the directory 269 | * 270 | * @param dir Dir handle. 271 | * @param offset Offset of the location to seek to, 272 | * must be a value returned from dir_tell 273 | */ 274 | virtual void dir_seek(mbed::fs_dir_t dir, off_t offset); 275 | 276 | /** Get the current position of the directory 277 | * 278 | * @param dir Dir handle. 279 | * @return Position of the directory that can be passed to dir_rewind 280 | */ 281 | virtual off_t dir_tell(mbed::fs_dir_t dir); 282 | 283 | /** Rewind the current position to the beginning of the directory 284 | * 285 | * @param dir Dir handle 286 | */ 287 | virtual void dir_rewind(mbed::fs_dir_t dir); 288 | #endif //!(DOXYGEN_ONLY) 289 | 290 | private: 291 | lfs2_t _lfs; // The actual file system 292 | struct lfs2_config _config; 293 | mbed::BlockDevice *_bd; // The block device 294 | 295 | // thread-safe locking 296 | PlatformMutex _mutex; 297 | }; 298 | 299 | } // namespace mbed 300 | 301 | // Added "using" for backwards compatibility 302 | #ifndef MBED_NO_GLOBAL_USING_DIRECTIVE 303 | using mbed::LittleFileSystem2; 304 | #endif 305 | 306 | #endif 307 | 308 | /** @}*/ 309 | -------------------------------------------------------------------------------- /littlefs/scripts/readmdir.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct 4 | import binascii 5 | import sys 6 | import itertools as it 7 | 8 | TAG_TYPES = { 9 | 'splice': (0x700, 0x400), 10 | 'create': (0x7ff, 0x401), 11 | 'delete': (0x7ff, 0x4ff), 12 | 'name': (0x700, 0x000), 13 | 'reg': (0x7ff, 0x001), 14 | 'dir': (0x7ff, 0x002), 15 | 'superblock': (0x7ff, 0x0ff), 16 | 'struct': (0x700, 0x200), 17 | 'dirstruct': (0x7ff, 0x200), 18 | 'ctzstruct': (0x7ff, 0x202), 19 | 'inlinestruct': (0x7ff, 0x201), 20 | 'userattr': (0x700, 0x300), 21 | 'tail': (0x700, 0x600), 22 | 'softtail': (0x7ff, 0x600), 23 | 'hardtail': (0x7ff, 0x601), 24 | 'gstate': (0x700, 0x700), 25 | 'movestate': (0x7ff, 0x7ff), 26 | 'crc': (0x700, 0x500), 27 | } 28 | 29 | class Tag: 30 | def __init__(self, *args): 31 | if len(args) == 1: 32 | self.tag = args[0] 33 | elif len(args) == 3: 34 | if isinstance(args[0], str): 35 | type = TAG_TYPES[args[0]][1] 36 | else: 37 | type = args[0] 38 | 39 | if isinstance(args[1], str): 40 | id = int(args[1], 0) if args[1] not in 'x.' else 0x3ff 41 | else: 42 | id = args[1] 43 | 44 | if isinstance(args[2], str): 45 | size = int(args[2], str) if args[2] not in 'x.' else 0x3ff 46 | else: 47 | size = args[2] 48 | 49 | self.tag = (type << 20) | (id << 10) | size 50 | else: 51 | assert False 52 | 53 | @property 54 | def isvalid(self): 55 | return not bool(self.tag & 0x80000000) 56 | 57 | @property 58 | def isattr(self): 59 | return not bool(self.tag & 0x40000000) 60 | 61 | @property 62 | def iscompactable(self): 63 | return bool(self.tag & 0x20000000) 64 | 65 | @property 66 | def isunique(self): 67 | return not bool(self.tag & 0x10000000) 68 | 69 | @property 70 | def type(self): 71 | return (self.tag & 0x7ff00000) >> 20 72 | 73 | @property 74 | def type1(self): 75 | return (self.tag & 0x70000000) >> 20 76 | 77 | @property 78 | def type3(self): 79 | return (self.tag & 0x7ff00000) >> 20 80 | 81 | @property 82 | def id(self): 83 | return (self.tag & 0x000ffc00) >> 10 84 | 85 | @property 86 | def size(self): 87 | return (self.tag & 0x000003ff) >> 0 88 | 89 | @property 90 | def dsize(self): 91 | return 4 + (self.size if self.size != 0x3ff else 0) 92 | 93 | @property 94 | def chunk(self): 95 | return self.type & 0xff 96 | 97 | @property 98 | def schunk(self): 99 | return struct.unpack('b', struct.pack('B', self.chunk))[0] 100 | 101 | def is_(self, type): 102 | return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1] 103 | 104 | def mkmask(self): 105 | return Tag( 106 | 0x700 if self.isunique else 0x7ff, 107 | 0x3ff if self.isattr else 0, 108 | 0) 109 | 110 | def chid(self, nid): 111 | ntag = Tag(self.type, nid, self.size) 112 | if hasattr(self, 'off'): ntag.off = self.off 113 | if hasattr(self, 'data'): ntag.data = self.data 114 | if hasattr(self, 'crc'): ntag.crc = self.crc 115 | return ntag 116 | 117 | def typerepr(self): 118 | if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff: 119 | return 'crc (bad)' 120 | 121 | reverse_types = {v: k for k, v in TAG_TYPES.items()} 122 | for prefix in range(12): 123 | mask = 0x7ff & ~((1 << prefix)-1) 124 | if (mask, self.type & mask) in reverse_types: 125 | type = reverse_types[mask, self.type & mask] 126 | if prefix > 0: 127 | return '%s %#0*x' % ( 128 | type, prefix//4, self.type & ((1 << prefix)-1)) 129 | else: 130 | return type 131 | else: 132 | return '%02x' % self.type 133 | 134 | def idrepr(self): 135 | return repr(self.id) if self.id != 0x3ff else '.' 136 | 137 | def sizerepr(self): 138 | return repr(self.size) if self.size != 0x3ff else 'x' 139 | 140 | def __repr__(self): 141 | return 'Tag(%r, %d, %d)' % (self.typerepr(), self.id, self.size) 142 | 143 | def __lt__(self, other): 144 | return (self.id, self.type) < (other.id, other.type) 145 | 146 | def __bool__(self): 147 | return self.isvalid 148 | 149 | def __int__(self): 150 | return self.tag 151 | 152 | def __index__(self): 153 | return self.tag 154 | 155 | class MetadataPair: 156 | def __init__(self, blocks): 157 | if len(blocks) > 1: 158 | self.pair = [MetadataPair([block]) for block in blocks] 159 | self.pair = sorted(self.pair, reverse=True) 160 | 161 | self.data = self.pair[0].data 162 | self.rev = self.pair[0].rev 163 | self.tags = self.pair[0].tags 164 | self.ids = self.pair[0].ids 165 | self.log = self.pair[0].log 166 | self.all_ = self.pair[0].all_ 167 | return 168 | 169 | self.pair = [self] 170 | self.data = blocks[0] 171 | block = self.data 172 | 173 | self.rev, = struct.unpack('= 4: 183 | ntag, = struct.unpack('>I', block[off:off+4]) 184 | 185 | tag = Tag(int(tag) ^ ntag) 186 | tag.off = off + 4 187 | tag.data = block[off+4:off+tag.dsize] 188 | if tag.is_('crc'): 189 | crc = binascii.crc32(block[off:off+4+4], crc) 190 | else: 191 | crc = binascii.crc32(block[off:off+tag.dsize], crc) 192 | tag.crc = crc 193 | off += tag.dsize 194 | 195 | self.all_.append(tag) 196 | 197 | if tag.is_('crc'): 198 | # is valid commit? 199 | if crc != 0xffffffff: 200 | corrupt = True 201 | if not corrupt: 202 | self.log = self.all_.copy() 203 | 204 | # reset tag parsing 205 | crc = 0 206 | tag = Tag(int(tag) ^ ((tag.type & 1) << 31)) 207 | 208 | # find active ids 209 | self.ids = list(it.takewhile( 210 | lambda id: Tag('name', id, 0) in self, 211 | it.count())) 212 | 213 | # find most recent tags 214 | self.tags = [] 215 | for tag in self.log: 216 | if tag.is_('crc') or tag.is_('splice'): 217 | continue 218 | elif tag.id == 0x3ff: 219 | if tag in self and self[tag] is tag: 220 | self.tags.append(tag) 221 | else: 222 | # id could have change, I know this is messy and slow 223 | # but it works 224 | for id in self.ids: 225 | ntag = tag.chid(id) 226 | if ntag in self and self[ntag] is tag: 227 | self.tags.append(ntag) 228 | 229 | self.tags = sorted(self.tags) 230 | 231 | def __bool__(self): 232 | return bool(self.log) 233 | 234 | def __lt__(self, other): 235 | # corrupt blocks don't count 236 | if not self or not other: 237 | return bool(other) 238 | 239 | # use sequence arithmetic to avoid overflow 240 | return not ((other.rev - self.rev) & 0x80000000) 241 | 242 | def __contains__(self, args): 243 | try: 244 | self[args] 245 | return True 246 | except KeyError: 247 | return False 248 | 249 | def __getitem__(self, args): 250 | if isinstance(args, tuple): 251 | gmask, gtag = args 252 | else: 253 | gmask, gtag = args.mkmask(), args 254 | 255 | gdiff = 0 256 | for tag in reversed(self.log): 257 | if (gmask.id != 0 and tag.is_('splice') and 258 | tag.id <= gtag.id - gdiff): 259 | if tag.is_('create') and tag.id == gtag.id - gdiff: 260 | # creation point 261 | break 262 | 263 | gdiff += tag.schunk 264 | 265 | if ((int(gmask) & int(tag)) == 266 | (int(gmask) & int(gtag.chid(gtag.id - gdiff)))): 267 | if tag.size == 0x3ff: 268 | # deleted 269 | break 270 | 271 | return tag 272 | 273 | raise KeyError(gmask, gtag) 274 | 275 | def _dump_tags(self, tags, f=sys.stdout, truncate=True): 276 | f.write("%-8s %-8s %-13s %4s %4s" % ( 277 | 'off', 'tag', 'type', 'id', 'len')) 278 | if truncate: 279 | f.write(' data (truncated)') 280 | f.write('\n') 281 | 282 | for tag in tags: 283 | f.write("%08x: %08x %-13s %4s %4s" % ( 284 | tag.off, tag, 285 | tag.typerepr(), tag.idrepr(), tag.sizerepr())) 286 | if truncate: 287 | f.write(" %-23s %-8s\n" % ( 288 | ' '.join('%02x' % c for c in tag.data[:8]), 289 | ''.join(c if c >= ' ' and c <= '~' else '.' 290 | for c in map(chr, tag.data[:8])))) 291 | else: 292 | f.write("\n") 293 | for i in range(0, len(tag.data), 16): 294 | f.write(" %08x: %-47s %-16s\n" % ( 295 | tag.off+i, 296 | ' '.join('%02x' % c for c in tag.data[i:i+16]), 297 | ''.join(c if c >= ' ' and c <= '~' else '.' 298 | for c in map(chr, tag.data[i:i+16])))) 299 | 300 | def dump_tags(self, f=sys.stdout, truncate=True): 301 | self._dump_tags(self.tags, f=f, truncate=truncate) 302 | 303 | def dump_log(self, f=sys.stdout, truncate=True): 304 | self._dump_tags(self.log, f=f, truncate=truncate) 305 | 306 | def dump_all(self, f=sys.stdout, truncate=True): 307 | self._dump_tags(self.all_, f=f, truncate=truncate) 308 | 309 | def main(args): 310 | blocks = [] 311 | with open(args.disk, 'rb') as f: 312 | for block in [args.block1, args.block2]: 313 | if block is None: 314 | continue 315 | f.seek(block * args.block_size) 316 | blocks.append(f.read(args.block_size) 317 | .ljust(args.block_size, b'\xff')) 318 | 319 | # find most recent pair 320 | mdir = MetadataPair(blocks) 321 | 322 | try: 323 | mdir.tail = mdir[Tag('tail', 0, 0)] 324 | if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff': 325 | mdir.tail = None 326 | except KeyError: 327 | mdir.tail = None 328 | 329 | print("mdir {%s} rev %d%s%s%s" % ( 330 | ', '.join('%#x' % b 331 | for b in [args.block1, args.block2] 332 | if b is not None), 333 | mdir.rev, 334 | ' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:]) 335 | if len(mdir.pair) > 1 else '', 336 | ' (corrupted!)' if not mdir else '', 337 | ' -> {%#x, %#x}' % struct.unpack('=': 'ge', 32 | '<': 'lt', 33 | '>': 'gt', 34 | } 35 | 36 | TYPE = { 37 | 'int': { 38 | 'ctype': 'intmax_t', 39 | 'fail': FAIL, 40 | 'print': """ 41 | __attribute__((unused)) 42 | static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ 43 | (void)size; 44 | printf("%"PRIiMAX, v); 45 | }} 46 | """, 47 | 'assert': """ 48 | #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) 49 | do {{ 50 | __typeof__(lh) _lh = lh; 51 | __typeof__(lh) _rh = (__typeof__(lh))rh; 52 | if (!(_lh {op} _rh)) {{ 53 | __{prefix}_assert_fail_{type}(file, line, "{comp}", 54 | (intmax_t)_lh, 0, (intmax_t)_rh, 0); 55 | }} 56 | }} while (0) 57 | """ 58 | }, 59 | 'bool': { 60 | 'ctype': 'bool', 61 | 'fail': FAIL, 62 | 'print': """ 63 | __attribute__((unused)) 64 | static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ 65 | (void)size; 66 | printf("%s", v ? "true" : "false"); 67 | }} 68 | """, 69 | 'assert': """ 70 | #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) 71 | do {{ 72 | bool _lh = !!(lh); 73 | bool _rh = !!(rh); 74 | if (!(_lh {op} _rh)) {{ 75 | __{prefix}_assert_fail_{type}(file, line, "{comp}", 76 | _lh, 0, _rh, 0); 77 | }} 78 | }} while (0) 79 | """ 80 | }, 81 | 'mem': { 82 | 'ctype': 'const void *', 83 | 'fail': FAIL, 84 | 'print': """ 85 | __attribute__((unused)) 86 | static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ 87 | const uint8_t *s = v; 88 | printf("\\\""); 89 | for (size_t i = 0; i < size && i < {maxwidth}; i++) {{ 90 | if (s[i] >= ' ' && s[i] <= '~') {{ 91 | printf("%c", s[i]); 92 | }} else {{ 93 | printf("\\\\x%02x", s[i]); 94 | }} 95 | }} 96 | if (size > {maxwidth}) {{ 97 | printf("..."); 98 | }} 99 | printf("\\\""); 100 | }} 101 | """, 102 | 'assert': """ 103 | #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size) 104 | do {{ 105 | const void *_lh = lh; 106 | const void *_rh = rh; 107 | if (!(memcmp(_lh, _rh, size) {op} 0)) {{ 108 | __{prefix}_assert_fail_{type}(file, line, "{comp}", 109 | _lh, size, _rh, size); 110 | }} 111 | }} while (0) 112 | """ 113 | }, 114 | 'str': { 115 | 'ctype': 'const char *', 116 | 'fail': FAIL, 117 | 'print': """ 118 | __attribute__((unused)) 119 | static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{ 120 | __{prefix}_assert_print_mem(v, size); 121 | }} 122 | """, 123 | 'assert': """ 124 | #define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh) 125 | do {{ 126 | const char *_lh = lh; 127 | const char *_rh = rh; 128 | if (!(strcmp(_lh, _rh) {op} 0)) {{ 129 | __{prefix}_assert_fail_{type}(file, line, "{comp}", 130 | _lh, strlen(_lh), _rh, strlen(_rh)); 131 | }} 132 | }} while (0) 133 | """ 134 | } 135 | } 136 | 137 | def mkdecls(outf, maxwidth=16): 138 | outf.write("#include \n") 139 | outf.write("#include \n") 140 | outf.write("#include \n") 141 | outf.write("#include \n") 142 | outf.write("#include \n") 143 | 144 | for type, desc in sorted(TYPE.items()): 145 | format = { 146 | 'type': type.lower(), 'TYPE': type.upper(), 147 | 'ctype': desc['ctype'], 148 | 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), 149 | 'maxwidth': maxwidth, 150 | } 151 | outf.write(re.sub('\s+', ' ', 152 | desc['print'].strip().format(**format))+'\n') 153 | outf.write(re.sub('\s+', ' ', 154 | desc['fail'].strip().format(**format))+'\n') 155 | 156 | for op, comp in sorted(COMP.items()): 157 | format.update({ 158 | 'comp': comp.lower(), 'COMP': comp.upper(), 159 | 'op': op, 160 | }) 161 | outf.write(re.sub('\s+', ' ', 162 | desc['assert'].strip().format(**format))+'\n') 163 | 164 | def mkassert(type, comp, lh, rh, size=None): 165 | format = { 166 | 'type': type.lower(), 'TYPE': type.upper(), 167 | 'comp': comp.lower(), 'COMP': comp.upper(), 168 | 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), 169 | 'lh': lh.strip(' '), 170 | 'rh': rh.strip(' '), 171 | 'size': size, 172 | } 173 | if size: 174 | return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})') 175 | .format(**format)) 176 | else: 177 | return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})') 178 | .format(**format)) 179 | 180 | 181 | # simple recursive descent parser 182 | LEX = { 183 | 'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'], 184 | 'assert': PATTERN, 185 | 'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"], 186 | 'arrow': ['=>'], 187 | 'paren': ['\(', '\)'], 188 | 'op': ['strcmp', 'memcmp', '->'], 189 | 'comp': ['==', '!=', '<=', '>=', '<', '>'], 190 | 'logic': ['\&\&', '\|\|'], 191 | 'sep': [':', ';', '\{', '\}', ','], 192 | } 193 | 194 | class ParseFailure(Exception): 195 | def __init__(self, expected, found): 196 | self.expected = expected 197 | self.found = found 198 | 199 | def __str__(self): 200 | return "expected %r, found %s..." % ( 201 | self.expected, repr(self.found)[:70]) 202 | 203 | class Parse: 204 | def __init__(self, inf, lexemes): 205 | p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l)) 206 | for n, l in lexemes.items()) 207 | p = re.compile(p, re.DOTALL) 208 | data = inf.read() 209 | tokens = [] 210 | while True: 211 | m = p.search(data) 212 | if m: 213 | if m.start() > 0: 214 | tokens.append((None, data[:m.start()])) 215 | tokens.append((m.lastgroup, m.group())) 216 | data = data[m.end():] 217 | else: 218 | tokens.append((None, data)) 219 | break 220 | self.tokens = tokens 221 | self.off = 0 222 | 223 | def lookahead(self, *pattern): 224 | if self.off < len(self.tokens): 225 | token = self.tokens[self.off] 226 | if token[0] in pattern or token[1] in pattern: 227 | self.m = token[1] 228 | return self.m 229 | self.m = None 230 | return self.m 231 | 232 | def accept(self, *patterns): 233 | m = self.lookahead(*patterns) 234 | if m is not None: 235 | self.off += 1 236 | return m 237 | 238 | def expect(self, *patterns): 239 | m = self.accept(*patterns) 240 | if not m: 241 | raise ParseFailure(patterns, self.tokens[self.off:]) 242 | return m 243 | 244 | def push(self): 245 | return self.off 246 | 247 | def pop(self, state): 248 | self.off = state 249 | 250 | def passert(p): 251 | def pastr(p): 252 | p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 253 | p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 254 | lh = pexpr(p) ; p.accept('ws') 255 | p.expect(',') ; p.accept('ws') 256 | rh = pexpr(p) ; p.accept('ws') 257 | p.expect(')') ; p.accept('ws') 258 | comp = p.expect('comp') ; p.accept('ws') 259 | p.expect('0') ; p.accept('ws') 260 | p.expect(')') 261 | return mkassert('str', COMP[comp], lh, rh) 262 | 263 | def pamem(p): 264 | p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 265 | p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 266 | lh = pexpr(p) ; p.accept('ws') 267 | p.expect(',') ; p.accept('ws') 268 | rh = pexpr(p) ; p.accept('ws') 269 | p.expect(',') ; p.accept('ws') 270 | size = pexpr(p) ; p.accept('ws') 271 | p.expect(')') ; p.accept('ws') 272 | comp = p.expect('comp') ; p.accept('ws') 273 | p.expect('0') ; p.accept('ws') 274 | p.expect(')') 275 | return mkassert('mem', COMP[comp], lh, rh, size) 276 | 277 | def paint(p): 278 | p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 279 | lh = pexpr(p) ; p.accept('ws') 280 | comp = p.expect('comp') ; p.accept('ws') 281 | rh = pexpr(p) ; p.accept('ws') 282 | p.expect(')') 283 | return mkassert('int', COMP[comp], lh, rh) 284 | 285 | def pabool(p): 286 | p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws') 287 | lh = pexprs(p) ; p.accept('ws') 288 | p.expect(')') 289 | return mkassert('bool', 'eq', lh, 'true') 290 | 291 | def pa(p): 292 | return p.expect('assert') 293 | 294 | state = p.push() 295 | lastf = None 296 | for pa in [pastr, pamem, paint, pabool, pa]: 297 | try: 298 | return pa(p) 299 | except ParseFailure as f: 300 | p.pop(state) 301 | lastf = f 302 | else: 303 | raise lastf 304 | 305 | def pexpr(p): 306 | res = [] 307 | while True: 308 | if p.accept('('): 309 | res.append(p.m) 310 | while True: 311 | res.append(pexprs(p)) 312 | if p.accept('sep'): 313 | res.append(p.m) 314 | else: 315 | break 316 | res.append(p.expect(')')) 317 | elif p.lookahead('assert'): 318 | res.append(passert(p)) 319 | elif p.accept('assert', 'ws', 'string', 'op', None): 320 | res.append(p.m) 321 | else: 322 | return ''.join(res) 323 | 324 | def pexprs(p): 325 | res = [] 326 | while True: 327 | res.append(pexpr(p)) 328 | if p.accept('comp', 'logic', ','): 329 | res.append(p.m) 330 | else: 331 | return ''.join(res) 332 | 333 | def pstmt(p): 334 | ws = p.accept('ws') or '' 335 | lh = pexprs(p) 336 | if p.accept('=>'): 337 | rh = pexprs(p) 338 | return ws + mkassert('int', 'eq', lh, rh) 339 | else: 340 | return ws + lh 341 | 342 | 343 | def main(args): 344 | inf = open(args.input, 'r') if args.input else sys.stdin 345 | outf = open(args.output, 'w') if args.output else sys.stdout 346 | 347 | lexemes = LEX.copy() 348 | if args.pattern: 349 | lexemes['assert'] = args.pattern 350 | p = Parse(inf, lexemes) 351 | 352 | # write extra verbose asserts 353 | mkdecls(outf, maxwidth=args.maxwidth) 354 | if args.input: 355 | outf.write("#line %d \"%s\"\n" % (1, args.input)) 356 | 357 | # parse and write out stmt at a time 358 | try: 359 | while True: 360 | outf.write(pstmt(p)) 361 | if p.accept('sep'): 362 | outf.write(p.m) 363 | else: 364 | break 365 | except ParseFailure as f: 366 | pass 367 | 368 | for i in range(p.off, len(p.tokens)): 369 | outf.write(p.tokens[i][1]) 370 | 371 | if __name__ == "__main__": 372 | import argparse 373 | parser = argparse.ArgumentParser( 374 | description="Cpp step that increases assert verbosity") 375 | parser.add_argument('input', nargs='?', 376 | help="Input C file after cpp.") 377 | parser.add_argument('-o', '--output', required=True, 378 | help="Output C file.") 379 | parser.add_argument('-p', '--pattern', action='append', 380 | help="Patterns to search for starting an assert statement.") 381 | parser.add_argument('--maxwidth', default=MAXWIDTH, type=int, 382 | help="Maximum number of characters to display for strcmp and memcmp.") 383 | main(parser.parse_args()) 384 | -------------------------------------------------------------------------------- /TESTS/filesystem/interspersed/main.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2017 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "mbed.h" 17 | #include "greentea-client/test_env.h" 18 | #include "unity.h" 19 | #include "utest.h" 20 | #include 21 | #include 22 | 23 | using namespace utest::v1; 24 | 25 | // test configuration 26 | #ifndef MBED_TEST_FILESYSTEM 27 | #define MBED_TEST_FILESYSTEM LittleFileSystem2 28 | #endif 29 | 30 | #ifndef MBED_TEST_FILESYSTEM_DECL 31 | #define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs") 32 | #endif 33 | 34 | #ifndef MBED_TEST_BLOCKDEVICE 35 | #error [NOT_SUPPORTED] Non-volatile block device required 36 | #endif 37 | 38 | #ifndef MBED_TEST_BLOCKDEVICE_DECL 39 | #define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd 40 | #endif 41 | 42 | #ifndef MBED_TEST_FILES 43 | #define MBED_TEST_FILES 4 44 | #endif 45 | 46 | #ifndef MBED_TEST_DIRS 47 | #define MBED_TEST_DIRS 4 48 | #endif 49 | 50 | #ifndef MBED_TEST_BUFFER 51 | #define MBED_TEST_BUFFER 8192 52 | #endif 53 | 54 | #ifndef MBED_TEST_TIMEOUT 55 | #define MBED_TEST_TIMEOUT 480 56 | #endif 57 | 58 | 59 | // declarations 60 | #define STRINGIZE(x) STRINGIZE2(x) 61 | #define STRINGIZE2(x) #x 62 | #define INCLUDE(x) STRINGIZE(x.h) 63 | 64 | #include INCLUDE(MBED_TEST_FILESYSTEM) 65 | #include INCLUDE(MBED_TEST_BLOCKDEVICE) 66 | 67 | MBED_TEST_FILESYSTEM_DECL; 68 | MBED_TEST_BLOCKDEVICE_DECL; 69 | 70 | Dir dir[MBED_TEST_DIRS]; 71 | File file[MBED_TEST_FILES]; 72 | DIR *dd[MBED_TEST_DIRS]; 73 | FILE *fd[MBED_TEST_FILES]; 74 | struct dirent ent; 75 | struct dirent *ed; 76 | size_t size; 77 | uint8_t buffer[MBED_TEST_BUFFER]; 78 | uint8_t rbuffer[MBED_TEST_BUFFER]; 79 | uint8_t wbuffer[MBED_TEST_BUFFER]; 80 | 81 | 82 | // tests 83 | 84 | void test_parallel_tests() 85 | { 86 | int res = bd.init(); 87 | TEST_ASSERT_EQUAL(0, res); 88 | 89 | { 90 | res = MBED_TEST_FILESYSTEM::format(&bd); 91 | TEST_ASSERT_EQUAL(0, res); 92 | } 93 | 94 | res = bd.deinit(); 95 | TEST_ASSERT_EQUAL(0, res); 96 | } 97 | 98 | void test_parallel_file_test() 99 | { 100 | int res = bd.init(); 101 | TEST_ASSERT_EQUAL(0, res); 102 | 103 | { 104 | res = fs.mount(&bd); 105 | TEST_ASSERT_EQUAL(0, res); 106 | res = file[0].open(&fs, "a", O_WRONLY | O_CREAT); 107 | TEST_ASSERT_EQUAL(0, res); 108 | res = file[1].open(&fs, "b", O_WRONLY | O_CREAT); 109 | TEST_ASSERT_EQUAL(0, res); 110 | res = file[2].open(&fs, "c", O_WRONLY | O_CREAT); 111 | TEST_ASSERT_EQUAL(0, res); 112 | res = file[3].open(&fs, "d", O_WRONLY | O_CREAT); 113 | TEST_ASSERT_EQUAL(0, res); 114 | 115 | for (int i = 0; i < 10; i++) { 116 | res = file[0].write((const void *)"a", 1); 117 | TEST_ASSERT_EQUAL(1, res); 118 | res = file[1].write((const void *)"b", 1); 119 | TEST_ASSERT_EQUAL(1, res); 120 | res = file[2].write((const void *)"c", 1); 121 | TEST_ASSERT_EQUAL(1, res); 122 | res = file[3].write((const void *)"d", 1); 123 | TEST_ASSERT_EQUAL(1, res); 124 | } 125 | 126 | file[0].close(); 127 | file[1].close(); 128 | file[2].close(); 129 | file[3].close(); 130 | res = dir[0].open(&fs, "/"); 131 | TEST_ASSERT_EQUAL(0, res); 132 | res = dir[0].read(&ent); 133 | TEST_ASSERT_EQUAL(1, res); 134 | res = strcmp(ent.d_name, "."); 135 | TEST_ASSERT_EQUAL(0, res); 136 | res = ent.d_type; 137 | TEST_ASSERT_EQUAL(DT_DIR, res); 138 | res = dir[0].read(&ent); 139 | TEST_ASSERT_EQUAL(1, res); 140 | res = strcmp(ent.d_name, ".."); 141 | TEST_ASSERT_EQUAL(0, res); 142 | res = ent.d_type; 143 | TEST_ASSERT_EQUAL(DT_DIR, res); 144 | res = dir[0].read(&ent); 145 | TEST_ASSERT_EQUAL(1, res); 146 | res = strcmp(ent.d_name, "a"); 147 | TEST_ASSERT_EQUAL(0, res); 148 | res = ent.d_type; 149 | TEST_ASSERT_EQUAL(DT_REG, res); 150 | 151 | res = dir[0].read(&ent); 152 | TEST_ASSERT_EQUAL(1, res); 153 | res = strcmp(ent.d_name, "b"); 154 | TEST_ASSERT_EQUAL(0, res); 155 | res = ent.d_type; 156 | TEST_ASSERT_EQUAL(DT_REG, res); 157 | 158 | res = dir[0].read(&ent); 159 | TEST_ASSERT_EQUAL(1, res); 160 | res = strcmp(ent.d_name, "c"); 161 | TEST_ASSERT_EQUAL(0, res); 162 | res = ent.d_type; 163 | TEST_ASSERT_EQUAL(DT_REG, res); 164 | 165 | res = dir[0].read(&ent); 166 | TEST_ASSERT_EQUAL(1, res); 167 | res = strcmp(ent.d_name, "d"); 168 | TEST_ASSERT_EQUAL(0, res); 169 | res = ent.d_type; 170 | TEST_ASSERT_EQUAL(DT_REG, res); 171 | 172 | res = dir[0].read(&ent); 173 | TEST_ASSERT_EQUAL(0, res); 174 | res = dir[0].close(); 175 | TEST_ASSERT_EQUAL(0, res); 176 | res = file[0].open(&fs, "a", O_RDONLY); 177 | TEST_ASSERT_EQUAL(0, res); 178 | res = file[1].open(&fs, "b", O_RDONLY); 179 | TEST_ASSERT_EQUAL(0, res); 180 | res = file[2].open(&fs, "c", O_RDONLY); 181 | TEST_ASSERT_EQUAL(0, res); 182 | res = file[3].open(&fs, "d", O_RDONLY); 183 | TEST_ASSERT_EQUAL(0, res); 184 | 185 | for (int i = 0; i < 10; i++) { 186 | res = file[0].read(buffer, 1); 187 | TEST_ASSERT_EQUAL(1, res); 188 | res = buffer[0]; 189 | TEST_ASSERT_EQUAL('a', res); 190 | res = file[1].read(buffer, 1); 191 | TEST_ASSERT_EQUAL(1, res); 192 | res = buffer[0]; 193 | TEST_ASSERT_EQUAL('b', res); 194 | res = file[2].read(buffer, 1); 195 | TEST_ASSERT_EQUAL(1, res); 196 | res = buffer[0]; 197 | TEST_ASSERT_EQUAL('c', res); 198 | res = file[3].read(buffer, 1); 199 | TEST_ASSERT_EQUAL(1, res); 200 | res = buffer[0]; 201 | TEST_ASSERT_EQUAL('d', res); 202 | } 203 | 204 | file[0].close(); 205 | file[1].close(); 206 | file[2].close(); 207 | file[3].close(); 208 | res = fs.unmount(); 209 | TEST_ASSERT_EQUAL(0, res); 210 | } 211 | 212 | res = bd.deinit(); 213 | TEST_ASSERT_EQUAL(0, res); 214 | } 215 | 216 | void test_parallel_remove_file_test() 217 | { 218 | int res = bd.init(); 219 | TEST_ASSERT_EQUAL(0, res); 220 | 221 | { 222 | res = fs.mount(&bd); 223 | TEST_ASSERT_EQUAL(0, res); 224 | res = file[0].open(&fs, "e", O_WRONLY | O_CREAT); 225 | TEST_ASSERT_EQUAL(0, res); 226 | 227 | for (int i = 0; i < 5; i++) { 228 | res = file[0].write((const void *)"e", 1); 229 | TEST_ASSERT_EQUAL(1, res); 230 | } 231 | res = fs.remove("a"); 232 | TEST_ASSERT_EQUAL(0, res); 233 | res = fs.remove("b"); 234 | TEST_ASSERT_EQUAL(0, res); 235 | res = fs.remove("c"); 236 | TEST_ASSERT_EQUAL(0, res); 237 | res = fs.remove("d"); 238 | TEST_ASSERT_EQUAL(0, res); 239 | 240 | for (int i = 0; i < 5; i++) { 241 | res = file[0].write((const void *)"e", 1); 242 | TEST_ASSERT_EQUAL(1, res); 243 | } 244 | 245 | file[0].close(); 246 | res = dir[0].open(&fs, "/"); 247 | TEST_ASSERT_EQUAL(0, res); 248 | res = dir[0].read(&ent); 249 | TEST_ASSERT_EQUAL(1, res); 250 | res = strcmp(ent.d_name, "."); 251 | TEST_ASSERT_EQUAL(0, res); 252 | res = ent.d_type; 253 | TEST_ASSERT_EQUAL(DT_DIR, res); 254 | res = dir[0].read(&ent); 255 | TEST_ASSERT_EQUAL(1, res); 256 | res = strcmp(ent.d_name, ".."); 257 | TEST_ASSERT_EQUAL(0, res); 258 | res = ent.d_type; 259 | TEST_ASSERT_EQUAL(DT_DIR, res); 260 | res = dir[0].read(&ent); 261 | TEST_ASSERT_EQUAL(1, res); 262 | res = strcmp(ent.d_name, "e"); 263 | TEST_ASSERT_EQUAL(0, res); 264 | res = ent.d_type; 265 | TEST_ASSERT_EQUAL(DT_REG, res); 266 | 267 | res = dir[0].read(&ent); 268 | TEST_ASSERT_EQUAL(0, res); 269 | res = dir[0].close(); 270 | TEST_ASSERT_EQUAL(0, res); 271 | res = file[0].open(&fs, "e", O_RDONLY); 272 | TEST_ASSERT_EQUAL(0, res); 273 | 274 | for (int i = 0; i < 10; i++) { 275 | res = file[0].read(buffer, 1); 276 | TEST_ASSERT_EQUAL(1, res); 277 | res = buffer[0]; 278 | TEST_ASSERT_EQUAL('e', res); 279 | } 280 | 281 | file[0].close(); 282 | res = fs.unmount(); 283 | TEST_ASSERT_EQUAL(0, res); 284 | } 285 | 286 | res = bd.deinit(); 287 | TEST_ASSERT_EQUAL(0, res); 288 | } 289 | 290 | void test_remove_inconveniently_test() 291 | { 292 | int res = bd.init(); 293 | TEST_ASSERT_EQUAL(0, res); 294 | 295 | { 296 | res = fs.mount(&bd); 297 | TEST_ASSERT_EQUAL(0, res); 298 | res = file[0].open(&fs, "e", O_WRONLY | O_TRUNC); 299 | TEST_ASSERT_EQUAL(0, res); 300 | res = file[1].open(&fs, "f", O_WRONLY | O_CREAT); 301 | TEST_ASSERT_EQUAL(0, res); 302 | res = file[2].open(&fs, "g", O_WRONLY | O_CREAT); 303 | TEST_ASSERT_EQUAL(0, res); 304 | 305 | for (int i = 0; i < 5; i++) { 306 | res = file[0].write((const void *)"e", 1); 307 | TEST_ASSERT_EQUAL(1, res); 308 | res = file[1].write((const void *)"f", 1); 309 | TEST_ASSERT_EQUAL(1, res); 310 | res = file[2].write((const void *)"g", 1); 311 | TEST_ASSERT_EQUAL(1, res); 312 | } 313 | res = fs.remove("f"); 314 | TEST_ASSERT_EQUAL(0, res); 315 | 316 | for (int i = 0; i < 5; i++) { 317 | res = file[0].write((const void *)"e", 1); 318 | TEST_ASSERT_EQUAL(1, res); 319 | res = file[1].write((const void *)"f", 1); 320 | TEST_ASSERT_EQUAL(1, res); 321 | res = file[2].write((const void *)"g", 1); 322 | TEST_ASSERT_EQUAL(1, res); 323 | } 324 | 325 | file[0].close(); 326 | file[1].close(); 327 | file[2].close(); 328 | res = dir[0].open(&fs, "/"); 329 | TEST_ASSERT_EQUAL(0, res); 330 | res = dir[0].read(&ent); 331 | TEST_ASSERT_EQUAL(1, res); 332 | res = strcmp(ent.d_name, "."); 333 | TEST_ASSERT_EQUAL(0, res); 334 | res = ent.d_type; 335 | TEST_ASSERT_EQUAL(DT_DIR, res); 336 | res = dir[0].read(&ent); 337 | TEST_ASSERT_EQUAL(1, res); 338 | res = strcmp(ent.d_name, ".."); 339 | TEST_ASSERT_EQUAL(0, res); 340 | res = ent.d_type; 341 | TEST_ASSERT_EQUAL(DT_DIR, res); 342 | res = dir[0].read(&ent); 343 | TEST_ASSERT_EQUAL(1, res); 344 | res = strcmp(ent.d_name, "e"); 345 | TEST_ASSERT_EQUAL(0, res); 346 | res = ent.d_type; 347 | TEST_ASSERT_EQUAL(DT_REG, res); 348 | 349 | res = dir[0].read(&ent); 350 | TEST_ASSERT_EQUAL(1, res); 351 | res = strcmp(ent.d_name, "g"); 352 | TEST_ASSERT_EQUAL(0, res); 353 | res = ent.d_type; 354 | TEST_ASSERT_EQUAL(DT_REG, res); 355 | 356 | res = dir[0].read(&ent); 357 | TEST_ASSERT_EQUAL(0, res); 358 | res = dir[0].close(); 359 | TEST_ASSERT_EQUAL(0, res); 360 | res = file[0].open(&fs, "e", O_RDONLY); 361 | TEST_ASSERT_EQUAL(0, res); 362 | res = file[1].open(&fs, "g", O_RDONLY); 363 | TEST_ASSERT_EQUAL(0, res); 364 | 365 | for (int i = 0; i < 10; i++) { 366 | res = file[0].read(buffer, 1); 367 | TEST_ASSERT_EQUAL(1, res); 368 | res = buffer[0]; 369 | TEST_ASSERT_EQUAL('e', res); 370 | res = file[1].read(buffer, 1); 371 | TEST_ASSERT_EQUAL(1, res); 372 | res = buffer[0]; 373 | TEST_ASSERT_EQUAL('g', res); 374 | } 375 | 376 | file[0].close(); 377 | file[1].close(); 378 | res = fs.unmount(); 379 | TEST_ASSERT_EQUAL(0, res); 380 | } 381 | 382 | res = bd.deinit(); 383 | TEST_ASSERT_EQUAL(0, res); 384 | } 385 | 386 | 387 | 388 | // test setup 389 | utest::v1::status_t test_setup(const size_t number_of_cases) 390 | { 391 | GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto"); 392 | return verbose_test_setup_handler(number_of_cases); 393 | } 394 | 395 | Case cases[] = { 396 | Case("Parallel tests", test_parallel_tests), 397 | Case("Parallel file test", test_parallel_file_test), 398 | Case("Parallel remove file test", test_parallel_remove_file_test), 399 | Case("Remove inconveniently test", test_remove_inconveniently_test), 400 | }; 401 | 402 | Specification specification(test_setup, cases); 403 | 404 | int main() 405 | { 406 | return !Harness::run(specification); 407 | } 408 | --------------------------------------------------------------------------------