Compare commits

..

2 Commits

Author SHA1 Message Date
Christopher Haster
b392c49e36 WIP one possible solution for non-DAG trees exploiting half-orphans 2020-02-22 23:50:39 -06:00
Christopher Haster
17efa7b3b9 Renamed test_format -> test_superblocks
With the superblock expansion stuff, the test_format tests have grown
to test more advanced superblock-related features. This is fine but
deserves a rename so it's more clear.
2020-02-18 18:08:24 -06:00
31 changed files with 1400 additions and 3244 deletions

View File

@@ -1,26 +0,0 @@
name: post-release
on:
release:
branches: [master]
types: [released]
jobs:
post-release:
runs-on: ubuntu-18.04
steps:
# trigger post-release in dependency repo, this indirection allows the
# dependency repo to be updated often without affecting this repo. At
# the time of this comment, the dependency repo is responsible for
# creating PRs for other dependent repos post-release.
- name: trigger-post-release
continue-on-error: true
run: |
curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \
"$GITHUB_API_URL/repos/${{secrets.POST_RELEASE_REPO}}/dispatches" \
-d "$(jq -n '{
event_type: "post-release",
client_payload: {
repo: env.GITHUB_REPOSITORY,
version: "${{github.event.release.tag_name}}"}}' \
| tee /dev/stderr)"

View File

@@ -1,215 +0,0 @@
name: release
on:
workflow_run:
workflows: [test]
branches: [master]
types: [completed]
jobs:
release:
runs-on: ubuntu-18.04
# need to manually check for a couple things
# - tests passed?
# - we are the most recent commit on master?
if: ${{github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_sha == github.sha}}
steps:
- uses: actions/checkout@v2
with:
ref: ${{github.event.workflow_run.head_sha}}
# need workflow access since we push branches
# containing workflows
token: ${{secrets.BOT_TOKEN}}
# need all tags
fetch-depth: 0
# try to get results from tests
- uses: dawidd6/action-download-artifact@v2
continue-on-error: true
with:
workflow: ${{github.event.workflow_run.name}}
run_id: ${{github.event.workflow_run.id}}
name: results
path: results
- name: find-version
run: |
# rip version from lfs.h
LFS_VERSION="$(grep -o '^#define LFS_VERSION .*$' lfs.h \
| awk '{print $3}')"
LFS_VERSION_MAJOR="$((0xffff & ($LFS_VERSION >> 16)))"
LFS_VERSION_MINOR="$((0xffff & ($LFS_VERSION >> 0)))"
# find a new patch version based on what we find in our tags
LFS_VERSION_PATCH="$( \
( git describe --tags --abbrev=0 \
--match="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.*" \
|| echo 'v0.0.-1' ) \
| awk -F '.' '{print $3+1}')"
# found new version
LFS_VERSION="v$LFS_VERSION_MAJOR`
`.$LFS_VERSION_MINOR`
`.$LFS_VERSION_PATCH"
echo "LFS_VERSION=$LFS_VERSION"
echo "LFS_VERSION=$LFS_VERSION" >> $GITHUB_ENV
echo "LFS_VERSION_MAJOR=$LFS_VERSION_MAJOR" >> $GITHUB_ENV
echo "LFS_VERSION_MINOR=$LFS_VERSION_MINOR" >> $GITHUB_ENV
echo "LFS_VERSION_PATCH=$LFS_VERSION_PATCH" >> $GITHUB_ENV
# try to find previous version?
- name: find-prev-version
continue-on-error: true
run: |
LFS_PREV_VERSION="$(git describe --tags --abbrev=0 --match 'v*')"
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION"
echo "LFS_PREV_VERSION=$LFS_PREV_VERSION" >> $GITHUB_ENV
# try to find results from tests
- name: collect-results
run: |
# previous results to compare against?
[ -n "$LFS_PREV_VERSION" ] && curl -sS \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/`
`status/$LFS_PREV_VERSION" \
| jq -re 'select(.sha != env.GITHUB_SHA) | .statuses[]' \
>> prev-results.json \
|| true
# unfortunately these each have their own format
[ -e results/code-thumb.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / code").description
| capture("Code size is (?<result>[0-9]+)").result' \
prev-results.json || echo 0)"
./scripts/code.py -u results/code-thumb.csv -s | awk '
NR==2 {printf "Code size,%d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
[ -e results/code-thumb-readonly.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / code (readonly)").description
| capture("Code size is (?<result>[0-9]+)").result' \
prev-results.json || echo 0)"
./scripts/code.py -u results/code-thumb-readonly.csv -s | awk '
NR==2 {printf "Code size<br/>(readonly),%d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
[ -e results/code-thumb-threadsafe.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / code (threadsafe)").description
| capture("Code size is (?<result>[0-9]+)").result' \
prev-results.json || echo 0)"
./scripts/code.py -u results/code-thumb-threadsafe.csv -s | awk '
NR==2 {printf "Code size<br/>(threadsafe),%d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
[ -e results/code-thumb-migrate.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / code (migrate)").description
| capture("Code size is (?<result>[0-9]+)").result' \
prev-results.json || echo 0)"
./scripts/code.py -u results/code-thumb-migrate.csv -s | awk '
NR==2 {printf "Code size<br/>(migrate),%d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
[ -e results/code-thumb-error-asserts.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / code (error-asserts)").description
| capture("Code size is (?<result>[0-9]+)").result' \
prev-results.json || echo 0)"
./scripts/code.py -u results/code-thumb-error-asserts.csv -s | awk '
NR==2 {printf "Code size<br/>(error-asserts),%d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
[ -e results/coverage.csv ] && ( \
export PREV="$(jq -re '
select(.context == "results / coverage").description
| capture("Coverage is (?<result>[0-9\\.]+)").result' \
prev-results.json || echo 0)"
./scripts/coverage.py -u results/coverage.csv -s | awk -F '[ /%]+' '
NR==2 {printf "Coverage,%.1f%% of %d lines",$4,$3}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",$4-ENVIRON["PREV"]}
NR==2 {printf "\n"}' \
>> results.csv)
# transpose to GitHub table
[ -e results.csv ] || exit 0
awk -F ',' '
{label[NR]=$1; value[NR]=$2}
END {
for (r=1; r<=NR; r++) {printf "| %s ",label[r]}; printf "|\n";
for (r=1; r<=NR; r++) {printf "|:--"}; printf "|\n";
for (r=1; r<=NR; r++) {printf "| %s ",value[r]}; printf "|\n"}' \
results.csv > results.txt
echo "RESULTS:"
cat results.txt
# find changes from history
- name: collect-changes
run: |
[ -n "$LFS_PREV_VERSION" ] || exit 0
# use explicit link to github commit so that release notes can
# be copied elsewhere
git log "$LFS_PREV_VERSION.." \
--grep='^Merge' --invert-grep \
--format="format:[\`%h\`](`
`https://github.com/$GITHUB_REPOSITORY/commit/%h) %s" \
> changes.txt
echo "CHANGES:"
cat changes.txt
# create and update major branches (vN and vN-prefix)
- name: create-major-branches
run: |
# create major branch
git branch "v$LFS_VERSION_MAJOR" HEAD
# create major prefix branch
git config user.name ${{secrets.BOT_USER}}
git config user.email ${{secrets.BOT_EMAIL}}
git fetch "https://github.com/$GITHUB_REPOSITORY.git" \
"v$LFS_VERSION_MAJOR-prefix" || true
./scripts/prefix.py "lfs$LFS_VERSION_MAJOR"
git branch "v$LFS_VERSION_MAJOR-prefix" $( \
git commit-tree $(git write-tree) \
$(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \
-p HEAD \
-m "Generated v$LFS_VERSION_MAJOR prefixes")
git reset --hard
# push!
git push --atomic origin \
"v$LFS_VERSION_MAJOR" \
"v$LFS_VERSION_MAJOR-prefix"
# build release notes
- name: create-release
run: |
# create release and patch version tag (vN.N.N)
# only draft if not a patch release
[ -e results.txt ] && export RESULTS="$(cat results.txt)"
[ -e changes.txt ] && export CHANGES="$(cat changes.txt)"
curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \
-d "$(jq -n '{
tag_name: env.LFS_VERSION,
name: env.LFS_VERSION | rtrimstr(".0"),
target_commitish: "${{github.event.workflow_run.head_sha}}",
draft: env.LFS_VERSION | endswith(".0"),
body: [env.RESULTS, env.CHANGES | select(.)] | join("\n\n")}' \
| tee /dev/stderr)"

View File

@@ -1,55 +0,0 @@
name: status
on:
workflow_run:
workflows: [test]
types: [completed]
jobs:
status:
runs-on: ubuntu-18.04
steps:
# custom statuses?
- uses: dawidd6/action-download-artifact@v2
continue-on-error: true
with:
workflow: ${{github.event.workflow_run.name}}
run_id: ${{github.event.workflow_run.id}}
name: status
path: status
- name: update-status
continue-on-error: true
run: |
ls status
for s in $(shopt -s nullglob ; echo status/*.json)
do
# parse requested status
export STATE="$(jq -er '.state' $s)"
export CONTEXT="$(jq -er '.context' $s)"
export DESCRIPTION="$(jq -er '.description' $s)"
# help lookup URL for job/steps because GitHub makes
# it VERY HARD to link to specific jobs
export TARGET_URL="$(
jq -er '.target_url // empty' $s || (
export TARGET_JOB="$(jq -er '.target_job' $s)"
export TARGET_STEP="$(jq -er '.target_step // ""' $s)"
curl -sS -H "authorization: token ${{secrets.BOT_TOKEN}}" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/runs/`
`${{github.event.workflow_run.id}}/jobs" \
| jq -er '.jobs[]
| select(.name == env.TARGET_JOB)
| .html_url
+ "?check_suite_focus=true"
+ ((.steps[]
| select(.name == env.TARGET_STEP)
| "#step:\(.number):0") // "")'))"
# update status
curl -sS -X POST -H "authorization: token ${{secrets.BOT_TOKEN}}" \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/statuses/`
`${{github.event.workflow_run.head_sha}}" \
-d "$(jq -n '{
state: env.STATE,
context: env.CONTEXT,
description: env.DESCRIPTION,
target_url: env.TARGET_URL}' \
| tee /dev/stderr)"
done

View File

@@ -1,446 +0,0 @@
name: test
on: [push, pull_request]
env:
CFLAGS: -Werror
MAKEFLAGS: -j
jobs:
# run tests
test:
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
arch: [x86_64, thumb, mips, powerpc]
steps:
- uses: actions/checkout@v2
- name: install
run: |
# need toml, also pip3 isn't installed by default?
sudo apt-get update -qq
sudo apt-get install -qq python3 python3-pip lcov
sudo pip3 install toml
gcc --version
# setup a ram-backed disk to speed up reentrant tests
mkdir disks
sudo mount -t tmpfs -o size=100m tmpfs disks
TESTFLAGS="$TESTFLAGS --disk=disks/disk"
# collect coverage
mkdir -p coverage
TESTFLAGS="$TESTFLAGS --coverage=`
`coverage/${{github.job}}-${{matrix.arch}}.info"
echo "TESTFLAGS=$TESTFLAGS" >> $GITHUB_ENV
# cross-compile with ARM Thumb (32-bit, little-endian)
- name: install-thumb
if: ${{matrix.arch == 'thumb'}}
run: |
sudo apt-get install -qq \
gcc-arm-linux-gnueabi \
libc6-dev-armel-cross \
qemu-user
echo "CC=arm-linux-gnueabi-gcc -mthumb --static" >> $GITHUB_ENV
echo "EXEC=qemu-arm" >> $GITHUB_ENV
arm-linux-gnueabi-gcc --version
qemu-arm -version
# cross-compile with MIPS (32-bit, big-endian)
- name: install-mips
if: ${{matrix.arch == 'mips'}}
run: |
sudo apt-get install -qq \
gcc-mips-linux-gnu \
libc6-dev-mips-cross \
qemu-user
echo "CC=mips-linux-gnu-gcc --static" >> $GITHUB_ENV
echo "EXEC=qemu-mips" >> $GITHUB_ENV
mips-linux-gnu-gcc --version
qemu-mips -version
# cross-compile with PowerPC (32-bit, big-endian)
- name: install-powerpc
if: ${{matrix.arch == 'powerpc'}}
run: |
sudo apt-get install -qq \
gcc-powerpc-linux-gnu \
libc6-dev-powerpc-cross \
qemu-user
echo "CC=powerpc-linux-gnu-gcc --static" >> $GITHUB_ENV
echo "EXEC=qemu-ppc" >> $GITHUB_ENV
powerpc-linux-gnu-gcc --version
qemu-ppc -version
# make sure example can at least compile
- name: test-example
run: |
sed -n '/``` c/,/```/{/```/d; p}' README.md > test.c
make all CFLAGS+=" \
-Duser_provided_block_device_read=NULL \
-Duser_provided_block_device_prog=NULL \
-Duser_provided_block_device_erase=NULL \
-Duser_provided_block_device_sync=NULL \
-include stdio.h"
rm test.c
# test configurations
# normal+reentrant tests
- name: test-default
run: |
make clean
make test TESTFLAGS+="-nrk"
# NOR flash: read/prog = 1 block = 4KiB
- name: test-nor
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_READ_SIZE=1 -DLFS_BLOCK_SIZE=4096"
# SD/eMMC: read/prog = 512 block = 512
- name: test-emmc
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_READ_SIZE=512 -DLFS_BLOCK_SIZE=512"
# NAND flash: read/prog = 4KiB block = 32KiB
- name: test-nand
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_READ_SIZE=4096 -DLFS_BLOCK_SIZE=\(32*1024\)"
# other extreme geometries that are useful for various corner cases
- name: test-no-intrinsics
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_NO_INTRINSICS"
- name: test-byte-writes
# it just takes too long to test byte-level writes when in qemu,
# should be plenty covered by the other configurations
if: ${{matrix.arch == 'x86_64'}}
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_READ_SIZE=1 -DLFS_CACHE_SIZE=1"
- name: test-block-cycles
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_BLOCK_CYCLES=1"
- name: test-odd-block-count
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD_SIZE=256"
- name: test-odd-block-size
run: |
make clean
make test TESTFLAGS+="-nrk \
-DLFS_READ_SIZE=11 -DLFS_BLOCK_SIZE=704"
# upload coverage for later coverage
- name: upload-coverage
uses: actions/upload-artifact@v2
with:
name: coverage
path: coverage
retention-days: 1
# update results
- name: results-code
run: |
mkdir -p results
make clean
make code \
CFLAGS+=" \
-DLFS_NO_ASSERT \
-DLFS_NO_DEBUG \
-DLFS_NO_WARN \
-DLFS_NO_ERROR" \
CODEFLAGS+="-o results/code-${{matrix.arch}}.csv"
- name: results-code-readonly
run: |
mkdir -p results
make clean
make code \
CFLAGS+=" \
-DLFS_NO_ASSERT \
-DLFS_NO_DEBUG \
-DLFS_NO_WARN \
-DLFS_NO_ERROR \
-DLFS_READONLY" \
CODEFLAGS+="-o results/code-${{matrix.arch}}-readonly.csv"
- name: results-code-threadsafe
run: |
mkdir -p results
make clean
make code \
CFLAGS+=" \
-DLFS_NO_ASSERT \
-DLFS_NO_DEBUG \
-DLFS_NO_WARN \
-DLFS_NO_ERROR \
-DLFS_THREADSAFE" \
CODEFLAGS+="-o results/code-${{matrix.arch}}-threadsafe.csv"
- name: results-code-migrate
run: |
mkdir -p results
make clean
make code \
CFLAGS+=" \
-DLFS_NO_ASSERT \
-DLFS_NO_DEBUG \
-DLFS_NO_WARN \
-DLFS_NO_ERROR \
-DLFS_MIGRATE" \
CODEFLAGS+="-o results/code-${{matrix.arch}}-migrate.csv"
- name: results-code-error-asserts
run: |
mkdir -p results
make clean
make code \
CFLAGS+=" \
-DLFS_NO_DEBUG \
-DLFS_NO_WARN \
-DLFS_NO_ERROR \
-D'LFS_ASSERT(test)=do {if(!(test)) {return -1;}} while(0)'" \
CODEFLAGS+="-o results/code-${{matrix.arch}}-error-asserts.csv"
- name: upload-results
uses: actions/upload-artifact@v2
with:
name: results
path: results
# limit reporting to Thumb, otherwise there would be too many numbers
# flying around for the results to be easily readable
- name: collect-status
if: ${{matrix.arch == 'thumb'}}
run: |
mkdir -p status
for f in $(shopt -s nullglob ; echo results/code*.csv)
do
export STEP="results-code$(
echo $f | sed -n 's/.*code-.*-\(.*\).csv/-\1/p')"
export CONTEXT="results / code$(
echo $f | sed -n 's/.*code-.*-\(.*\).csv/ (\1)/p')"
export PREV="$(curl -sS \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/status/master" \
| jq -re 'select(.sha != env.GITHUB_SHA) | .statuses[]
| select(.context == env.CONTEXT).description
| capture("Code size is (?<result>[0-9]+)").result' \
|| echo 0)"
export DESCRIPTION="$(./scripts/code.py -u $f -s | awk '
NR==2 {printf "Code size is %d B",$2}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",100*($2-ENVIRON["PREV"])/ENVIRON["PREV"]}')"
jq -n '{
state: "success",
context: env.CONTEXT,
description: env.DESCRIPTION,
target_job: "${{github.job}} (${{matrix.arch}})",
target_step: env.STEP}' \
| tee status/code$(
echo $f | sed -n 's/.*code-.*-\(.*\).csv/-\1/p').json
done
- name: upload-status
if: ${{matrix.arch == 'thumb'}}
uses: actions/upload-artifact@v2
with:
name: status
path: status
retention-days: 1
# run under Valgrind to check for memory errors
valgrind:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: install
run: |
# need toml, also pip3 isn't installed by default?
sudo apt-get update -qq
sudo apt-get install -qq python3 python3-pip
sudo pip3 install toml
- name: install-valgrind
run: |
sudo apt-get update -qq
sudo apt-get install -qq valgrind
valgrind --version
# normal tests, we don't need to test all geometries
- name: test-valgrind
run: make test TESTFLAGS+="-k --valgrind"
# self-host with littlefs-fuse for a fuzz-like test
fuse:
runs-on: ubuntu-18.04
if: ${{!endsWith(github.ref, '-prefix')}}
steps:
- uses: actions/checkout@v2
- name: install
run: |
# need toml, also pip3 isn't installed by default?
sudo apt-get update -qq
sudo apt-get install -qq python3 python3-pip libfuse-dev
sudo pip3 install toml
fusermount -V
gcc --version
- uses: actions/checkout@v2
with:
repository: littlefs-project/littlefs-fuse
ref: v2
path: littlefs-fuse
- name: setup
run: |
# copy our new version into littlefs-fuse
rm -rf littlefs-fuse/littlefs/*
cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs
# setup disk for littlefs-fuse
mkdir mount
sudo chmod a+rw /dev/loop0
dd if=/dev/zero bs=512 count=128K of=disk
losetup /dev/loop0 disk
- name: test
run: |
# self-host test
make -C littlefs-fuse
littlefs-fuse/lfs --format /dev/loop0
littlefs-fuse/lfs /dev/loop0 mount
ls mount
mkdir mount/littlefs
cp -r $(git ls-tree --name-only HEAD) mount/littlefs
cd mount/littlefs
stat .
ls -flh
make -B test
# test migration using littlefs-fuse
migrate:
runs-on: ubuntu-18.04
if: ${{!endsWith(github.ref, '-prefix')}}
steps:
- uses: actions/checkout@v2
- name: install
run: |
# need toml, also pip3 isn't installed by default?
sudo apt-get update -qq
sudo apt-get install -qq python3 python3-pip libfuse-dev
sudo pip3 install toml
fusermount -V
gcc --version
- uses: actions/checkout@v2
with:
repository: littlefs-project/littlefs-fuse
ref: v2
path: v2
- uses: actions/checkout@v2
with:
repository: littlefs-project/littlefs-fuse
ref: v1
path: v1
- name: setup
run: |
# copy our new version into littlefs-fuse
rm -rf v2/littlefs/*
cp -r $(git ls-tree --name-only HEAD) v2/littlefs
# setup disk for littlefs-fuse
mkdir mount
sudo chmod a+rw /dev/loop0
dd if=/dev/zero bs=512 count=128K of=disk
losetup /dev/loop0 disk
- name: test
run: |
# compile v1 and v2
make -C v1
make -C v2
# run self-host test with v1
v1/lfs --format /dev/loop0
v1/lfs /dev/loop0 mount
ls mount
mkdir mount/littlefs
cp -r $(git ls-tree --name-only HEAD) mount/littlefs
cd mount/littlefs
stat .
ls -flh
make -B test
# attempt to migrate
cd ../..
fusermount -u mount
v2/lfs --migrate /dev/loop0
v2/lfs /dev/loop0 mount
# run self-host test with v2 right where we left off
ls mount
cd mount/littlefs
stat .
ls -flh
make -B test
# collect coverage info
coverage:
runs-on: ubuntu-18.04
needs: [test]
steps:
- uses: actions/checkout@v2
- name: install
run: |
sudo apt-get update -qq
sudo apt-get install -qq python3 python3-pip lcov
sudo pip3 install toml
# yes we continue-on-error nearly every step, continue-on-error
# at job level apparently still marks a job as failed, which isn't
# what we want
- uses: actions/download-artifact@v2
continue-on-error: true
with:
name: coverage
path: coverage
- name: results-coverage
continue-on-error: true
run: |
mkdir -p results
lcov $(for f in coverage/*.info ; do echo "-a $f" ; done) \
-o results/coverage.info
./scripts/coverage.py results/coverage.info -o results/coverage.csv
- name: upload-results
uses: actions/upload-artifact@v2
with:
name: results
path: results
- name: collect-status
run: |
mkdir -p status
[ -e results/coverage.csv ] || exit 0
export STEP="results-coverage"
export CONTEXT="results / coverage"
export PREV="$(curl -sS \
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/status/master" \
| jq -re 'select(.sha != env.GITHUB_SHA) | .statuses[]
| select(.context == env.CONTEXT).description
| capture("Coverage is (?<result>[0-9\\.]+)").result' \
|| echo 0)"
export DESCRIPTION="$(
./scripts/coverage.py -u results/coverage.csv -s | awk -F '[ /%]+' '
NR==2 {printf "Coverage is %.1f%% of %d lines",$4,$3}
NR==2 && ENVIRON["PREV"]+0 != 0 {
printf " (%+.1f%%)",$4-ENVIRON["PREV"]}')"
jq -n '{
state: "success",
context: env.CONTEXT,
description: env.DESCRIPTION,
target_job: "${{github.job}}",
target_step: env.STEP}' \
| tee status/coverage.json
- name: upload-status
uses: actions/upload-artifact@v2
with:
name: status
path: status
retention-days: 1

2
.gitignore vendored
View File

@@ -8,5 +8,3 @@ blocks/
lfs lfs
test.c test.c
tests/*.toml.* tests/*.toml.*
scripts/__pycache__
.gdb_history

429
.travis.yml Normal file
View File

@@ -0,0 +1,429 @@
# environment variables
env:
global:
- CFLAGS=-Werror
- MAKEFLAGS=-j
# cache installation dirs
cache:
pip: true
directories:
- $HOME/.cache/apt
# common installation
_: &install-common
# need toml, also pip3 isn't installed by default?
- sudo apt-get install python3 python3-pip
- sudo pip3 install toml
# setup a ram-backed disk to speed up reentrant tests
- mkdir disks
- sudo mount -t tmpfs -o size=100m tmpfs disks
- export TFLAGS="$TFLAGS --disk=disks/disk"
# test cases
_: &test-example
# make sure example can at least compile
- sed -n '/``` c/,/```/{/```/d; p}' README.md > test.c &&
make all CFLAGS+="
-Duser_provided_block_device_read=NULL
-Duser_provided_block_device_prog=NULL
-Duser_provided_block_device_erase=NULL
-Duser_provided_block_device_sync=NULL
-include stdio.h"
# default tests
_: &test-default
# normal+reentrant tests
- make test TFLAGS+="-nrk"
# common real-life geometries
_: &test-nor
# NOR flash: read/prog = 1 block = 4KiB
- make test TFLAGS+="-nrk -DLFS_READ_SIZE=1 -DLFS_BLOCK_SIZE=4096"
_: &test-emmc
# eMMC: read/prog = 512 block = 512
- make test TFLAGS+="-nrk -DLFS_READ_SIZE=512 -DLFS_BLOCK_SIZE=512"
_: &test-nand
# NAND flash: read/prog = 4KiB block = 32KiB
- make test TFLAGS+="-nrk -DLFS_READ_SIZE=4096 -DLFS_BLOCK_SIZE=\(32*1024\)"
# other extreme geometries that are useful for testing various corner cases
_: &test-no-intrinsics
- make test TFLAGS+="-nrk -DLFS_NO_INTRINSICS"
_: &test-no-inline
- make test TFLAGS+="-nrk -DLFS_INLINE_MAX=0"
_: &test-byte-writes
- make test TFLAGS+="-nrk -DLFS_READ_SIZE=1 -DLFS_CACHE_SIZE=1"
_: &test-block-cycles
- make test TFLAGS+="-nrk -DLFS_BLOCK_CYCLES=1"
_: &test-odd-block-count
- make test TFLAGS+="-nrk -DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD_SIZE=256"
_: &test-odd-block-size
- make test TFLAGS+="-nrk -DLFS_READ_SIZE=11 -DLFS_BLOCK_SIZE=704"
# report size
_: &report-size
# compile and find the code size with the smallest configuration
- make -j1 clean size
OBJ="$(ls lfs*.c | sed 's/\.c/\.o/' | tr '\n' ' ')"
CFLAGS+="-DLFS_NO_ASSERT -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR"
| tee sizes
# update status if we succeeded, compare with master if possible
- |
if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
then
CURR=$(tail -n1 sizes | awk '{print $1}')
PREV=$(curl -u "$GEKY_BOT_STATUSES" https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
| jq -re "select(.sha != \"$TRAVIS_COMMIT\")
| .statuses[] | select(.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\").description
| capture(\"code size is (?<size>[0-9]+)\").size" \
|| echo 0)
STATUS="Passed, code size is ${CURR}B"
if [ "$PREV" -ne 0 ]
then
STATUS="$STATUS ($(python -c "print '%+.2f' % (100*($CURR-$PREV)/$PREV.0)")%)"
fi
fi
# stage control
stages:
- name: test
- name: deploy
if: branch = master AND type = push
# job control
jobs:
# native testing
- &x86
stage: test
env:
- NAME=littlefs-x86
install: *install-common
script: [*test-example, *report-size]
- {<<: *x86, script: [*test-default, *report-size]}
- {<<: *x86, script: [*test-nor, *report-size]}
- {<<: *x86, script: [*test-emmc, *report-size]}
- {<<: *x86, script: [*test-nand, *report-size]}
- {<<: *x86, script: [*test-no-intrinsics, *report-size]}
- {<<: *x86, script: [*test-no-inline, *report-size]}
- {<<: *x86, script: [*test-byte-writes, *report-size]}
- {<<: *x86, script: [*test-block-cycles, *report-size]}
- {<<: *x86, script: [*test-odd-block-count, *report-size]}
- {<<: *x86, script: [*test-odd-block-size, *report-size]}
# cross-compile with ARM (thumb mode)
- &arm
stage: test
env:
- NAME=littlefs-arm
- CC="arm-linux-gnueabi-gcc --static -mthumb"
- TFLAGS="$TFLAGS --exec=qemu-arm"
install:
- *install-common
- sudo apt-get install
gcc-arm-linux-gnueabi
libc6-dev-armel-cross
qemu-user
- arm-linux-gnueabi-gcc --version
- qemu-arm -version
script: [*test-example, *report-size]
- {<<: *arm, script: [*test-default, *report-size]}
- {<<: *arm, script: [*test-nor, *report-size]}
- {<<: *arm, script: [*test-emmc, *report-size]}
- {<<: *arm, script: [*test-nand, *report-size]}
- {<<: *arm, script: [*test-no-intrinsics, *report-size]}
- {<<: *arm, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *arm, script: [*test-byte-writes, *report-size]}
- {<<: *arm, script: [*test-block-cycles, *report-size]}
- {<<: *arm, script: [*test-odd-block-count, *report-size]}
- {<<: *arm, script: [*test-odd-block-size, *report-size]}
# cross-compile with MIPS
- &mips
stage: test
env:
- NAME=littlefs-mips
- CC="mips-linux-gnu-gcc --static"
- TFLAGS="$TFLAGS --exec=qemu-mips"
install:
- *install-common
- sudo apt-get install
gcc-mips-linux-gnu
libc6-dev-mips-cross
qemu-user
- mips-linux-gnu-gcc --version
- qemu-mips -version
script: [*test-example, *report-size]
- {<<: *mips, script: [*test-default, *report-size]}
- {<<: *mips, script: [*test-nor, *report-size]}
- {<<: *mips, script: [*test-emmc, *report-size]}
- {<<: *mips, script: [*test-nand, *report-size]}
- {<<: *mips, script: [*test-no-intrinsics, *report-size]}
- {<<: *mips, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *mips, script: [*test-byte-writes, *report-size]}
- {<<: *mips, script: [*test-block-cycles, *report-size]}
- {<<: *mips, script: [*test-odd-block-count, *report-size]}
- {<<: *mips, script: [*test-odd-block-size, *report-size]}
# cross-compile with PowerPC
- &powerpc
stage: test
env:
- NAME=littlefs-powerpc
- CC="powerpc-linux-gnu-gcc --static"
- TFLAGS="$TFLAGS --exec=qemu-ppc"
install:
- *install-common
- sudo apt-get install
gcc-powerpc-linux-gnu
libc6-dev-powerpc-cross
qemu-user
- powerpc-linux-gnu-gcc --version
- qemu-ppc -version
script: [*test-example, *report-size]
- {<<: *powerpc, script: [*test-default, *report-size]}
- {<<: *powerpc, script: [*test-nor, *report-size]}
- {<<: *powerpc, script: [*test-emmc, *report-size]}
- {<<: *powerpc, script: [*test-nand, *report-size]}
- {<<: *powerpc, script: [*test-no-intrinsics, *report-size]}
- {<<: *powerpc, script: [*test-no-inline, *report-size]}
# it just takes way to long to run byte-level writes in qemu,
# note this is still tested in the native tests
#- {<<: *powerpc, script: [*test-byte-writes, *report-size]}
- {<<: *powerpc, script: [*test-block-cycles, *report-size]}
- {<<: *powerpc, script: [*test-odd-block-count, *report-size]}
- {<<: *powerpc, script: [*test-odd-block-size, *report-size]}
# test under valgrind, checking for memory errors
- &valgrind
stage: test
env:
- NAME=littlefs-valgrind
install:
- *install-common
- sudo apt-get install valgrind
- valgrind --version
script:
- make test TFLAGS+="-k --valgrind"
# self-host with littlefs-fuse for fuzz test
- stage: test
env:
- NAME=littlefs-fuse
if: branch !~ -prefix$
install:
- *install-common
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2
- fusermount -V
- gcc --version
# setup disk for littlefs-fuse
- rm -rf littlefs-fuse/littlefs/*
- cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs
- mkdir mount
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=128K of=disk
- losetup /dev/loop0 disk
script:
# self-host test
- make -C littlefs-fuse
- littlefs-fuse/lfs --format /dev/loop0
- littlefs-fuse/lfs /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# test migration using littlefs-fuse
- stage: test
env:
- NAME=littlefs-migration
if: branch !~ -prefix$
install:
- *install-common
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 v2
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1
- fusermount -V
- gcc --version
# setup disk for littlefs-fuse
- rm -rf v2/littlefs/*
- cp -r $(git ls-tree --name-only HEAD) v2/littlefs
- mkdir mount
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=128K of=disk
- losetup /dev/loop0 disk
script:
# compile v1 and v2
- make -C v1
- make -C v2
# run self-host test with v1
- v1/lfs --format /dev/loop0
- v1/lfs /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# attempt to migrate
- cd ../..
- fusermount -u mount
- v2/lfs --migrate /dev/loop0
- v2/lfs /dev/loop0 mount
# run self-host test with v2 right where we left off
- ls mount
- cd mount/littlefs
- stat .
- ls -flh
- make -B test
# automatically create releases
- stage: deploy
env:
- NAME=deploy
script:
- |
bash << 'SCRIPT'
set -ev
# Find version defined in lfs.h
LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' lfs.h | cut -d ' ' -f3)
LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16)))
LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0)))
# Grab latests patch from repo tags, default to 0, needs finagling
# to get past github's pagination api
PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.
PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I \
| sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' \
|| echo $PREV_URL)
LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" \
| jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g")
.captures[].string | tonumber) | max + 1' \
|| echo 0)
# We have our new version
LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH"
echo "VERSION $LFS_VERSION"
# Check that we're the most recent commit
CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \
| jq -re '.sha')
[ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0
# Create major branch
git branch v$LFS_VERSION_MAJOR HEAD
# Create major prefix branch
git config user.name "geky bot"
git config user.email "bot@geky.net"
git fetch https://github.com/$TRAVIS_REPO_SLUG.git \
--depth=50 v$LFS_VERSION_MAJOR-prefix || true
./scripts/prefix.py lfs$LFS_VERSION_MAJOR
git branch v$LFS_VERSION_MAJOR-prefix $( \
git commit-tree $(git write-tree) \
$(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \
-p HEAD \
-m "Generated v$LFS_VERSION_MAJOR prefixes")
git reset --hard
# Update major version branches (vN and vN-prefix)
git push --atomic https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \
v$LFS_VERSION_MAJOR \
v$LFS_VERSION_MAJOR-prefix
# Build release notes
PREV=$(git tag --sort=-v:refname -l "v*" | head -1)
if [ ! -z "$PREV" ]
then
echo "PREV $PREV"
CHANGES=$(git log --oneline $PREV.. --grep='^Merge' --invert-grep)
printf "CHANGES\n%s\n\n" "$CHANGES"
fi
case ${GEKY_BOT_DRAFT:-minor} in
true) DRAFT=true ;;
minor) DRAFT=$(jq -R 'endswith(".0")' <<< "$LFS_VERSION") ;;
false) DRAFT=false ;;
esac
# Create the release and patch version tag (vN.N.N)
curl -f -u "$GEKY_BOT_RELEASES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
-d "{
\"tag_name\": \"$LFS_VERSION\",
\"name\": \"${LFS_VERSION%.0}\",
\"target_commitish\": \"$TRAVIS_COMMIT\",
\"draft\": $DRAFT,
\"body\": $(jq -sR '.' <<< "$CHANGES")
}" #"
SCRIPT
# manage statuses
before_install:
- |
# don't clobber other (not us) failures
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
.state == \"failure\" and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"pending\",
\"description\": \"${STATUS:-In progress}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi
after_failure:
- |
# don't clobber other (not us) failures
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
.state == \"failure\" and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"failure\",
\"description\": \"${STATUS:-Failed}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi
after_success:
- |
# don't clobber other (not us) failures
# only update if we were last job to mark in progress,
# this isn't perfect but is probably good enough
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
| jq -e ".statuses[] | select(
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
(.state == \"failure\" or .state == \"pending\") and
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
then
curl -u "$GEKY_BOT_STATUSES" -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
\"state\": \"success\",
\"description\": \"${STATUS:-Passed}\",
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
}"
fi

View File

@@ -1,114 +1,69 @@
ifdef BUILDDIR TARGET = lfs.a
# make sure BUILDDIR ends with a slash
override BUILDDIR := $(BUILDDIR)/
# bit of a hack, but we want to make sure BUILDDIR directory structure
# is correct before any commands
$(if $(findstring n,$(MAKEFLAGS)),, $(shell mkdir -p \
$(BUILDDIR) \
$(BUILDDIR)bd \
$(BUILDDIR)tests))
endif
# overridable target/src/tools/flags/etc
ifneq ($(wildcard test.c main.c),) ifneq ($(wildcard test.c main.c),)
TARGET ?= $(BUILDDIR)lfs override TARGET = lfs
else
TARGET ?= $(BUILDDIR)lfs.a
endif endif
CC ?= gcc CC ?= gcc
AR ?= ar AR ?= ar
SIZE ?= size SIZE ?= size
CTAGS ?= ctags
NM ?= nm
LCOV ?= lcov
SRC ?= $(wildcard *.c) SRC += $(wildcard *.c bd/*.c)
OBJ := $(SRC:%.c=$(BUILDDIR)%.o) OBJ := $(SRC:.c=.o)
DEP := $(SRC:%.c=$(BUILDDIR)%.d) DEP := $(SRC:.c=.d)
ASM := $(SRC:%.c=$(BUILDDIR)%.s) ASM := $(SRC:.c=.s)
ifdef DEBUG ifdef DEBUG
override CFLAGS += -O0 -g3 override CFLAGS += -O0 -g3
else else
override CFLAGS += -Os override CFLAGS += -Os
endif endif
ifdef WORD
override CFLAGS += -m$(WORD)
endif
ifdef TRACE ifdef TRACE
override CFLAGS += -DLFS_YES_TRACE override CFLAGS += -DLFS_YES_TRACE
endif endif
override CFLAGS += -I. override CFLAGS += -I.
override CFLAGS += -std=c99 -Wall -pedantic override CFLAGS += -std=c99 -Wall -pedantic
override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
# Remove missing-field-initializers because of GCC bug
override CFLAGS += -Wno-missing-field-initializers
ifdef VERBOSE ifdef VERBOSE
override TESTFLAGS += -v override TFLAGS += -v
override CODEFLAGS += -v
override COVERAGEFLAGS += -v
endif
ifdef EXEC
override TESTFLAGS += --exec="$(EXEC)"
endif
ifdef BUILDDIR
override TESTFLAGS += --build-dir="$(BUILDDIR:/=)"
override CODEFLAGS += --build-dir="$(BUILDDIR:/=)"
endif
ifneq ($(NM),nm)
override CODEFLAGS += --nm-tool="$(NM)"
endif endif
# commands all: $(TARGET)
.PHONY: all build
all build: $(TARGET)
.PHONY: asm
asm: $(ASM) asm: $(ASM)
.PHONY: size
size: $(OBJ) size: $(OBJ)
$(SIZE) -t $^ $(SIZE) -t $^
.PHONY: tags
tags:
$(CTAGS) --totals --c-types=+p $(shell find -H -name '*.h') $(SRC)
.PHONY: code
code: $(OBJ)
./scripts/code.py $^ $(CODEFLAGS)
.PHONY: test
test: test:
./scripts/test.py $(TESTFLAGS) ./scripts/test.py $(TFLAGS)
.SECONDEXPANSION: .SECONDEXPANSION:
test%: tests/test$$(firstword $$(subst \#, ,%)).toml test%: tests/test$$(firstword $$(subst \#, ,%)).toml
./scripts/test.py $@ $(TESTFLAGS) ./scripts/test.py $@ $(TFLAGS)
.PHONY: coverage
coverage:
./scripts/coverage.py $(BUILDDIR)tests/*.toml.info $(COVERAGEFLAGS)
# rules
-include $(DEP) -include $(DEP)
.SUFFIXES:
$(BUILDDIR)lfs: $(OBJ) lfs: $(OBJ)
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
$(BUILDDIR)%.a: $(OBJ) %.a: $(OBJ)
$(AR) rcs $@ $^ $(AR) rcs $@ $^
$(BUILDDIR)%.o: %.c %.o: %.c
$(CC) -c -MMD $(CFLAGS) $< -o $@ $(CC) -c -MMD $(CFLAGS) $< -o $@
$(BUILDDIR)%.s: %.c %.s: %.c
$(CC) -S $(CFLAGS) $< -o $@ $(CC) -S $(CFLAGS) $< -o $@
# clean everything
.PHONY: clean
clean: clean:
rm -f $(TARGET) rm -f $(TARGET)
rm -f $(OBJ) rm -f $(OBJ)
rm -f $(DEP) rm -f $(DEP)
rm -f $(ASM) rm -f $(ASM)
rm -f $(BUILDDIR)tests/*.toml.* rm -f tests/*.toml.*

View File

@@ -115,9 +115,6 @@ the filesystem until sync or close is called on the file.
## Other notes ## Other notes
Littlefs is written in C, and specifically should compile with any compiler
that conforms to the `C99` standard.
All littlefs calls have the potential to return a negative error code. The All littlefs calls have the potential to return a negative error code. The
errors can be either one of those found in the `enum lfs_error` in errors can be either one of those found in the `enum lfs_error` in
[lfs.h](lfs.h), or an error returned by the user's block device operations. [lfs.h](lfs.h), or an error returned by the user's block device operations.
@@ -221,11 +218,6 @@ License Identifiers that are here available: http://spdx.org/licenses/
- [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would - [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would
want this, but it is handy for demos. You can see it in action want this, but it is handy for demos. You can see it in action
[here][littlefs-js-demo]. [here][littlefs-js-demo].
- [littlefs-python] - A Python wrapper for littlefs. The project allows you
to create images of the filesystem on your PC. Check if littlefs will fit
your needs, create images for a later download to the target memory or
inspect the content of a binary image of the target memory.
- [mklfs] - A command line tool built by the [Lua RTOS] guys for making - [mklfs] - A command line tool built by the [Lua RTOS] guys for making
littlefs images from a host PC. Supports Windows, Mac OS, and Linux. littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
@@ -255,4 +247,3 @@ License Identifiers that are here available: http://spdx.org/licenses/
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html
[SPIFFS]: https://github.com/pellepl/spiffs [SPIFFS]: https://github.com/pellepl/spiffs
[Dhara]: https://github.com/dlbeer/dhara [Dhara]: https://github.com/dlbeer/dhara
[littlefs-python]: https://pypi.org/project/littlefs-python/

18
SPEC.md
View File

@@ -289,8 +289,8 @@ Layout of the name tag:
``` ```
tag data tag data
[-- 32 --][--- variable length ---] [-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] [1| 3| 8 | 10 | 10 ][--- (size) ---]
^ ^ ^ ^ ^- size ^- file name ^ ^ ^ ^ ^- size ^- file name
| | | '------ id | | | '------ id
| | '----------- file type | | '----------- file type
| '-------------- type1 (0x0) | '-------------- type1 (0x0)
@@ -470,8 +470,8 @@ Layout of the inline-struct tag:
``` ```
tag data tag data
[-- 32 --][--- variable length ---] [-- 32 --][--- variable length ---]
[1|- 11 -| 10 | 10 ][--- (size * 8) ---] [1|- 11 -| 10 | 10 ][--- (size) ---]
^ ^ ^ ^- size ^- inline data ^ ^ ^ ^- size ^- inline data
| | '------ id | | '------ id
| '------------ type (0x201) | '------------ type (0x201)
'----------------- valid bit '----------------- valid bit
@@ -556,8 +556,8 @@ Layout of the user-attr tag:
``` ```
tag data tag data
[-- 32 --][--- variable length ---] [-- 32 --][--- variable length ---]
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---] [1| 3| 8 | 10 | 10 ][--- (size) ---]
^ ^ ^ ^ ^- size ^- attr data ^ ^ ^ ^ ^- size ^- attr data
| | | '------ id | | | '------ id
| | '----------- attr type | | '----------- attr type
| '-------------- type1 (0x3) | '-------------- type1 (0x3)
@@ -764,9 +764,9 @@ Layout of the CRC tag:
``` ```
tag data tag data
[-- 32 --][-- 32 --|--- variable length ---] [-- 32 --][-- 32 --|--- variable length ---]
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---] [1| 3| 8 | 10 | 10 ][-- 32 --|--- (size) ---]
^ ^ ^ ^ ^ ^- crc ^- padding ^ ^ ^ ^ ^ ^- crc ^- padding
| | | | '- size | | | | '- size (12)
| | | '------ id (0x3ff) | | | '------ id (0x3ff)
| | '----------- valid state | | '----------- valid state
| '-------------- type1 (0x5) | '-------------- type1 (0x5)

View File

@@ -12,7 +12,7 @@
int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path, int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path,
const struct lfs_filebd_config *bdcfg) { const struct lfs_filebd_config *bdcfg) {
LFS_FILEBD_TRACE("lfs_filebd_createcfg(%p {.context=%p, " LFS_TRACE("lfs_filebd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, " ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -30,16 +30,16 @@ int lfs_filebd_createcfg(const struct lfs_config *cfg, const char *path,
bd->fd = open(path, O_RDWR | O_CREAT, 0666); bd->fd = open(path, O_RDWR | O_CREAT, 0666);
if (bd->fd < 0) { if (bd->fd < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", err); LFS_TRACE("lfs_filebd_createcfg -> %d", err);
return err; return err;
} }
LFS_FILEBD_TRACE("lfs_filebd_createcfg -> %d", 0); LFS_TRACE("lfs_filebd_createcfg -> %d", 0);
return 0; return 0;
} }
int lfs_filebd_create(const struct lfs_config *cfg, const char *path) { int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
LFS_FILEBD_TRACE("lfs_filebd_create(%p {.context=%p, " LFS_TRACE("lfs_filebd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, " ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -51,27 +51,26 @@ int lfs_filebd_create(const struct lfs_config *cfg, const char *path) {
path); path);
static const struct lfs_filebd_config defaults = {.erase_value=-1}; static const struct lfs_filebd_config defaults = {.erase_value=-1};
int err = lfs_filebd_createcfg(cfg, path, &defaults); int err = lfs_filebd_createcfg(cfg, path, &defaults);
LFS_FILEBD_TRACE("lfs_filebd_create -> %d", err); LFS_TRACE("lfs_filebd_create -> %d", err);
return err; return err;
} }
int lfs_filebd_destroy(const struct lfs_config *cfg) { int lfs_filebd_destroy(const struct lfs_config *cfg) {
LFS_FILEBD_TRACE("lfs_filebd_destroy(%p)", (void*)cfg); LFS_TRACE("lfs_filebd_destroy(%p)", (void*)cfg);
lfs_filebd_t *bd = cfg->context; lfs_filebd_t *bd = cfg->context;
int err = close(bd->fd); int err = close(bd->fd);
if (err < 0) { if (err < 0) {
err = -errno; err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", err); LFS_TRACE("lfs_filebd_destroy -> %d", err);
return err; return err;
} }
LFS_FILEBD_TRACE("lfs_filebd_destroy -> %d", 0); LFS_TRACE("lfs_filebd_destroy -> %d", 0);
return 0; return 0;
} }
int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block, int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) { lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_FILEBD_TRACE("lfs_filebd_read(%p, " LFS_TRACE("lfs_filebd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_filebd_t *bd = cfg->context; lfs_filebd_t *bd = cfg->context;
@@ -90,24 +89,24 @@ int lfs_filebd_read(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET); (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) { if (res1 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); LFS_TRACE("lfs_filebd_read -> %d", err);
return err; return err;
} }
ssize_t res2 = read(bd->fd, buffer, size); ssize_t res2 = read(bd->fd, buffer, size);
if (res2 < 0) { if (res2 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", err); LFS_TRACE("lfs_filebd_read -> %d", err);
return err; return err;
} }
LFS_FILEBD_TRACE("lfs_filebd_read -> %d", 0); LFS_TRACE("lfs_filebd_read -> %d", 0);
return 0; return 0;
} }
int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block, int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) { lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_FILEBD_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")", LFS_TRACE("lfs_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_filebd_t *bd = cfg->context; lfs_filebd_t *bd = cfg->context;
@@ -122,7 +121,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET); (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) { if (res1 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); LFS_TRACE("lfs_filebd_prog -> %d", err);
return err; return err;
} }
@@ -131,7 +130,7 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
ssize_t res2 = read(bd->fd, &c, 1); ssize_t res2 = read(bd->fd, &c, 1);
if (res2 < 0) { if (res2 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); LFS_TRACE("lfs_filebd_prog -> %d", err);
return err; return err;
} }
@@ -144,23 +143,23 @@ int lfs_filebd_prog(const struct lfs_config *cfg, lfs_block_t block,
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET); (off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
if (res1 < 0) { if (res1 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); LFS_TRACE("lfs_filebd_prog -> %d", err);
return err; return err;
} }
ssize_t res2 = write(bd->fd, buffer, size); ssize_t res2 = write(bd->fd, buffer, size);
if (res2 < 0) { if (res2 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", err); LFS_TRACE("lfs_filebd_prog -> %d", err);
return err; return err;
} }
LFS_FILEBD_TRACE("lfs_filebd_prog -> %d", 0); LFS_TRACE("lfs_filebd_prog -> %d", 0);
return 0; return 0;
} }
int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) { int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_FILEBD_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); LFS_TRACE("lfs_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_filebd_t *bd = cfg->context; lfs_filebd_t *bd = cfg->context;
// check if erase is valid // check if erase is valid
@@ -171,7 +170,7 @@ int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET); off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET);
if (res1 < 0) { if (res1 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err); LFS_TRACE("lfs_filebd_erase -> %d", err);
return err; return err;
} }
@@ -179,27 +178,27 @@ int lfs_filebd_erase(const struct lfs_config *cfg, lfs_block_t block) {
ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1); ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1);
if (res2 < 0) { if (res2 < 0) {
int err = -errno; int err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", err); LFS_TRACE("lfs_filebd_erase -> %d", err);
return err; return err;
} }
} }
} }
LFS_FILEBD_TRACE("lfs_filebd_erase -> %d", 0); LFS_TRACE("lfs_filebd_erase -> %d", 0);
return 0; return 0;
} }
int lfs_filebd_sync(const struct lfs_config *cfg) { int lfs_filebd_sync(const struct lfs_config *cfg) {
LFS_FILEBD_TRACE("lfs_filebd_sync(%p)", (void*)cfg); LFS_TRACE("lfs_filebd_sync(%p)", (void*)cfg);
// file sync // file sync
lfs_filebd_t *bd = cfg->context; lfs_filebd_t *bd = cfg->context;
int err = fsync(bd->fd); int err = fsync(bd->fd);
if (err) { if (err) {
err = -errno; err = -errno;
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); LFS_TRACE("lfs_filebd_sync -> %d", 0);
return err; return err;
} }
LFS_FILEBD_TRACE("lfs_filebd_sync -> %d", 0); LFS_TRACE("lfs_filebd_sync -> %d", 0);
return 0; return 0;
} }

View File

@@ -15,14 +15,6 @@ extern "C"
{ {
#endif #endif
// Block device specific tracing
#ifdef LFS_FILEBD_YES_TRACE
#define LFS_FILEBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_FILEBD_TRACE(...)
#endif
// filebd config (optional) // filebd config (optional)
struct lfs_filebd_config { struct lfs_filebd_config {
// 8-bit erase value to use for simulating erases. -1 does not simulate // 8-bit erase value to use for simulating erases. -1 does not simulate

View File

@@ -8,7 +8,7 @@
int lfs_rambd_createcfg(const struct lfs_config *cfg, int lfs_rambd_createcfg(const struct lfs_config *cfg,
const struct lfs_rambd_config *bdcfg) { const struct lfs_rambd_config *bdcfg) {
LFS_RAMBD_TRACE("lfs_rambd_createcfg(%p {.context=%p, " LFS_TRACE("lfs_rambd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, " ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -27,7 +27,7 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
} else { } else {
bd->buffer = lfs_malloc(cfg->block_size * cfg->block_count); bd->buffer = lfs_malloc(cfg->block_size * cfg->block_count);
if (!bd->buffer) { if (!bd->buffer) {
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM); LFS_TRACE("lfs_rambd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
} }
@@ -38,12 +38,12 @@ int lfs_rambd_createcfg(const struct lfs_config *cfg,
cfg->block_size * cfg->block_count); cfg->block_size * cfg->block_count);
} }
LFS_RAMBD_TRACE("lfs_rambd_createcfg -> %d", 0); LFS_TRACE("lfs_rambd_createcfg -> %d", 0);
return 0; return 0;
} }
int lfs_rambd_create(const struct lfs_config *cfg) { int lfs_rambd_create(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_create(%p {.context=%p, " LFS_TRACE("lfs_rambd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"})", ".block_size=%"PRIu32", .block_count=%"PRIu32"})",
@@ -53,25 +53,24 @@ int lfs_rambd_create(const struct lfs_config *cfg) {
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count); cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count);
static const struct lfs_rambd_config defaults = {.erase_value=-1}; static const struct lfs_rambd_config defaults = {.erase_value=-1};
int err = lfs_rambd_createcfg(cfg, &defaults); int err = lfs_rambd_createcfg(cfg, &defaults);
LFS_RAMBD_TRACE("lfs_rambd_create -> %d", err); LFS_TRACE("lfs_rambd_create -> %d", err);
return err; return err;
} }
int lfs_rambd_destroy(const struct lfs_config *cfg) { int lfs_rambd_destroy(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_destroy(%p)", (void*)cfg); LFS_TRACE("lfs_rambd_destroy(%p)", (void*)cfg);
// clean up memory // clean up memory
lfs_rambd_t *bd = cfg->context; lfs_rambd_t *bd = cfg->context;
if (!bd->cfg->buffer) { if (!bd->cfg->buffer) {
lfs_free(bd->buffer); lfs_free(bd->buffer);
} }
LFS_RAMBD_TRACE("lfs_rambd_destroy -> %d", 0); LFS_TRACE("lfs_rambd_destroy -> %d", 0);
return 0; return 0;
} }
int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block, int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) { lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_RAMBD_TRACE("lfs_rambd_read(%p, " LFS_TRACE("lfs_rambd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_rambd_t *bd = cfg->context; lfs_rambd_t *bd = cfg->context;
@@ -83,14 +82,13 @@ int lfs_rambd_read(const struct lfs_config *cfg, lfs_block_t block,
// read data // read data
memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size); memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size);
LFS_RAMBD_TRACE("lfs_rambd_read -> %d", 0); LFS_TRACE("lfs_rambd_read -> %d", 0);
return 0; return 0;
} }
int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block, int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) { lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_RAMBD_TRACE("lfs_rambd_prog(%p, " LFS_TRACE("lfs_rambd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_rambd_t *bd = cfg->context; lfs_rambd_t *bd = cfg->context;
@@ -110,12 +108,12 @@ int lfs_rambd_prog(const struct lfs_config *cfg, lfs_block_t block,
// program data // program data
memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size); memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size);
LFS_RAMBD_TRACE("lfs_rambd_prog -> %d", 0); LFS_TRACE("lfs_rambd_prog -> %d", 0);
return 0; return 0;
} }
int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) { int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_RAMBD_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); LFS_TRACE("lfs_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_rambd_t *bd = cfg->context; lfs_rambd_t *bd = cfg->context;
// check if erase is valid // check if erase is valid
@@ -127,14 +125,14 @@ int lfs_rambd_erase(const struct lfs_config *cfg, lfs_block_t block) {
bd->cfg->erase_value, cfg->block_size); bd->cfg->erase_value, cfg->block_size);
} }
LFS_RAMBD_TRACE("lfs_rambd_erase -> %d", 0); LFS_TRACE("lfs_rambd_erase -> %d", 0);
return 0; return 0;
} }
int lfs_rambd_sync(const struct lfs_config *cfg) { int lfs_rambd_sync(const struct lfs_config *cfg) {
LFS_RAMBD_TRACE("lfs_rambd_sync(%p)", (void*)cfg); LFS_TRACE("lfs_rambd_sync(%p)", (void*)cfg);
// sync does nothing because we aren't backed by anything real // sync does nothing because we aren't backed by anything real
(void)cfg; (void)cfg;
LFS_RAMBD_TRACE("lfs_rambd_sync -> %d", 0); LFS_TRACE("lfs_rambd_sync -> %d", 0);
return 0; return 0;
} }

View File

@@ -15,14 +15,6 @@ extern "C"
{ {
#endif #endif
// Block device specific tracing
#ifdef LFS_RAMBD_YES_TRACE
#define LFS_RAMBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_RAMBD_TRACE(...)
#endif
// rambd config (optional) // rambd config (optional)
struct lfs_rambd_config { struct lfs_rambd_config {
// 8-bit erase value to simulate erasing with. -1 indicates no erase // 8-bit erase value to simulate erasing with. -1 indicates no erase

View File

@@ -12,7 +12,7 @@
int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
const struct lfs_testbd_config *bdcfg) { const struct lfs_testbd_config *bdcfg) {
LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, " LFS_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, " ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -38,9 +38,9 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
if (bd->cfg->wear_buffer) { if (bd->cfg->wear_buffer) {
bd->wear = bd->cfg->wear_buffer; bd->wear = bd->cfg->wear_buffer;
} else { } else {
bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count); bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t) * cfg->block_count);
if (!bd->wear) { if (!bd->wear) {
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM); LFS_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
} }
@@ -54,7 +54,7 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
.erase_value = bd->cfg->erase_value, .erase_value = bd->cfg->erase_value,
}; };
int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg); int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg);
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); LFS_TRACE("lfs_testbd_createcfg -> %d", err);
return err; return err;
} else { } else {
bd->u.ram.cfg = (struct lfs_rambd_config){ bd->u.ram.cfg = (struct lfs_rambd_config){
@@ -62,13 +62,13 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
.buffer = bd->cfg->buffer, .buffer = bd->cfg->buffer,
}; };
int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg); int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg);
LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err); LFS_TRACE("lfs_testbd_createcfg -> %d", err);
return err; return err;
} }
} }
int lfs_testbd_create(const struct lfs_config *cfg, const char *path) { int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
LFS_TESTBD_TRACE("lfs_testbd_create(%p {.context=%p, " LFS_TRACE("lfs_testbd_create(%p {.context=%p, "
".read=%p, .prog=%p, .erase=%p, .sync=%p, " ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
".read_size=%"PRIu32", .prog_size=%"PRIu32", " ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
".block_size=%"PRIu32", .block_count=%"PRIu32"}, " ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
@@ -80,12 +80,12 @@ int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
path); path);
static const struct lfs_testbd_config defaults = {.erase_value=-1}; static const struct lfs_testbd_config defaults = {.erase_value=-1};
int err = lfs_testbd_createcfg(cfg, path, &defaults); int err = lfs_testbd_createcfg(cfg, path, &defaults);
LFS_TESTBD_TRACE("lfs_testbd_create -> %d", err); LFS_TRACE("lfs_testbd_create -> %d", err);
return err; return err;
} }
int lfs_testbd_destroy(const struct lfs_config *cfg) { int lfs_testbd_destroy(const struct lfs_config *cfg) {
LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg); LFS_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) { if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
lfs_free(bd->wear); lfs_free(bd->wear);
@@ -93,11 +93,11 @@ int lfs_testbd_destroy(const struct lfs_config *cfg) {
if (bd->persist) { if (bd->persist) {
int err = lfs_filebd_destroy(cfg); int err = lfs_filebd_destroy(cfg);
LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); LFS_TRACE("lfs_testbd_destroy -> %d", err);
return err; return err;
} else { } else {
int err = lfs_rambd_destroy(cfg); int err = lfs_rambd_destroy(cfg);
LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err); LFS_TRACE("lfs_testbd_destroy -> %d", err);
return err; return err;
} }
} }
@@ -145,8 +145,7 @@ static int lfs_testbd_rawsync(const struct lfs_config *cfg) {
/// block device API /// /// block device API ///
int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) { lfs_off_t off, void *buffer, lfs_size_t size) {
LFS_TESTBD_TRACE("lfs_testbd_read(%p, " LFS_TRACE("lfs_testbd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
@@ -158,20 +157,19 @@ int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
// block bad? // block bad?
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles && if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) { bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) {
LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT; return LFS_ERR_CORRUPT;
} }
// read // read
int err = lfs_testbd_rawread(cfg, block, off, buffer, size); int err = lfs_testbd_rawread(cfg, block, off, buffer, size);
LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err); LFS_TRACE("lfs_testbd_read -> %d", err);
return err; return err;
} }
int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) { lfs_off_t off, const void *buffer, lfs_size_t size) {
LFS_TESTBD_TRACE("lfs_testbd_prog(%p, " LFS_TRACE("lfs_testbd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
(void*)cfg, block, off, buffer, size); (void*)cfg, block, off, buffer, size);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
@@ -184,13 +182,13 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) { if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior == if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_PROGERROR) { LFS_TESTBD_BADBLOCK_PROGERROR) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT; return LFS_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior == } else if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_PROGNOOP || LFS_TESTBD_BADBLOCK_PROGNOOP ||
bd->cfg->badblock_behavior == bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASENOOP) { LFS_TESTBD_BADBLOCK_ERASENOOP) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0; return 0;
} }
} }
@@ -198,7 +196,7 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
// prog // prog
int err = lfs_testbd_rawprog(cfg, block, off, buffer, size); int err = lfs_testbd_rawprog(cfg, block, off, buffer, size);
if (err) { if (err) {
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err); LFS_TRACE("lfs_testbd_prog -> %d", err);
return err; return err;
} }
@@ -207,18 +205,18 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
bd->power_cycles -= 1; bd->power_cycles -= 1;
if (bd->power_cycles == 0) { if (bd->power_cycles == 0) {
// sync to make sure we persist the last changes // sync to make sure we persist the last changes
LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0); assert(lfs_testbd_rawsync(cfg) == 0);
// simulate power loss // simulate power loss
exit(33); exit(33);
} }
} }
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0; return 0;
} }
int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
LFS_TESTBD_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block); LFS_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
// check if erase is valid // check if erase is valid
@@ -229,11 +227,11 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
if (bd->wear[block] >= bd->cfg->erase_cycles) { if (bd->wear[block] >= bd->cfg->erase_cycles) {
if (bd->cfg->badblock_behavior == if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASEERROR) { LFS_TESTBD_BADBLOCK_ERASEERROR) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
return LFS_ERR_CORRUPT; return LFS_ERR_CORRUPT;
} else if (bd->cfg->badblock_behavior == } else if (bd->cfg->badblock_behavior ==
LFS_TESTBD_BADBLOCK_ERASENOOP) { LFS_TESTBD_BADBLOCK_ERASENOOP) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", 0); LFS_TRACE("lfs_testbd_erase -> %d", 0);
return 0; return 0;
} }
} else { } else {
@@ -245,7 +243,7 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
// erase // erase
int err = lfs_testbd_rawerase(cfg, block); int err = lfs_testbd_rawerase(cfg, block);
if (err) { if (err) {
LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err); LFS_TRACE("lfs_testbd_erase -> %d", err);
return err; return err;
} }
@@ -254,20 +252,20 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
bd->power_cycles -= 1; bd->power_cycles -= 1;
if (bd->power_cycles == 0) { if (bd->power_cycles == 0) {
// sync to make sure we persist the last changes // sync to make sure we persist the last changes
LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0); assert(lfs_testbd_rawsync(cfg) == 0);
// simulate power loss // simulate power loss
exit(33); exit(33);
} }
} }
LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0); LFS_TRACE("lfs_testbd_prog -> %d", 0);
return 0; return 0;
} }
int lfs_testbd_sync(const struct lfs_config *cfg) { int lfs_testbd_sync(const struct lfs_config *cfg) {
LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg); LFS_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
int err = lfs_testbd_rawsync(cfg); int err = lfs_testbd_rawsync(cfg);
LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err); LFS_TRACE("lfs_testbd_sync -> %d", err);
return err; return err;
} }
@@ -275,20 +273,20 @@ int lfs_testbd_sync(const struct lfs_config *cfg) {
/// simulated wear operations /// /// simulated wear operations ///
lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg, lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg,
lfs_block_t block) { lfs_block_t block) {
LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block); LFS_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
// check if block is valid // check if block is valid
LFS_ASSERT(bd->cfg->erase_cycles); LFS_ASSERT(bd->cfg->erase_cycles);
LFS_ASSERT(block < cfg->block_count); LFS_ASSERT(block < cfg->block_count);
LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]); LFS_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
return bd->wear[block]; return bd->wear[block];
} }
int lfs_testbd_setwear(const struct lfs_config *cfg, int lfs_testbd_setwear(const struct lfs_config *cfg,
lfs_block_t block, lfs_testbd_wear_t wear) { lfs_block_t block, lfs_testbd_wear_t wear) {
LFS_TESTBD_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block); LFS_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
lfs_testbd_t *bd = cfg->context; lfs_testbd_t *bd = cfg->context;
// check if block is valid // check if block is valid
@@ -297,6 +295,6 @@ int lfs_testbd_setwear(const struct lfs_config *cfg,
bd->wear[block] = wear; bd->wear[block] = wear;
LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0); LFS_TRACE("lfs_testbd_setwear -> %d", 0);
return 0; return 0;
} }

View File

@@ -19,13 +19,6 @@ extern "C"
#endif #endif
// Block device specific tracing
#ifdef LFS_TESTBD_YES_TRACE
#define LFS_TESTBD_TRACE(...) LFS_TRACE(__VA_ARGS__)
#else
#define LFS_TESTBD_TRACE(...)
#endif
// Mode determining how "bad blocks" behave during testing. This simulates // Mode determining how "bad blocks" behave during testing. This simulates
// some real-world circumstances such as progs not sticking (prog-noop), // some real-world circumstances such as progs not sticking (prog-noop),
// a readonly disk (erase-noop), and ECC failures (read-error). // a readonly disk (erase-noop), and ECC failures (read-error).

1425
lfs.c

File diff suppressed because it is too large Load Diff

49
lfs.h
View File

@@ -9,7 +9,6 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "lfs_util.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@@ -22,7 +21,7 @@ extern "C"
// Software library version // Software library version
// Major (top-nibble), incremented on backwards incompatible changes // Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions // Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x00020003 #define LFS_VERSION 0x00020001
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
@@ -124,25 +123,20 @@ enum lfs_type {
enum lfs_open_flags { enum lfs_open_flags {
// open flags // open flags
LFS_O_RDONLY = 1, // Open a file as read only LFS_O_RDONLY = 1, // Open a file as read only
#ifndef LFS_READONLY
LFS_O_WRONLY = 2, // Open a file as write only LFS_O_WRONLY = 2, // Open a file as write only
LFS_O_RDWR = 3, // Open a file as read and write LFS_O_RDWR = 3, // Open a file as read and write
LFS_O_CREAT = 0x0100, // Create a file if it does not exist LFS_O_CREAT = 0x0100, // Create a file if it does not exist
LFS_O_EXCL = 0x0200, // Fail if a file already exists LFS_O_EXCL = 0x0200, // Fail if a file already exists
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS_O_APPEND = 0x0800, // Move to end of file on every write LFS_O_APPEND = 0x0800, // Move to end of file on every write
#endif
// internally used flags // internally used flags
#ifndef LFS_READONLY
LFS_F_DIRTY = 0x010000, // File does not match storage LFS_F_DIRTY = 0x010000, // File does not match storage
LFS_F_WRITING = 0x020000, // File has been written since last flush LFS_F_WRITING = 0x020000, // File has been written since last flush
#endif
LFS_F_READING = 0x040000, // File has been read since last flush LFS_F_READING = 0x040000, // File has been read since last flush
#ifndef LFS_READONLY LFS_F_ERRED = 0x080000, // An error occured during write
LFS_F_ERRED = 0x080000, // An error occurred during write
#endif
LFS_F_INLINE = 0x100000, // Currently inlined in directory entry LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
LFS_F_OPENED = 0x200000, // File has been opened
}; };
// File seek flags // File seek flags
@@ -180,16 +174,6 @@ struct lfs_config {
// are propogated to the user. // are propogated to the user.
int (*sync)(const struct lfs_config *c); int (*sync)(const struct lfs_config *c);
#ifdef LFS_THREADSAFE
// Lock the underlying block device. Negative error codes
// are propogated to the user.
int (*lock)(const struct lfs_config *c);
// Unlock the underlying block device. Negative error codes
// are propogated to the user.
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read. All read operations will be a // Minimum size of a block read. All read operations will be a
// multiple of this value. // multiple of this value.
lfs_size_t read_size; lfs_size_t read_size;
@@ -207,7 +191,7 @@ struct lfs_config {
// Number of erasable blocks on the device. // Number of erasable blocks on the device.
lfs_size_t block_count; lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves // Number of erase cycles before littlefs evicts metadata logs and moves
// the metadata to another block. Suggested values are in the // the metadata to another block. Suggested values are in the
// range 100-1000, with large values having better performance at the cost // range 100-1000, with large values having better performance at the cost
// of less consistent wear distribution. // of less consistent wear distribution.
@@ -256,12 +240,6 @@ struct lfs_config {
// larger attributes size but must be <= LFS_ATTR_MAX. Defaults to // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
// LFS_ATTR_MAX when zero. // LFS_ATTR_MAX when zero.
lfs_size_t attr_max; lfs_size_t attr_max;
// Optional upper limit on total space given to metadata pairs in bytes. On
// devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB)
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
lfs_size_t metadata_max;
}; };
// File info structure // File info structure
@@ -333,6 +311,7 @@ typedef struct lfs_mdir {
uint16_t count; uint16_t count;
bool erased; bool erased;
bool split; bool split;
bool first;
lfs_block_t tail[2]; lfs_block_t tail[2];
} lfs_mdir_t; } lfs_mdir_t;
@@ -421,7 +400,6 @@ typedef struct lfs {
/// Filesystem functions /// /// Filesystem functions ///
#ifndef LFS_READONLY
// Format a block device with the littlefs // Format a block device with the littlefs
// //
// Requires a littlefs object and config struct. This clobbers the littlefs // Requires a littlefs object and config struct. This clobbers the littlefs
@@ -430,7 +408,6 @@ typedef struct lfs {
// //
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_format(lfs_t *lfs, const struct lfs_config *config); int lfs_format(lfs_t *lfs, const struct lfs_config *config);
#endif
// Mounts a littlefs // Mounts a littlefs
// //
@@ -450,15 +427,12 @@ int lfs_unmount(lfs_t *lfs);
/// General operations /// /// General operations ///
#ifndef LFS_READONLY
// Removes a file or directory // Removes a file or directory
// //
// If removing a directory, the directory must be empty. // If removing a directory, the directory must be empty.
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_remove(lfs_t *lfs, const char *path); int lfs_remove(lfs_t *lfs, const char *path);
#endif
#ifndef LFS_READONLY
// Rename or move a file or directory // Rename or move a file or directory
// //
// If the destination exists, it must match the source in type. // If the destination exists, it must match the source in type.
@@ -466,7 +440,6 @@ int lfs_remove(lfs_t *lfs, const char *path);
// //
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
#endif
// Find info about a file or directory // Find info about a file or directory
// //
@@ -489,7 +462,6 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
uint8_t type, void *buffer, lfs_size_t size); uint8_t type, void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Set custom attributes // Set custom attributes
// //
// Custom attributes are uniquely identified by an 8-bit type and limited // Custom attributes are uniquely identified by an 8-bit type and limited
@@ -499,16 +471,13 @@ lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_setattr(lfs_t *lfs, const char *path, int lfs_setattr(lfs_t *lfs, const char *path,
uint8_t type, const void *buffer, lfs_size_t size); uint8_t type, const void *buffer, lfs_size_t size);
#endif
#ifndef LFS_READONLY
// Removes a custom attribute // Removes a custom attribute
// //
// If an attribute is not found, nothing happens. // If an attribute is not found, nothing happens.
// //
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
#endif
/// File operations /// /// File operations ///
@@ -557,7 +526,6 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size); void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Write data to file // Write data to file
// //
// Takes a buffer and size indicating the data to write. The file will not // Takes a buffer and size indicating the data to write. The file will not
@@ -566,7 +534,6 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
// Returns the number of bytes written, or a negative error code on failure. // Returns the number of bytes written, or a negative error code on failure.
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size); const void *buffer, lfs_size_t size);
#endif
// Change the position of the file // Change the position of the file
// //
@@ -575,12 +542,10 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t off, int whence); lfs_soff_t off, int whence);
#ifndef LFS_READONLY
// Truncates the size of the file to the specified size // Truncates the size of the file to the specified size
// //
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
#endif
// Return the position of the file // Return the position of the file
// //
@@ -603,12 +568,10 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
/// Directory operations /// /// Directory operations ///
#ifndef LFS_READONLY
// Create a directory // Create a directory
// //
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_mkdir(lfs_t *lfs, const char *path); int lfs_mkdir(lfs_t *lfs, const char *path);
#endif
// Open a directory // Open a directory
// //
@@ -670,7 +633,6 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs);
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
#ifndef LFS_READONLY
#ifdef LFS_MIGRATE #ifdef LFS_MIGRATE
// Attempts to migrate a previous version of littlefs // Attempts to migrate a previous version of littlefs
// //
@@ -685,7 +647,6 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
// Returns a negative error code on failure. // Returns a negative error code on failure.
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg);
#endif #endif
#endif
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -49,54 +49,40 @@ extern "C"
// code footprint // code footprint
// Logging functions // Logging functions
#ifndef LFS_TRACE
#ifdef LFS_YES_TRACE #ifdef LFS_YES_TRACE
#define LFS_TRACE_(fmt, ...) \ #define LFS_TRACE(fmt, ...) \
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) printf("%s:%d:trace: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")
#else #else
#define LFS_TRACE(...) #define LFS_TRACE(fmt, ...)
#endif
#endif #endif
#ifndef LFS_DEBUG
#ifndef LFS_NO_DEBUG #ifndef LFS_NO_DEBUG
#define LFS_DEBUG_(fmt, ...) \ #define LFS_DEBUG(fmt, ...) \
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) printf("%s:%d:debug: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "")
#else #else
#define LFS_DEBUG(...) #define LFS_DEBUG(fmt, ...)
#endif
#endif #endif
#ifndef LFS_WARN
#ifndef LFS_NO_WARN #ifndef LFS_NO_WARN
#define LFS_WARN_(fmt, ...) \ #define LFS_WARN(fmt, ...) \
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) printf("%s:%d:warn: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "")
#else #else
#define LFS_WARN(...) #define LFS_WARN(fmt, ...)
#endif
#endif #endif
#ifndef LFS_ERROR
#ifndef LFS_NO_ERROR #ifndef LFS_NO_ERROR
#define LFS_ERROR_(fmt, ...) \ #define LFS_ERROR(fmt, ...) \
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) printf("%s:%d:error: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "")
#else #else
#define LFS_ERROR(...) #define LFS_ERROR(fmt, ...)
#endif
#endif #endif
// Runtime assertions // Runtime assertions
#ifndef LFS_ASSERT
#ifndef LFS_NO_ASSERT #ifndef LFS_NO_ASSERT
#define LFS_ASSERT(test) assert(test) #define LFS_ASSERT(test) assert(test)
#else #else
#define LFS_ASSERT(test) #define LFS_ASSERT(test)
#endif #endif
#endif
// Builtin functions, these may be replaced by more efficient // Builtin functions, these may be replaced by more efficient
@@ -121,7 +107,7 @@ static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
return lfs_aligndown(a + alignment-1, alignment); return lfs_aligndown(a + alignment-1, alignment);
} }
// Find the smallest power of 2 greater than or equal to a // Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs_npw2(uint32_t a) { static inline uint32_t lfs_npw2(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1); return 32 - __builtin_clz(a-1);

View File

@@ -1,214 +0,0 @@
#!/usr/bin/env python3
#
# Script to find code size at the function level. Basically just a bit wrapper
# around nm with some extra conveniences for comparing builds. Heavily inspired
# by Linux's Bloat-O-Meter.
#
import os
import glob
import itertools as it
import subprocess as sp
import shlex
import re
import csv
import collections as co
OBJ_PATHS = ['*.o', 'bd/*.o']
def collect(paths, **args):
results = co.defaultdict(lambda: 0)
pattern = re.compile(
'^(?P<size>[0-9a-fA-F]+)' +
' (?P<type>[%s])' % re.escape(args['type']) +
' (?P<func>.+?)$')
for path in paths:
# note nm-tool may contain extra args
cmd = args['nm_tool'] + ['--size-sort', path]
if args.get('verbose'):
print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd,
stdout=sp.PIPE,
stderr=sp.PIPE if not args.get('verbose') else None,
universal_newlines=True)
for line in proc.stdout:
m = pattern.match(line)
if m:
results[(path, m.group('func'))] += int(m.group('size'), 16)
proc.wait()
if proc.returncode != 0:
if not args.get('verbose'):
for line in proc.stderr:
sys.stdout.write(line)
sys.exit(-1)
flat_results = []
for (file, func), size in results.items():
# map to source files
if args.get('build_dir'):
file = re.sub('%s/*' % re.escape(args['build_dir']), '', file)
# discard internal functions
if func.startswith('__'):
continue
# discard .8449 suffixes created by optimizer
func = re.sub('\.[0-9]+', '', func)
flat_results.append((file, func, size))
return flat_results
def main(**args):
# find sizes
if not args.get('use', None):
# find .o files
paths = []
for path in args['obj_paths']:
if os.path.isdir(path):
path = path + '/*.o'
for path in glob.glob(path):
paths.append(path)
if not paths:
print('no .obj files found in %r?' % args['obj_paths'])
sys.exit(-1)
results = collect(paths, **args)
else:
with open(args['use']) as f:
r = csv.DictReader(f)
results = [
( result['file'],
result['function'],
int(result['size']))
for result in r]
total = 0
for _, _, size in results:
total += size
# find previous results?
if args.get('diff'):
with open(args['diff']) as f:
r = csv.DictReader(f)
prev_results = [
( result['file'],
result['function'],
int(result['size']))
for result in r]
prev_total = 0
for _, _, size in prev_results:
prev_total += size
# write results to CSV
if args.get('output'):
with open(args['output'], 'w') as f:
w = csv.writer(f)
w.writerow(['file', 'function', 'size'])
for file, func, size in sorted(results):
w.writerow((file, func, size))
# print results
def dedup_entries(results, by='function'):
entries = co.defaultdict(lambda: 0)
for file, func, size in results:
entry = (file if by == 'file' else func)
entries[entry] += size
return entries
def diff_entries(olds, news):
diff = co.defaultdict(lambda: (0, 0, 0, 0))
for name, new in news.items():
diff[name] = (0, new, new, 1.0)
for name, old in olds.items():
_, new, _, _ = diff[name]
diff[name] = (old, new, new-old, (new-old)/old if old else 1.0)
return diff
def print_header(by=''):
if not args.get('diff'):
print('%-36s %7s' % (by, 'size'))
else:
print('%-36s %7s %7s %7s' % (by, 'old', 'new', 'diff'))
def print_entries(by='function'):
entries = dedup_entries(results, by=by)
if not args.get('diff'):
print_header(by=by)
for name, size in sorted(entries.items()):
print("%-36s %7d" % (name, size))
else:
prev_entries = dedup_entries(prev_results, by=by)
diff = diff_entries(prev_entries, entries)
print_header(by='%s (%d added, %d removed)' % (by,
sum(1 for old, _, _, _ in diff.values() if not old),
sum(1 for _, new, _, _ in diff.values() if not new)))
for name, (old, new, diff, ratio) in sorted(diff.items(),
key=lambda x: (-x[1][3], x)):
if ratio or args.get('all'):
print("%-36s %7s %7s %+7d%s" % (name,
old or "-",
new or "-",
diff,
' (%+.1f%%)' % (100*ratio) if ratio else ''))
def print_totals():
if not args.get('diff'):
print("%-36s %7d" % ('TOTAL', total))
else:
ratio = (total-prev_total)/prev_total if prev_total else 1.0
print("%-36s %7s %7s %+7d%s" % (
'TOTAL',
prev_total if prev_total else '-',
total if total else '-',
total-prev_total,
' (%+.1f%%)' % (100*ratio) if ratio else ''))
if args.get('quiet'):
pass
elif args.get('summary'):
print_header()
print_totals()
elif args.get('files'):
print_entries(by='file')
print_totals()
else:
print_entries(by='function')
print_totals()
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Find code size at the function level.")
parser.add_argument('obj_paths', nargs='*', default=OBJ_PATHS,
help="Description of where to find *.o files. May be a directory \
or a list of paths. Defaults to %r." % OBJ_PATHS)
parser.add_argument('-v', '--verbose', action='store_true',
help="Output commands that run behind the scenes.")
parser.add_argument('-o', '--output',
help="Specify CSV file to store results.")
parser.add_argument('-u', '--use',
help="Don't compile and find code sizes, instead use this CSV file.")
parser.add_argument('-d', '--diff',
help="Specify CSV file to diff code size against.")
parser.add_argument('-a', '--all', action='store_true',
help="Show all functions, not just the ones that changed.")
parser.add_argument('--files', action='store_true',
help="Show file-level code sizes. Note this does not include padding! "
"So sizes may differ from other tools.")
parser.add_argument('-s', '--summary', action='store_true',
help="Only show the total code size.")
parser.add_argument('-q', '--quiet', action='store_true',
help="Don't show anything, useful with -o.")
parser.add_argument('--type', default='tTrRdDbB',
help="Type of symbols to report, this uses the same single-character "
"type-names emitted by nm. Defaults to %(default)r.")
parser.add_argument('--nm-tool', default=['nm'], type=lambda x: x.split(),
help="Path to the nm tool to use.")
parser.add_argument('--build-dir',
help="Specify the relative build directory. Used to map object files \
to the correct source files.")
sys.exit(main(**vars(parser.parse_args())))

View File

@@ -1,254 +0,0 @@
#!/usr/bin/env python3
#
# Parse and report coverage info from .info files generated by lcov
#
import os
import glob
import csv
import re
import collections as co
import bisect as b
INFO_PATHS = ['tests/*.toml.info']
def collect(paths, **args):
file = None
funcs = []
lines = co.defaultdict(lambda: 0)
pattern = re.compile(
'^(?P<file>SF:/?(?P<file_name>.*))$'
'|^(?P<func>FN:(?P<func_lineno>[0-9]*),(?P<func_name>.*))$'
'|^(?P<line>DA:(?P<line_lineno>[0-9]*),(?P<line_hits>[0-9]*))$')
for path in paths:
with open(path) as f:
for line in f:
m = pattern.match(line)
if m and m.group('file'):
file = m.group('file_name')
elif m and file and m.group('func'):
funcs.append((file, int(m.group('func_lineno')),
m.group('func_name')))
elif m and file and m.group('line'):
lines[(file, int(m.group('line_lineno')))] += (
int(m.group('line_hits')))
# map line numbers to functions
funcs.sort()
def func_from_lineno(file, lineno):
i = b.bisect(funcs, (file, lineno))
if i and funcs[i-1][0] == file:
return funcs[i-1][2]
else:
return None
# reduce to function info
reduced_funcs = co.defaultdict(lambda: (0, 0))
for (file, line_lineno), line_hits in lines.items():
func = func_from_lineno(file, line_lineno)
if not func:
continue
hits, count = reduced_funcs[(file, func)]
reduced_funcs[(file, func)] = (hits + (line_hits > 0), count + 1)
results = []
for (file, func), (hits, count) in reduced_funcs.items():
# discard internal/testing functions (test_* injected with
# internal testing)
if func.startswith('__') or func.startswith('test_'):
continue
# discard .8449 suffixes created by optimizer
func = re.sub('\.[0-9]+', '', func)
results.append((file, func, hits, count))
return results
def main(**args):
# find coverage
if not args.get('use'):
# find *.info files
paths = []
for path in args['info_paths']:
if os.path.isdir(path):
path = path + '/*.gcov'
for path in glob.glob(path):
paths.append(path)
if not paths:
print('no .info files found in %r?' % args['info_paths'])
sys.exit(-1)
results = collect(paths, **args)
else:
with open(args['use']) as f:
r = csv.DictReader(f)
results = [
( result['file'],
result['function'],
int(result['hits']),
int(result['count']))
for result in r]
total_hits, total_count = 0, 0
for _, _, hits, count in results:
total_hits += hits
total_count += count
# find previous results?
if args.get('diff'):
with open(args['diff']) as f:
r = csv.DictReader(f)
prev_results = [
( result['file'],
result['function'],
int(result['hits']),
int(result['count']))
for result in r]
prev_total_hits, prev_total_count = 0, 0
for _, _, hits, count in prev_results:
prev_total_hits += hits
prev_total_count += count
# write results to CSV
if args.get('output'):
with open(args['output'], 'w') as f:
w = csv.writer(f)
w.writerow(['file', 'function', 'hits', 'count'])
for file, func, hits, count in sorted(results):
w.writerow((file, func, hits, count))
# print results
def dedup_entries(results, by='function'):
entries = co.defaultdict(lambda: (0, 0))
for file, func, hits, count in results:
entry = (file if by == 'file' else func)
entry_hits, entry_count = entries[entry]
entries[entry] = (entry_hits + hits, entry_count + count)
return entries
def diff_entries(olds, news):
diff = co.defaultdict(lambda: (0, 0, 0, 0, 0, 0, 0))
for name, (new_hits, new_count) in news.items():
diff[name] = (
0, 0,
new_hits, new_count,
new_hits, new_count,
(new_hits/new_count if new_count else 1.0) - 1.0)
for name, (old_hits, old_count) in olds.items():
_, _, new_hits, new_count, _, _, _ = diff[name]
diff[name] = (
old_hits, old_count,
new_hits, new_count,
new_hits-old_hits, new_count-old_count,
((new_hits/new_count if new_count else 1.0)
- (old_hits/old_count if old_count else 1.0)))
return diff
def print_header(by=''):
if not args.get('diff'):
print('%-36s %19s' % (by, 'hits/line'))
else:
print('%-36s %19s %19s %11s' % (by, 'old', 'new', 'diff'))
def print_entries(by='function'):
entries = dedup_entries(results, by=by)
if not args.get('diff'):
print_header(by=by)
for name, (hits, count) in sorted(entries.items()):
print("%-36s %11s %7s" % (name,
'%d/%d' % (hits, count)
if count else '-',
'%.1f%%' % (100*hits/count)
if count else '-'))
else:
prev_entries = dedup_entries(prev_results, by=by)
diff = diff_entries(prev_entries, entries)
print_header(by='%s (%d added, %d removed)' % (by,
sum(1 for _, old, _, _, _, _, _ in diff.values() if not old),
sum(1 for _, _, _, new, _, _, _ in diff.values() if not new)))
for name, (
old_hits, old_count,
new_hits, new_count,
diff_hits, diff_count, ratio) in sorted(diff.items(),
key=lambda x: (-x[1][6], x)):
if ratio or args.get('all'):
print("%-36s %11s %7s %11s %7s %11s%s" % (name,
'%d/%d' % (old_hits, old_count)
if old_count else '-',
'%.1f%%' % (100*old_hits/old_count)
if old_count else '-',
'%d/%d' % (new_hits, new_count)
if new_count else '-',
'%.1f%%' % (100*new_hits/new_count)
if new_count else '-',
'%+d/%+d' % (diff_hits, diff_count),
' (%+.1f%%)' % (100*ratio) if ratio else ''))
def print_totals():
if not args.get('diff'):
print("%-36s %11s %7s" % ('TOTAL',
'%d/%d' % (total_hits, total_count)
if total_count else '-',
'%.1f%%' % (100*total_hits/total_count)
if total_count else '-'))
else:
ratio = ((total_hits/total_count
if total_count else 1.0)
- (prev_total_hits/prev_total_count
if prev_total_count else 1.0))
print("%-36s %11s %7s %11s %7s %11s%s" % ('TOTAL',
'%d/%d' % (prev_total_hits, prev_total_count)
if prev_total_count else '-',
'%.1f%%' % (100*prev_total_hits/prev_total_count)
if prev_total_count else '-',
'%d/%d' % (total_hits, total_count)
if total_count else '-',
'%.1f%%' % (100*total_hits/total_count)
if total_count else '-',
'%+d/%+d' % (total_hits-prev_total_hits,
total_count-prev_total_count),
' (%+.1f%%)' % (100*ratio) if ratio else ''))
if args.get('quiet'):
pass
elif args.get('summary'):
print_header()
print_totals()
elif args.get('files'):
print_entries(by='file')
print_totals()
else:
print_entries(by='function')
print_totals()
if __name__ == "__main__":
import argparse
import sys
parser = argparse.ArgumentParser(
description="Parse and report coverage info from .info files \
generated by lcov")
parser.add_argument('info_paths', nargs='*', default=INFO_PATHS,
help="Description of where to find *.info files. May be a directory \
or list of paths. *.info files will be merged to show the total \
coverage. Defaults to %r." % INFO_PATHS)
parser.add_argument('-v', '--verbose', action='store_true',
help="Output commands that run behind the scenes.")
parser.add_argument('-o', '--output',
help="Specify CSV file to store results.")
parser.add_argument('-u', '--use',
help="Don't do any work, instead use this CSV file.")
parser.add_argument('-d', '--diff',
help="Specify CSV file to diff code size against.")
parser.add_argument('-a', '--all', action='store_true',
help="Show all functions, not just the ones that changed.")
parser.add_argument('--files', action='store_true',
help="Show file-level coverage.")
parser.add_argument('-s', '--summary', action='store_true',
help="Only show the total coverage.")
parser.add_argument('-q', '--quiet', action='store_true',
help="Don't show anything, useful with -o.")
sys.exit(main(**vars(parser.parse_args())))

View File

@@ -166,8 +166,8 @@ def mkassert(type, comp, lh, rh, size=None):
'type': type.lower(), 'TYPE': type.upper(), 'type': type.lower(), 'TYPE': type.upper(),
'comp': comp.lower(), 'COMP': comp.upper(), 'comp': comp.lower(), 'COMP': comp.upper(),
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(), 'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
'lh': lh.strip(' '), 'lh': lh.strip(),
'rh': rh.strip(' '), 'rh': rh.strip(),
'size': size, 'size': size,
} }
if size: if size:

View File

@@ -233,8 +233,8 @@ class MetadataPair:
def __lt__(self, other): def __lt__(self, other):
# corrupt blocks don't count # corrupt blocks don't count
if not self or not other: if not self and other:
return bool(other) return True
# use sequence arithmetic to avoid overflow # use sequence arithmetic to avoid overflow
return not ((other.rev - self.rev) & 0x80000000) return not ((other.rev - self.rev) & 0x80000000)
@@ -318,24 +318,6 @@ def main(args):
# find most recent pair # find most recent pair
mdir = MetadataPair(blocks) mdir = MetadataPair(blocks)
try:
mdir.tail = mdir[Tag('tail', 0, 0)]
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
mdir.tail = None
except KeyError:
mdir.tail = None
print("mdir {%s} rev %d%s%s%s" % (
', '.join('%#x' % b
for b in [args.block1, args.block2]
if b is not None),
mdir.rev,
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
if len(mdir.pair) > 1 else '',
' (corrupted!)' if not mdir else '',
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
if mdir.tail else ''))
if args.all: if args.all:
mdir.dump_all(truncate=not args.no_truncate) mdir.dump_all(truncate=not args.no_truncate)
elif args.log: elif args.log:

View File

@@ -7,14 +7,79 @@ import io
import itertools as it import itertools as it
from readmdir import Tag, MetadataPair from readmdir import Tag, MetadataPair
def popc(x):
return bin(x).count('1')
def ctz(x):
return len(bin(x)) - len(bin(x).rstrip('0'))
def dumpentries(args, mdir, f):
for k, id_ in enumerate(mdir.ids):
name = mdir[Tag('name', id_, 0)]
struct_ = mdir[Tag('struct', id_, 0)]
f.write("id %d %s %s" % (
id_, name.typerepr(),
json.dumps(name.data.decode('utf8'))))
if struct_.is_('dirstruct'):
f.write(" dir {%#x, %#x}" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff')))
if struct_.is_('ctzstruct'):
f.write(" ctz {%#x} size %d" % struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff')))
if struct_.is_('inlinestruct'):
f.write(" inline size %d" % struct_.size)
f.write("\n")
if args.data and struct_.is_('inlinestruct'):
for i in range(0, len(struct_.data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in struct_.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, struct_.data[i:i+16]))))
elif args.data and struct_.is_('ctzstruct'):
block, size = struct.unpack(
'<II', struct_.data[:8].ljust(8, b'\xff'))
data = []
i = 0 if size == 0 else (size-1) // (args.block_size - 8)
if i != 0:
i = ((size-1) - 4*popc(i-1)+2) // (args.block_size - 8)
with open(args.disk, 'rb') as f2:
while i >= 0:
f2.seek(block * args.block_size)
dat = f2.read(args.block_size)
data.append(dat[4*(ctz(i)+1) if i != 0 else 0:])
block, = struct.unpack('<I', dat[:4].ljust(4, b'\xff'))
i -= 1
data = bytes(it.islice(
it.chain.from_iterable(reversed(data)), size))
for i in range(0, min(len(data), 256)
if not args.no_truncate else len(data), 16):
f.write(" %08x: %-47s %-16s\n" % (
i, ' '.join('%02x' % c for c in data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, data[i:i+16]))))
for tag in mdir.tags:
if tag.id==id_ and tag.is_('userattr'):
f.write("id %d %s size %d\n" % (
id_, tag.typerepr(), tag.size))
if args.data:
for i in range(0, len(tag.data), 16):
f.write(" %-47s %-16s\n" % (
' '.join('%02x' % c for c in tag.data[i:i+16]),
''.join(c if c >= ' ' and c <= '~' else '.'
for c in map(chr, tag.data[i:i+16]))))
def main(args): def main(args):
superblock = None
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
dirs = []
mdirs = []
corrupted = []
cycle = False
with open(args.disk, 'rb') as f: with open(args.disk, 'rb') as f:
dirs = []
superblock = None
gstate = b''
mdirs = []
cycle = False
tail = (args.block1, args.block2) tail = (args.block1, args.block2)
hard = False hard = False
while True: while True:
@@ -61,10 +126,6 @@ def main(args):
except KeyError: except KeyError:
pass pass
# corrupted?
if not mdir:
corrupted.append(mdir)
# add to directories # add to directories
mdirs.append(mdir) mdirs.append(mdir)
if mdir.tail is None or not mdir.tail.is_('hardtail'): if mdir.tail is None or not mdir.tail.is_('hardtail'):
@@ -99,65 +160,72 @@ def main(args):
dir[0].path = path.replace('//', '/') dir[0].path = path.replace('//', '/')
# print littlefs + version info # dump tree
version = ('?', '?') if not args.superblock and not args.gstate and not args.mdirs:
if superblock: args.superblock = True
version = tuple(reversed( args.gstate = True
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff')))) args.mdirs = True
print("%-47s%s" % ("littlefs v%s.%s" % version,
"data (truncated, if it fits)"
if not any([args.no_truncate, args.log, args.all]) else ""))
# print gstate if args.superblock and superblock:
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate)) print("superblock %s v%d.%d" % (
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0]) json.dumps(superblock[0].data.decode('utf8')),
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff')) struct.unpack('<H', superblock[1].data[2:2+2])[0],
if tag.size or not tag.isvalid: struct.unpack('<H', superblock[1].data[0:0+2])[0]))
print(" orphans >=%d" % max(tag.size, 1)) print(
if tag.type: " block_size %d\n"
print(" move dir {%#x, %#x} id %d" % ( " block_count %d\n"
blocks[0], blocks[1], tag.id)) " name_max %d\n"
" file_max %d\n"
" attr_max %d" % struct.unpack(
'<IIIII', superblock[1].data[4:4+20].ljust(20, b'\xff')))
# print mdir info if args.gstate and gstate:
for i, dir in enumerate(dirs): print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
print("dir %s" % (json.dumps(dir[0].path) tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
if hasattr(dir[0], 'path') else '(orphan)')) blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
if tag.size:
print(" orphans %d" % tag.size)
if tag.type:
print(" move dir {%#x, %#x} id %d" % (
blocks[0], blocks[1], tag.id))
for j, mdir in enumerate(dir): if args.mdirs:
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % ( for i, dir in enumerate(dirs):
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev, print("dir %s" % (json.dumps(dir[0].path)
' (corrupted!)' if not mdir else '', if hasattr(dir[0], 'path') else '(orphan)'))
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
if mdir.tail else ''))
f = io.StringIO() for j, mdir in enumerate(dir):
if args.log: print("mdir {%#x, %#x} rev %d%s" % (
mdir.dump_log(f, truncate=not args.no_truncate) mdir.blocks[0], mdir.blocks[1], mdir.rev,
elif args.all: ' (corrupted)' if not mdir else ''))
mdir.dump_all(f, truncate=not args.no_truncate)
else:
mdir.dump_tags(f, truncate=not args.no_truncate)
lines = list(filter(None, f.getvalue().split('\n'))) f = io.StringIO()
for k, line in enumerate(lines): if args.tags:
print("%s %s" % ( mdir.dump_tags(f, truncate=not args.no_truncate)
' ' if j == len(dir)-1 else elif args.log:
'v' if k == len(lines)-1 else mdir.dump_log(f, truncate=not args.no_truncate)
'|', elif args.all:
line)) mdir.dump_all(f, truncate=not args.no_truncate)
else:
dumpentries(args, mdir, f)
errcode = 0 lines = list(filter(None, f.getvalue().split('\n')))
for mdir in corrupted: for k, line in enumerate(lines):
errcode = errcode or 1 print("%s %s" % (
print("*** corrupted mdir {%#x, %#x}! ***" % ( ' ' if j == len(dir)-1 else
mdir.blocks[0], mdir.blocks[1])) 'v' if k == len(lines)-1 else
'|',
line))
if cycle: if cycle:
errcode = errcode or 2 print("*** cycle detected! -> {%#x, %#x} ***" % (cycle[0], cycle[1]))
print("*** cycle detected {%#x, %#x}! ***" % (
cycle[0], cycle[1]))
return errcode if cycle:
return 2
elif not all(mdir for dir in dirs for mdir in dir):
return 1
else:
return 0;
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
@@ -170,14 +238,24 @@ if __name__ == "__main__":
help="Size of a block in bytes.") help="Size of a block in bytes.")
parser.add_argument('block1', nargs='?', default=0, parser.add_argument('block1', nargs='?', default=0,
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Optional first block address for finding the superblock.") help="Optional first block address for finding the root.")
parser.add_argument('block2', nargs='?', default=1, parser.add_argument('block2', nargs='?', default=1,
type=lambda x: int(x, 0), type=lambda x: int(x, 0),
help="Optional second block address for finding the superblock.") help="Optional second block address for finding the root.")
parser.add_argument('-s', '--superblock', action='store_true',
help="Show contents of the superblock.")
parser.add_argument('-g', '--gstate', action='store_true',
help="Show contents of global-state.")
parser.add_argument('-m', '--mdirs', action='store_true',
help="Show contents of metadata-pairs/directories.")
parser.add_argument('-t', '--tags', action='store_true',
help="Show metadata tags instead of reconstructing entries.")
parser.add_argument('-l', '--log', action='store_true', parser.add_argument('-l', '--log', action='store_true',
help="Show tags in log.") help="Show tags in log.")
parser.add_argument('-a', '--all', action='store_true', parser.add_argument('-a', '--all', action='store_true',
help="Show all tags in log, included tags in corrupted commits.") help="Show all tags in log, included tags in corrupted commits.")
parser.add_argument('-d', '--data', action='store_true',
help="Also show the raw contents of files/attrs/tags.")
parser.add_argument('-T', '--no-truncate', action='store_true', parser.add_argument('-T', '--no-truncate', action='store_true',
help="Show the full contents of files/attrs/tags.") help="Don't truncate large amounts of data.")
sys.exit(main(parser.parse_args())) sys.exit(main(parser.parse_args()))

View File

@@ -20,50 +20,19 @@ import pty
import errno import errno
import signal import signal
TEST_PATHS = 'tests' TESTDIR = 'tests'
RULES = """ RULES = """
# add block devices to sources
TESTSRC ?= $(SRC) $(wildcard bd/*.c)
define FLATTEN define FLATTEN
%(path)s%%$(subst /,.,$(target)): $(target) tests/%$(subst /,.,$(target)): $(target)
./scripts/explode_asserts.py $$< -o $$@ ./scripts/explode_asserts.py $$< -o $$@
endef endef
$(foreach target,$(TESTSRC),$(eval $(FLATTEN))) $(foreach target,$(SRC),$(eval $(FLATTEN)))
-include tests/*.d
-include %(path)s*.d
.SECONDARY: .SECONDARY:
%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
%(path)s.test: %(path)s.test.o \\
$(foreach t,$(subst /,.,$(TESTSRC:.c=.o)),%(path)s.$t)
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
# needed in case builddir is different
%(path)s%%.o: %(path)s%%.c
$(CC) -c -MMD $(CFLAGS) $< -o $@
"""
COVERAGE_RULES = """
%(path)s.test: override CFLAGS += -fprofile-arcs -ftest-coverage
# delete lingering coverage
%(path)s.test: | %(path)s.info.clean
.PHONY: %(path)s.info.clean
%(path)s.info.clean:
rm -f %(path)s*.gcda
# accumulate coverage info
.PHONY: %(path)s.info
%(path)s.info:
$(strip $(LCOV) -c \\
$(addprefix -d ,$(wildcard %(path)s*.gcda)) \\
--rc 'geninfo_adjust_src_path=$(shell pwd)' \\
-o $@)
$(LCOV) -e $@ $(addprefix /,$(SRC)) -o $@
ifdef COVERAGETARGET
$(strip $(LCOV) -a $@ \\
$(addprefix -a ,$(wildcard $(COVERAGETARGET))) \\
-o $(COVERAGETARGET))
endif
""" """
GLOBALS = """ GLOBALS = """
//////////////// AUTOGENERATED TEST //////////////// //////////////// AUTOGENERATED TEST ////////////////
@@ -150,8 +119,6 @@ class TestCase:
self.if_ = config.get('if', None) self.if_ = config.get('if', None)
self.in_ = config.get('in', None) self.in_ = config.get('in', None)
self.result = None
def __str__(self): def __str__(self):
if hasattr(self, 'permno'): if hasattr(self, 'permno'):
if any(k not in self.case.defines for k in self.defines): if any(k not in self.case.defines for k in self.defines):
@@ -212,7 +179,7 @@ class TestCase:
len(self.filter) >= 2 and len(self.filter) >= 2 and
self.filter[1] != self.permno): self.filter[1] != self.permno):
return False return False
elif args.get('no_internal') and self.in_ is not None: elif args.get('no_internal', False) and self.in_ is not None:
return False return False
elif self.if_ is not None: elif self.if_ is not None:
if_ = self.if_ if_ = self.if_
@@ -246,7 +213,7 @@ class TestCase:
try: try:
with open(disk, 'w') as f: with open(disk, 'w') as f:
f.truncate(0) f.truncate(0)
if args.get('verbose'): if args.get('verbose', False):
print('truncate --size=0', disk) print('truncate --size=0', disk)
except FileNotFoundError: except FileNotFoundError:
pass pass
@@ -264,20 +231,20 @@ class TestCase:
ncmd.extend(['-ex', 'r']) ncmd.extend(['-ex', 'r'])
if failure.assert_: if failure.assert_:
ncmd.extend(['-ex', 'up 2']) ncmd.extend(['-ex', 'up 2'])
elif gdb == 'main': elif gdb == 'start':
ncmd.extend([ ncmd.extend([
'-ex', 'b %s:%d' % (self.suite.path, self.code_lineno), '-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
'-ex', 'r']) '-ex', 'r'])
ncmd.extend(['--args'] + cmd) ncmd.extend(['--args'] + cmd)
if args.get('verbose'): if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in ncmd)) print(' '.join(shlex.quote(c) for c in ncmd))
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
sys.exit(sp.call(ncmd)) sys.exit(sp.call(ncmd))
# run test case! # run test case!
mpty, spty = pty.openpty() mpty, spty = pty.openpty()
if args.get('verbose'): if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in cmd)) print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd, stdout=spty, stderr=spty) proc = sp.Popen(cmd, stdout=spty, stderr=spty)
os.close(spty) os.close(spty)
@@ -293,7 +260,7 @@ class TestCase:
break break
raise raise
stdout.append(line) stdout.append(line)
if args.get('verbose'): if args.get('verbose', False):
sys.stdout.write(line) sys.stdout.write(line)
# intercept asserts # intercept asserts
m = re.match( m = re.match(
@@ -332,7 +299,7 @@ class ValgrindTestCase(TestCase):
return not self.leaky and super().shouldtest(**args) return not self.leaky and super().shouldtest(**args)
def test(self, exec=[], **args): def test(self, exec=[], **args):
verbose = args.get('verbose') verbose = args.get('verbose', False)
uninit = (self.defines.get('LFS_ERASE_VALUE', None) == -1) uninit = (self.defines.get('LFS_ERASE_VALUE', None) == -1)
exec = [ exec = [
'valgrind', 'valgrind',
@@ -384,17 +351,12 @@ class TestSuite:
self.name = os.path.basename(path) self.name = os.path.basename(path)
if self.name.endswith('.toml'): if self.name.endswith('.toml'):
self.name = self.name[:-len('.toml')] self.name = self.name[:-len('.toml')]
if args.get('build_dir'): self.path = path
self.toml = path
self.path = args['build_dir'] + '/' + path
else:
self.toml = path
self.path = path
self.classes = classes self.classes = classes
self.defines = defines.copy() self.defines = defines.copy()
self.filter = filter self.filter = filter
with open(self.toml) as f: with open(path) as f:
# load tests # load tests
config = toml.load(f) config = toml.load(f)
@@ -505,7 +467,7 @@ class TestSuite:
def build(self, **args): def build(self, **args):
# build test files # build test files
tf = open(self.path + '.test.tc', 'w') tf = open(self.path + '.test.c.t', 'w')
tf.write(GLOBALS) tf.write(GLOBALS)
if self.code is not None: if self.code is not None:
tf.write('#line %d "%s"\n' % (self.code_lineno, self.path)) tf.write('#line %d "%s"\n' % (self.code_lineno, self.path))
@@ -515,7 +477,7 @@ class TestSuite:
for case in self.cases: for case in self.cases:
if case.in_ not in tfs: if case.in_ not in tfs:
tfs[case.in_] = open(self.path+'.'+ tfs[case.in_] = open(self.path+'.'+
re.sub('(\.c)?$', '.tc', case.in_.replace('/', '.')), 'w') case.in_.replace('/', '.')+'.t', 'w')
tfs[case.in_].write('#line 1 "%s"\n' % case.in_) tfs[case.in_].write('#line 1 "%s"\n' % case.in_)
with open(case.in_) as f: with open(case.in_) as f:
for line in f: for line in f:
@@ -554,33 +516,25 @@ class TestSuite:
# write makefiles # write makefiles
with open(self.path + '.mk', 'w') as mk: with open(self.path + '.mk', 'w') as mk:
mk.write(RULES.replace(4*' ', '\t') % dict(path=self.path)) mk.write(RULES.replace(4*' ', '\t'))
mk.write('\n') mk.write('\n')
# add coverage hooks?
if args.get('coverage'):
mk.write(COVERAGE_RULES.replace(4*' ', '\t') % dict(
path=self.path))
mk.write('\n')
# add truely global defines globally # add truely global defines globally
for k, v in sorted(self.defines.items()): for k, v in sorted(self.defines.items()):
mk.write('%s.test: override CFLAGS += -D%s=%r\n' mk.write('%s: override CFLAGS += -D%s=%r\n' % (
% (self.path, k, v)) self.path+'.test', k, v))
for path in tfs: for path in tfs:
if path is None: if path is None:
mk.write('%s: %s | %s\n' % ( mk.write('%s: %s | %s\n' % (
self.path+'.test.c', self.path+'.test.c',
self.toml, self.path,
self.path+'.test.tc')) self.path+'.test.c.t'))
else: else:
mk.write('%s: %s %s | %s\n' % ( mk.write('%s: %s %s | %s\n' % (
self.path+'.'+path.replace('/', '.'), self.path+'.'+path.replace('/', '.'),
self.toml, self.path, path,
path, self.path+'.'+path.replace('/', '.')+'.t'))
self.path+'.'+re.sub('(\.c)?$', '.tc',
path.replace('/', '.'))))
mk.write('\t./scripts/explode_asserts.py $| -o $@\n') mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
self.makefile = self.path + '.mk' self.makefile = self.path + '.mk'
@@ -603,7 +557,7 @@ class TestSuite:
if not args.get('verbose', True): if not args.get('verbose', True):
sys.stdout.write(FAIL) sys.stdout.write(FAIL)
sys.stdout.flush() sys.stdout.flush()
if not args.get('keep_going'): if not args.get('keep_going', False):
if not args.get('verbose', True): if not args.get('verbose', True):
sys.stdout.write('\n') sys.stdout.write('\n')
raise raise
@@ -625,30 +579,30 @@ def main(**args):
# and what class of TestCase to run # and what class of TestCase to run
classes = [] classes = []
if args.get('normal'): if args.get('normal', False):
classes.append(TestCase) classes.append(TestCase)
if args.get('reentrant'): if args.get('reentrant', False):
classes.append(ReentrantTestCase) classes.append(ReentrantTestCase)
if args.get('valgrind'): if args.get('valgrind', False):
classes.append(ValgrindTestCase) classes.append(ValgrindTestCase)
if not classes: if not classes:
classes = [TestCase] classes = [TestCase]
suites = [] suites = []
for testpath in args['test_paths']: for testpath in args['testpaths']:
# optionally specified test case/perm # optionally specified test case/perm
testpath, *filter = testpath.split('#') testpath, *filter = testpath.split('#')
filter = [int(f) for f in filter] filter = [int(f) for f in filter]
# figure out the suite's toml file # figure out the suite's toml file
if os.path.isdir(testpath): if os.path.isdir(testpath):
testpath = testpath + '/*.toml' testpath = testpath + '/test_*.toml'
elif os.path.isfile(testpath): elif os.path.isfile(testpath):
testpath = testpath testpath = testpath
elif testpath.endswith('.toml'): elif testpath.endswith('.toml'):
testpath = TEST_PATHS + '/' + testpath testpath = TESTDIR + '/' + testpath
else: else:
testpath = TEST_PATHS + '/' + testpath + '.toml' testpath = TESTDIR + '/' + testpath + '.toml'
# find tests # find tests
for path in glob.glob(testpath): for path in glob.glob(testpath):
@@ -674,7 +628,7 @@ def main(**args):
list(it.chain.from_iterable(['-f', m] for m in makefiles)) + list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
[target for target in targets]) [target for target in targets])
mpty, spty = pty.openpty() mpty, spty = pty.openpty()
if args.get('verbose'): if args.get('verbose', False):
print(' '.join(shlex.quote(c) for c in cmd)) print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd, stdout=spty, stderr=spty) proc = sp.Popen(cmd, stdout=spty, stderr=spty)
os.close(spty) os.close(spty)
@@ -688,14 +642,14 @@ def main(**args):
break break
raise raise
stdout.append(line) stdout.append(line)
if args.get('verbose'): if args.get('verbose', False):
sys.stdout.write(line) sys.stdout.write(line)
# intercept warnings # intercept warnings
m = re.match( m = re.match(
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$' '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
.format('(?:\033\[[\d;]*.| )*', 'warning'), .format('(?:\033\[[\d;]*.| )*', 'warning'),
line) line)
if m and not args.get('verbose'): if m and not args.get('verbose', False):
try: try:
with open(m.group(1)) as f: with open(m.group(1)) as f:
lineno = int(m.group(2)) lineno = int(m.group(2))
@@ -708,26 +662,27 @@ def main(**args):
except: except:
pass pass
proc.wait() proc.wait()
if proc.returncode != 0: if proc.returncode != 0:
if not args.get('verbose'): if not args.get('verbose', False):
for line in stdout: for line in stdout:
sys.stdout.write(line) sys.stdout.write(line)
sys.exit(-1) sys.exit(-3)
print('built %d test suites, %d test cases, %d permutations' % ( print('built %d test suites, %d test cases, %d permutations' % (
len(suites), len(suites),
sum(len(suite.cases) for suite in suites), sum(len(suite.cases) for suite in suites),
sum(len(suite.perms) for suite in suites))) sum(len(suite.perms) for suite in suites)))
total = 0 filtered = 0
for suite in suites: for suite in suites:
for perm in suite.perms: for perm in suite.perms:
total += perm.shouldtest(**args) filtered += perm.shouldtest(**args)
if total != sum(len(suite.perms) for suite in suites): if filtered != sum(len(suite.perms) for suite in suites):
print('filtered down to %d permutations' % total) print('filtered down to %d permutations' % filtered)
# only requested to build? # only requested to build?
if args.get('build'): if args.get('build', False):
return 0 return 0
print('====== testing ======') print('====== testing ======')
@@ -742,12 +697,15 @@ def main(**args):
failed = 0 failed = 0
for suite in suites: for suite in suites:
for perm in suite.perms: for perm in suite.perms:
if not hasattr(perm, 'result'):
continue
if perm.result == PASS: if perm.result == PASS:
passed += 1 passed += 1
elif isinstance(perm.result, TestFailure): else:
sys.stdout.write( sys.stdout.write(
"\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m " "\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
"{perm} failed\n".format( "{perm} failed with {returncode}\n".format(
perm=perm, path=perm.suite.path, lineno=perm.lineno, perm=perm, path=perm.suite.path, lineno=perm.lineno,
returncode=perm.result.returncode or 0)) returncode=perm.result.returncode or 0))
if perm.result.stdout: if perm.result.stdout:
@@ -765,33 +723,11 @@ def main(**args):
sys.stdout.write('\n') sys.stdout.write('\n')
failed += 1 failed += 1
if args.get('coverage'): if args.get('gdb', False):
# collect coverage info
# why -j1? lcov doesn't work in parallel because of gcov limitations
cmd = (['make', '-j1', '-f', 'Makefile'] +
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
(['COVERAGETARGET=%s' % args['coverage']]
if isinstance(args['coverage'], str) else []) +
[suite.path + '.info' for suite in suites
if any(perm.result == PASS for perm in suite.perms)])
if args.get('verbose'):
print(' '.join(shlex.quote(c) for c in cmd))
proc = sp.Popen(cmd,
stdout=sp.PIPE if not args.get('verbose') else None,
stderr=sp.STDOUT if not args.get('verbose') else None,
universal_newlines=True)
proc.wait()
if proc.returncode != 0:
if not args.get('verbose'):
for line in proc.stdout:
sys.stdout.write(line)
sys.exit(-1)
if args.get('gdb'):
failure = None failure = None
for suite in suites: for suite in suites:
for perm in suite.perms: for perm in suite.perms:
if isinstance(perm.result, TestFailure): if getattr(perm, 'result', PASS) != PASS:
failure = perm.result failure = perm.result
if failure is not None: if failure is not None:
print('======= gdb ======') print('======= gdb ======')
@@ -799,22 +735,20 @@ def main(**args):
failure.case.test(failure=failure, **args) failure.case.test(failure=failure, **args)
sys.exit(0) sys.exit(0)
print('tests passed %d/%d (%.2f%%)' % (passed, total, print('tests passed: %d' % passed)
100*(passed/total if total else 1.0))) print('tests failed: %d' % failed)
print('tests failed %d/%d (%.2f%%)' % (failed, total,
100*(failed/total if total else 1.0)))
return 1 if failed > 0 else 0 return 1 if failed > 0 else 0
if __name__ == "__main__": if __name__ == "__main__":
import argparse import argparse
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Run parameterized tests in various configurations.") description="Run parameterized tests in various configurations.")
parser.add_argument('test_paths', nargs='*', default=[TEST_PATHS], parser.add_argument('testpaths', nargs='*', default=[TESTDIR],
help="Description of test(s) to run. By default, this is all tests \ help="Description of test(s) to run. By default, this is all tests \
found in the \"{0}\" directory. Here, you can specify a different \ found in the \"{0}\" directory. Here, you can specify a different \
directory of tests, a specific file, a suite by name, and even \ directory of tests, a specific file, a suite by name, and even a \
specific test cases and permutations. For example \ specific test case by adding brackets. For example \
\"test_dirs#1\" or \"{0}/test_dirs.toml#1#1\".".format(TEST_PATHS)) \"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR))
parser.add_argument('-D', action='append', default=[], parser.add_argument('-D', action='append', default=[],
help="Overriding parameter definitions.") help="Overriding parameter definitions.")
parser.add_argument('-v', '--verbose', action='store_true', parser.add_argument('-v', '--verbose', action='store_true',
@@ -826,7 +760,7 @@ if __name__ == "__main__":
help="Store disk image in a file.") help="Store disk image in a file.")
parser.add_argument('-b', '--build', action='store_true', parser.add_argument('-b', '--build', action='store_true',
help="Only build the tests, do not execute.") help="Only build the tests, do not execute.")
parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'], parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'],
nargs='?', const='assert', nargs='?', const='assert',
help="Drop into gdb on test failure.") help="Drop into gdb on test failure.")
parser.add_argument('--no-internal', action='store_true', parser.add_argument('--no-internal', action='store_true',
@@ -835,19 +769,10 @@ if __name__ == "__main__":
help="Run tests normally.") help="Run tests normally.")
parser.add_argument('-r', '--reentrant', action='store_true', parser.add_argument('-r', '--reentrant', action='store_true',
help="Run reentrant tests with simulated power-loss.") help="Run reentrant tests with simulated power-loss.")
parser.add_argument('--valgrind', action='store_true', parser.add_argument('-V', '--valgrind', action='store_true',
help="Run non-leaky tests under valgrind to check for memory leaks.") help="Run non-leaky tests under valgrind to check for memory leaks.")
parser.add_argument('--exec', default=[], type=lambda e: e.split(), parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
help="Run tests with another executable prefixed on the command line.") help="Run tests with another executable prefixed on the command line.")
parser.add_argument('--disk', parser.add_argument('-d', '--disk',
help="Specify a file to use for persistent/reentrant tests.") help="Specify a file to use for persistent/reentrant tests.")
parser.add_argument('--coverage', type=lambda x: x if x else True,
nargs='?', const='',
help="Collect coverage information during testing. This uses lcov/gcov \
to accumulate coverage information into *.info files. May also \
a path to a *.info file to accumulate coverage info into.")
parser.add_argument('--build-dir',
help="Build relative to the specified directory instead of the \
current directory.")
sys.exit(main(**vars(parser.parse_args()))) sys.exit(main(**vars(parser.parse_args())))

View File

@@ -323,90 +323,6 @@ code = '''
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # what if we have a bad block during an allocation scan?
in = "lfs.c"
define.LFS_ERASE_CYCLES = 0xffffffff
define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_READERROR'
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// first fill to exhaustion to find available space
lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
lfs_size_t filesize = 0;
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
if (res == LFS_ERR_NOSPC) {
break;
}
filesize += size;
}
lfs_file_close(&lfs, &file) => 0;
// now fill all but a couple of blocks of the filesystem with data
filesize -= 3*LFS_BLOCK_SIZE;
lfs_file_open(&lfs, &file, "pacman", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs_size_t i = 0; i < filesize/size; i++) {
lfs_file_write(&lfs, &file, buffer, size) => size;
}
lfs_file_close(&lfs, &file) => 0;
// also save head of file so we can error during lookahead scan
lfs_block_t fileblock = file.ctz.head;
lfs_unmount(&lfs) => 0;
// remount to force an alloc scan
lfs_mount(&lfs, &cfg) => 0;
// but mark the head of our file as a "bad block", this is force our
// scan to bail early
lfs_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_CORRUPT);
if (res == LFS_ERR_CORRUPT) {
break;
}
}
lfs_file_close(&lfs, &file) => 0;
// now reverse the "bad block" and try to write the file again until we
// run out of space
lfs_testbd_setwear(&cfg, fileblock, 0) => 0;
lfs_file_open(&lfs, &file, "ghost", LFS_O_WRONLY | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "chomp");
size = strlen("chomp");
while (true) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, buffer, size);
assert(res == (lfs_ssize_t)size || res == LFS_ERR_NOSPC);
if (res == LFS_ERR_NOSPC) {
break;
}
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// check that the disk isn't hurt
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "pacman", LFS_O_RDONLY) => 0;
strcpy((char*)buffer, "waka");
size = strlen("waka");
for (lfs_size_t i = 0; i < filesize/size; i++) {
uint8_t rbuffer[4];
lfs_file_read(&lfs, &file, rbuffer, size) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
# Below, I don't like these tests. They're fragile and depend _heavily_ # Below, I don't like these tests. They're fragile and depend _heavily_
# on the geometry of the block device. But they are valuable. Eventually they # on the geometry of the block device. But they are valuable. Eventually they
# should be removed and replaced with generalized tests. # should be removed and replaced with generalized tests.

View File

@@ -1,288 +0,0 @@
# Tests for recovering from conditions which shouldn't normally
# happen during normal operation of littlefs
# invalid pointer tests (outside of block_count)
[[case]] # invalid tail-pointer test
define.TAIL_TYPE = ['LFS_TYPE_HARDTAIL', 'LFS_TYPE_SOFTTAIL']
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// change tail-pointer to invalid pointers
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # invalid dir pointer test
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a dir
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "dir_here") => 0;
lfs_unmount(&lfs) => 0;
// change the dir pointer to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our directory
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("dir_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_DIR, 1, strlen("dir_here"));
assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
// change dir pointer
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, 8),
(lfs_block_t[2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad dir fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dir_here", &info) => 0;
assert(strcmp(info.name, "dir_here") == 0);
assert(info.type == LFS_TYPE_DIR);
lfs_dir_open(&lfs, &dir, "dir_here") => LFS_ERR_CORRUPT;
lfs_stat(&lfs, "dir_here/file_here", &info) => LFS_ERR_CORRUPT;
lfs_dir_open(&lfs, &dir, "dir_here/dir_here") => LFS_ERR_CORRUPT;
lfs_file_open(&lfs, &file, "dir_here/file_here",
LFS_O_RDONLY) => LFS_ERR_CORRUPT;
lfs_file_open(&lfs, &file, "dir_here/file_here",
LFS_O_WRONLY | LFS_O_CREAT) => LFS_ERR_CORRUPT;
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid file pointer test
in = "lfs.c"
define.SIZE = [10, 1000, 100000] # faked file size
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a file
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "file_here",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// change the file pointer to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
// change file pointer
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz)),
&(struct lfs_ctz){0xcccccccc, lfs_tole32(SIZE)}})) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
lfs_file_close(&lfs, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS_BLOCK_SIZE) {
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
}
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid pointer in CTZ skip-list test
define.SIZE = ['2*LFS_BLOCK_SIZE', '3*LFS_BLOCK_SIZE', '4*LFS_BLOCK_SIZE']
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// make a file
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "file_here",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (int i = 0; i < SIZE; i++) {
char c = 'c';
lfs_file_write(&lfs, &file, &c, 1) => 1;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// change pointer in CTZ skip-list to be invalid
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
// make sure id 1 == our file and get our CTZ structure
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_NAME, 1, strlen("file_here")), buffer)
=> LFS_MKTAG(LFS_TYPE_REG, 1, strlen("file_here"));
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
struct lfs_ctz ctz;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x700, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_STRUCT, 1, sizeof(struct lfs_ctz)), &ctz)
=> LFS_MKTAG(LFS_TYPE_CTZSTRUCT, 1, sizeof(struct lfs_ctz));
lfs_ctz_fromle32(&ctz);
// rewrite block to contain bad pointer
uint8_t bbuffer[LFS_BLOCK_SIZE];
cfg.read(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
uint32_t bad = lfs_tole32(0xcccccccc);
memcpy(&bbuffer[0], &bad, sizeof(bad));
memcpy(&bbuffer[4], &bad, sizeof(bad));
cfg.erase(&cfg, ctz.head) => 0;
cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS_BLOCK_SIZE) => 0;
lfs_deinit(&lfs) => 0;
// test that accessing our bad file fails, note there's a number
// of ways to access the dir, some can fail, but some don't
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "file_here", &info) => 0;
assert(strcmp(info.name, "file_here") == 0);
assert(info.type == LFS_TYPE_REG);
assert(info.size == SIZE);
lfs_file_open(&lfs, &file, "file_here", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file, buffer, SIZE) => LFS_ERR_CORRUPT;
lfs_file_close(&lfs, &file) => 0;
// any allocs that traverse CTZ must unfortunately must fail
if (SIZE > 2*LFS_BLOCK_SIZE) {
lfs_mkdir(&lfs, "dir_here") => LFS_ERR_CORRUPT;
}
lfs_unmount(&lfs) => 0;
'''
[[case]] # invalid gstate pointer
define.INVALSET = [0x3, 0x1, 0x2]
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// create an invalid gstate
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){
(INVALSET & 0x1) ? 0xcccccccc : 0,
(INVALSET & 0x2) ? 0xcccccccc : 0});
lfs_dir_commit(&lfs, &mdir, NULL, 0) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
// mount may not fail, but our first alloc should fail when
// we try to fix the gstate
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "should_fail") => LFS_ERR_CORRUPT;
lfs_unmount(&lfs) => 0;
'''
# cycle detection/recovery tests
[[case]] # metadata-pair threaded-list loop test
in = "lfs.c"
code = '''
// create littlefs
lfs_format(&lfs, &cfg) => 0;
// change tail-pointer to point to ourself
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 2-length loop test
in = "lfs.c"
code = '''
// create littlefs with child dir
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "child") => 0;
lfs_unmount(&lfs) => 0;
// find child
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_block_t pair[2];
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs_pair_fromle32(pair);
// change tail-pointer to point to root
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8),
(lfs_block_t[2]){0, 1}})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''
[[case]] # metadata-pair threaded-list 1-length child loop test
in = "lfs.c"
code = '''
// create littlefs with child dir
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "child") => 0;
lfs_unmount(&lfs) => 0;
// find child
lfs_init(&lfs, &cfg) => 0;
lfs_mdir_t mdir;
lfs_block_t pair[2];
lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0;
lfs_dir_get(&lfs, &mdir,
LFS_MKTAG(0x7ff, 0x3ff, 0),
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
=> LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 1, sizeof(pair));
lfs_pair_fromle32(pair);
// change tail-pointer to point to ourself
lfs_dir_fetch(&lfs, &mdir, pair) => 0;
lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS(
{LFS_MKTAG(LFS_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
lfs_deinit(&lfs) => 0;
// test that mount fails gracefully
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
'''

View File

@@ -350,116 +350,117 @@ exhausted:
LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
''' '''
[[case]] # test that we wear blocks roughly evenly # TODO fixme
define.LFS_ERASE_CYCLES = 0xffffffff #[[case]] # test that we wear blocks roughly evenly
define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster #define.LFS_ERASE_CYCLES = 0xffffffff
define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1] #define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster
define.CYCLES = 100 #define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1]
define.FILES = 10 #define.CYCLES = 100
if = 'LFS_BLOCK_CYCLES < CYCLES/10' #define.FILES = 10
code = ''' #if = 'LFS_BLOCK_CYCLES < CYCLES/10'
lfs_format(&lfs, &cfg) => 0; #code = '''
lfs_mount(&lfs, &cfg) => 0; # lfs_format(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "roadrunner") => 0; # lfs_mount(&lfs, &cfg) => 0;
lfs_unmount(&lfs) => 0; # lfs_mkdir(&lfs, "roadrunner") => 0;
# lfs_unmount(&lfs) => 0;
uint32_t cycle = 0; #
while (cycle < CYCLES) { # uint32_t cycle = 0;
lfs_mount(&lfs, &cfg) => 0; # while (cycle < CYCLES) {
for (uint32_t i = 0; i < FILES; i++) { # lfs_mount(&lfs, &cfg) => 0;
// chose name, roughly random seed, and random 2^n size # for (uint32_t i = 0; i < FILES; i++) {
sprintf(path, "roadrunner/test%d", i); # // chose name, roughly random seed, and random 2^n size
srand(cycle * i); # sprintf(path, "roadrunner/test%d", i);
size = 1 << 4; //((rand() % 10)+2); # srand(cycle * i);
# size = 1 << 4; //((rand() % 10)+2);
lfs_file_open(&lfs, &file, path, #
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; # lfs_file_open(&lfs, &file, path,
# LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
for (lfs_size_t j = 0; j < size; j++) { #
char c = 'a' + (rand() % 26); # for (lfs_size_t j = 0; j < size; j++) {
lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); # char c = 'a' + (rand() % 26);
assert(res == 1 || res == LFS_ERR_NOSPC); # lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1);
if (res == LFS_ERR_NOSPC) { # assert(res == 1 || res == LFS_ERR_NOSPC);
err = lfs_file_close(&lfs, &file); # if (res == LFS_ERR_NOSPC) {
assert(err == 0 || err == LFS_ERR_NOSPC); # err = lfs_file_close(&lfs, &file);
lfs_unmount(&lfs) => 0; # assert(err == 0 || err == LFS_ERR_NOSPC);
goto exhausted; # lfs_unmount(&lfs) => 0;
} # goto exhausted;
} # }
# }
err = lfs_file_close(&lfs, &file); #
assert(err == 0 || err == LFS_ERR_NOSPC); # err = lfs_file_close(&lfs, &file);
if (err == LFS_ERR_NOSPC) { # assert(err == 0 || err == LFS_ERR_NOSPC);
lfs_unmount(&lfs) => 0; # if (err == LFS_ERR_NOSPC) {
goto exhausted; # lfs_unmount(&lfs) => 0;
} # goto exhausted;
} # }
# }
for (uint32_t i = 0; i < FILES; i++) { #
// check for errors # for (uint32_t i = 0; i < FILES; i++) {
sprintf(path, "roadrunner/test%d", i); # // check for errors
srand(cycle * i); # sprintf(path, "roadrunner/test%d", i);
size = 1 << 4; //((rand() % 10)+2); # srand(cycle * i);
# size = 1 << 4; //((rand() % 10)+2);
lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; #
for (lfs_size_t j = 0; j < size; j++) { # lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0;
char c = 'a' + (rand() % 26); # for (lfs_size_t j = 0; j < size; j++) {
char r; # char c = 'a' + (rand() % 26);
lfs_file_read(&lfs, &file, &r, 1) => 1; # char r;
assert(r == c); # lfs_file_read(&lfs, &file, &r, 1) => 1;
} # assert(r == c);
# }
lfs_file_close(&lfs, &file) => 0; #
} # lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0; # }
# lfs_unmount(&lfs) => 0;
cycle += 1; #
} # cycle += 1;
# }
exhausted: #
// should still be readable #exhausted:
lfs_mount(&lfs, &cfg) => 0; # // should still be readable
for (uint32_t i = 0; i < FILES; i++) { # lfs_mount(&lfs, &cfg) => 0;
// check for errors # for (uint32_t i = 0; i < FILES; i++) {
sprintf(path, "roadrunner/test%d", i); # // check for errors
lfs_stat(&lfs, path, &info) => 0; # sprintf(path, "roadrunner/test%d", i);
} # lfs_stat(&lfs, path, &info) => 0;
lfs_unmount(&lfs) => 0; # }
# lfs_unmount(&lfs) => 0;
LFS_WARN("completed %d cycles", cycle); #
# LFS_WARN("completed %d cycles", cycle);
// check the wear on our block device #
lfs_testbd_wear_t minwear = -1; # // check the wear on our block device
lfs_testbd_wear_t totalwear = 0; # lfs_testbd_wear_t minwear = -1;
lfs_testbd_wear_t maxwear = 0; # lfs_testbd_wear_t totalwear = 0;
// skip 0 and 1 as superblock movement is intentionally avoided # lfs_testbd_wear_t maxwear = 0;
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { # // skip 0 and 1 as superblock movement is intentionally avoided
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); # for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
printf("%08x: wear %d\n", b, wear); # lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
assert(wear >= 0); # printf("%08x: wear %d\n", b, wear);
if (wear < minwear) { # assert(wear >= 0);
minwear = wear; # if (wear < minwear) {
} # minwear = wear;
if (wear > maxwear) { # }
maxwear = wear; # if (wear > maxwear) {
} # maxwear = wear;
totalwear += wear; # }
} # totalwear += wear;
lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT; # }
LFS_WARN("max wear: %d cycles", maxwear); # lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT;
LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT); # LFS_WARN("max wear: %d cycles", maxwear);
LFS_WARN("min wear: %d cycles", minwear); # LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT);
# LFS_WARN("min wear: %d cycles", minwear);
// find standard deviation^2 #
lfs_testbd_wear_t dev2 = 0; # // find standard deviation^2
for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { # lfs_testbd_wear_t dev2 = 0;
lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); # for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) {
assert(wear >= 0); # lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b);
lfs_testbd_swear_t diff = wear - avgwear; # assert(wear >= 0);
dev2 += diff*diff; # lfs_testbd_swear_t diff = wear - avgwear;
} # dev2 += diff*diff;
dev2 /= totalwear; # }
LFS_WARN("std dev^2: %d", dev2); # dev2 /= totalwear;
assert(dev2 < 8); # LFS_WARN("std dev^2: %d", dev2);
''' # assert(dev2 < 8);
#'''

View File

@@ -59,7 +59,7 @@ code = '''
[[case]] # reentrant testing for orphans, basically just spam mkdir/remove [[case]] # reentrant testing for orphans, basically just spam mkdir/remove
reentrant = true reentrant = true
# TODO fix this case, caused by non-DAG trees # TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' #if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [ define = [
{FILES=6, DEPTH=1, CYCLES=20}, {FILES=6, DEPTH=1, CYCLES=20},
{FILES=26, DEPTH=1, CYCLES=20}, {FILES=26, DEPTH=1, CYCLES=20},

View File

@@ -31,7 +31,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
strcmp(info.name, path) => 0; assert(strcmp(info.name, path) == 0);
} }
lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0; lfs_dir_close(&lfs, &dir) => 0;
@@ -54,7 +54,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
strcmp(info.name, path) => 0; assert(strcmp(info.name, path) == 0);
} }
lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0; lfs_dir_close(&lfs, &dir) => 0;
@@ -97,7 +97,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
strcmp(info.name, path) => 0; assert(strcmp(info.name, path) == 0);
info.size => 0; info.size => 0;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -113,7 +113,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
strcmp(info.name, path) => 0; assert(strcmp(info.name, path) == 0);
info.size => 2; info.size => 2;
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i); sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
@@ -129,7 +129,7 @@ code = '''
for (int i = 0; i < COUNT; i++) { for (int i = 0; i < COUNT; i++) {
sprintf(path, "test%03d_loooooooooooooooooong_name", i); sprintf(path, "test%03d_loooooooooooooooooong_name", i);
lfs_dir_read(&lfs, &dir, &info) => 1; lfs_dir_read(&lfs, &dir, &info) => 1;
strcmp(info.name, path) => 0; assert(strcmp(info.name, path) == 0);
info.size => 2; info.size => 2;
} }
lfs_dir_read(&lfs, &dir, &info) => 0; lfs_dir_read(&lfs, &dir, &info) => 0;
@@ -143,12 +143,90 @@ code = '''
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # non-DAG tree test
define.LFS_BLOCK_CYCLES = [8, 1]
define.N = [10, 100, 1000]
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
// first create directories
lfs_mkdir(&lfs, "child_1") => 0;
lfs_mkdir(&lfs, "child_2") => 0;
// then move the second child under the first,
// this creates a cycle since the second child should have been
// inserted before the first
lfs_rename(&lfs, "child_2", "child_1/child_2") => 0;
// now try to force second child to relocate
lfs_file_open(&lfs, &file, "child_1/child_2/grandchild",
LFS_O_WRONLY | LFS_O_CREAT) => 0;
size = 0;
for (int i = 0; i < N; i++) {
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
sprintf((char*)buffer, "%d", i);
size = strlen((char*)buffer);
lfs_file_write(&lfs, &file, buffer, size) => size;
lfs_file_sync(&lfs, &file) => 0;
}
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs);
// check that nothing broke
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir, "/") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "child_1") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/child_1") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "child_2") == 0);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_dir_open(&lfs, &dir, "/child_1/child_2") => 0;
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, ".") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_DIR);
assert(strcmp(info.name, "..") == 0);
lfs_dir_read(&lfs, &dir, &info) => 1;
assert(info.type == LFS_TYPE_REG);
assert(strcmp(info.name, "grandchild") == 0);
assert(info.size == size);
lfs_dir_read(&lfs, &dir, &info) => 0;
lfs_dir_close(&lfs, &dir) => 0;
lfs_file_open(&lfs, &file, "child_1/child_2/grandchild",
LFS_O_RDONLY) => 0;
uint8_t rbuffer[1024];
lfs_file_read(&lfs, &file, rbuffer, sizeof(rbuffer)) => size;
assert(memcmp(rbuffer, buffer, size) == 0);
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''
[[case]] # reentrant testing for relocations, this is the same as the [[case]] # reentrant testing for relocations, this is the same as the
# orphan testing, except here we also set block_cycles so that # orphan testing, except here we also set block_cycles so that
# almost every tree operation needs a relocation # almost every tree operation needs a relocation
reentrant = true reentrant = true
# TODO fix this case, caused by non-DAG trees # TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' #if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [ define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, {FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
@@ -210,7 +288,7 @@ code = '''
[[case]] # reentrant testing for relocations, but now with random renames! [[case]] # reentrant testing for relocations, but now with random renames!
reentrant = true reentrant = true
# TODO fix this case, caused by non-DAG trees # TODO fix this case, caused by non-DAG trees
if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' #if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)'
define = [ define = [
{FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, {FILES=6, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},
{FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1},

View File

@@ -27,55 +27,41 @@ code = '''
''' '''
[[case]] # expanding superblock [[case]] # expanding superblock
define.LFS_BLOCK_CYCLES = [32, 33, 1] define.BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000] define.N = [10, 100, 1000]
code = ''' code = '''
lfs_format(&lfs, &cfg) => 0; lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
lfs_file_open(&lfs, &file, "dummy", lfs_mkdir(&lfs, "dummy") => 0;
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0; lfs_remove(&lfs, "dummy") => 0;
} }
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
// one last check after power-cycle // one last check after power-cycle
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "dummy", lfs_mkdir(&lfs, "dummy") => 0;
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # expanding superblock with power cycle [[case]] # expanding superblock with power cycle
define.LFS_BLOCK_CYCLES = [32, 33, 1] define.BLOCK_CYCLES = [32, 33, 1]
define.N = [10, 100, 1000] define.N = [10, 100, 1000]
code = ''' code = '''
lfs_format(&lfs, &cfg) => 0; lfs_format(&lfs, &cfg) => 0;
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
// remove lingering dummy? // remove lingering dummy?
err = lfs_stat(&lfs, "dummy", &info); err = lfs_remove(&lfs, "dummy");
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0); lfs_mkdir(&lfs, "dummy") => 0;
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
} }
@@ -83,12 +69,11 @@ code = '''
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # reentrant expanding superblock [[case]] # reentrant expanding superblock
define.LFS_BLOCK_CYCLES = [2, 1] define.BLOCK_CYCLES = [2, 1]
define.N = 24 define.N = 24
reentrant = true reentrant = true
code = ''' code = '''
@@ -100,20 +85,12 @@ code = '''
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
// remove lingering dummy? // remove lingering dummy?
err = lfs_stat(&lfs, "dummy", &info); err = lfs_remove(&lfs, "dummy");
assert(err == 0 || (err == LFS_ERR_NOENT && i == 0)); assert(err == 0 || (err == LFS_ERR_NOENT && i == 0));
if (!err) {
assert(strcmp(info.name, "dummy") == 0); lfs_mkdir(&lfs, "dummy") => 0;
assert(info.type == LFS_TYPE_REG);
lfs_remove(&lfs, "dummy") => 0;
}
lfs_file_open(&lfs, &file, "dummy",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
} }
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
@@ -122,6 +99,5 @@ code = '''
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "dummy", &info) => 0; lfs_stat(&lfs, "dummy", &info) => 0;
assert(strcmp(info.name, "dummy") == 0); assert(strcmp(info.name, "dummy") == 0);
assert(info.type == LFS_TYPE_REG);
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''

View File

@@ -392,48 +392,3 @@ code = '''
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
''' '''
[[case]] # noop truncate
define.MEDIUMSIZE = [32, 2048]
code = '''
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "baldynoop",
LFS_O_RDWR | LFS_O_CREAT) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_write(&lfs, &file, buffer, size) => size;
// this truncate should do nothing
lfs_file_truncate(&lfs, &file, j+size) => 0;
}
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
lfs_file_seek(&lfs, &file, 0, LFS_SEEK_SET) => 0;
// should do nothing again
lfs_file_truncate(&lfs, &file, MEDIUMSIZE) => 0;
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
// still there after reboot?
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file, "baldynoop", LFS_O_RDWR) => 0;
lfs_file_size(&lfs, &file) => MEDIUMSIZE;
for (lfs_off_t j = 0; j < MEDIUMSIZE; j += size) {
lfs_file_read(&lfs, &file, buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
lfs_file_read(&lfs, &file, buffer, size) => 0;
lfs_file_close(&lfs, &file) => 0;
lfs_unmount(&lfs) => 0;
'''