Merge commit '451c3d137fbd49ce5e00e765af66c16bb5169ee9' into lfs-update

pull/6179/head
Christopher Haster 2018-02-22 18:43:04 -06:00
commit 453a1b6815
14 changed files with 855 additions and 272 deletions

View File

@ -0,0 +1,9 @@
# Compilation output
*.o
*.d
*.a
# Testing things
blocks/
lfs
test.c

View File

@ -1,22 +1,121 @@
# Environment variables
env:
global:
- CFLAGS=-Werror
# Common test script
script: script:
# make sure example can at least compile # make sure example can at least compile
- sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c && - sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c &&
CFLAGS=' make all CFLAGS+="
-Duser_provided_block_device_read=NULL -Duser_provided_block_device_read=NULL
-Duser_provided_block_device_prog=NULL -Duser_provided_block_device_prog=NULL
-Duser_provided_block_device_erase=NULL -Duser_provided_block_device_erase=NULL
-Duser_provided_block_device_sync=NULL -Duser_provided_block_device_sync=NULL
-include stdio.h -Werror' make all size -include stdio.h"
# run tests # run tests
- make test QUIET=1 - make test QUIET=1
# run tests with a few different configurations # run tests with a few different configurations
- CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test QUIET=1 - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1"
- CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test QUIET=1 - make test QUIET=1 CFLAGS+="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"
- CFLAGS="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048" make test QUIET=1 - make test QUIET=1 CFLAGS+="-DLFS_BLOCK_COUNT=1023 -DLFS_LOOKAHEAD=2048"
- make clean test QUIET=1 CFLAGS+="-DLFS_NO_INTRINSICS"
# compile and find the code size with the smallest configuration
- make clean size
OBJ="$(ls lfs*.o | tr '\n' ' ')"
CFLAGS+="-DLFS_NO{ASSERT,DEBUG,WARN,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 https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
| jq -re "select(.sha != \"$TRAVIS_COMMIT\")
| .statuses[] | select(.context == \"$STAGE/$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
# CI matrix
jobs:
include:
# native testing
- stage: test
env:
- STAGE=test
- NAME=littlefs-x86
# cross-compile with ARM (thumb mode)
- stage: test
env:
- STAGE=test
- NAME=littlefs-arm
- CC="arm-linux-gnueabi-gcc --static -mthumb"
- EXEC="qemu-arm"
install:
- sudo apt-get install gcc-arm-linux-gnueabi qemu-user
- arm-linux-gnueabi-gcc --version
- qemu-arm -version
# cross-compile with PowerPC
- stage: test
env:
- STAGE=test
- NAME=littlefs-powerpc
- CC="powerpc-linux-gnu-gcc --static"
- EXEC="qemu-ppc"
install:
- sudo apt-get install gcc-powerpc-linux-gnu qemu-user
- powerpc-linux-gnu-gcc --version
- qemu-ppc -version
# cross-compile with MIPS
- stage: test
env:
- STAGE=test
- NAME=littlefs-mips
- CC="mips-linux-gnu-gcc --static"
- EXEC="qemu-mips"
install:
- sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main universe"
- sudo apt-get -qq update
- sudo apt-get install gcc-mips-linux-gnu qemu-user
- mips-linux-gnu-gcc --version
- qemu-mips -version
# self-host with littlefs-fuse for fuzz test # self-host with littlefs-fuse for fuzz test
- stage: test
env:
- STAGE=test
- NAME=littlefs-fuse
install:
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse
- fusermount -V
- gcc --version
before_script:
# 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=2048 of=disk
- losetup /dev/loop0 disk
script:
# self-host test
- make -C littlefs-fuse - make -C littlefs-fuse
- littlefs-fuse/lfs --format /dev/loop0 - littlefs-fuse/lfs --format /dev/loop0
@ -27,21 +126,98 @@ script:
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs - cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs - cd mount/littlefs
- ls - ls
- make -B test_dirs QUIET=1 - make -B test_dirs test_files QUIET=1
# Automatically update releases
- stage: deploy
env:
- STAGE=deploy
- NAME=deploy
script:
# Update tag for 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)))
- LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR"
- echo "littlefs version $LFS_VERSION"
- |
curl -u $GEKY_BOT_RELEASES -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \
-d "{
\"ref\": \"refs/tags/$LFS_VERSION\",
\"sha\": \"$TRAVIS_COMMIT\"
}"
- |
curl -f -u $GEKY_BOT_RELEASES -X PATCH \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/$LFS_VERSION \
-d "{
\"sha\": \"$TRAVIS_COMMIT\"
}"
# Create release notes from commits
- LFS_PREV_VERSION="v$LFS_VERSION_MAJOR.$(($LFS_VERSION_MINOR-1))"
- |
if [ $(git tag -l "$LFS_PREV_VERSION") ]
then
curl -u $GEKY_BOT_RELEASES -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
-d "{
\"tag_name\": \"$LFS_VERSION\",
\"name\": \"$LFS_VERSION\"
}"
RELEASE=$(
curl -f https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/tags/$LFS_VERSION
)
CHANGES=$(
git log --oneline $LFS_PREV_VERSION.. --grep='^Merge' --invert-grep
)
curl -f -u $GEKY_BOT_RELEASES -X PATCH \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/$(
jq -r '.id' <<< "$RELEASE"
) \
-d "$(
jq -s '{
"body": ((.[0] // "" | sub("(?<=\n)#+ Changes.*"; ""; "mi"))
+ "### Changes\n\n" + .[1])
}' <(jq '.body' <<< "$RELEASE") <(jq -sR '.' <<< "$CHANGES")
)"
fi
# Manage statuses
before_install: before_install:
- fusermount -V - |
- gcc --version curl -u $GEKY_BOT_STATUSES -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"$STAGE/$NAME\",
\"state\": \"pending\",
\"description\": \"${STATUS:-In progress}\",
\"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\"
}"
install: after_failure:
- sudo apt-get install libfuse-dev - |
- git clone --depth 1 https://github.com/geky/littlefs-fuse curl -u $GEKY_BOT_STATUSES -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"$STAGE/$NAME\",
\"state\": \"failure\",
\"description\": \"${STATUS:-Failed}\",
\"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\"
}"
before_script: after_success:
- rm -rf littlefs-fuse/littlefs/* - |
- cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs curl -u $GEKY_BOT_STATUSES -X POST \
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
-d "{
\"context\": \"$STAGE/$NAME\",
\"state\": \"success\",
\"description\": \"${STATUS:-Passed}\",
\"target_url\": \"https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID\"
}"
- mkdir mount # Job control
- sudo chmod a+rw /dev/loop0 stages:
- dd if=/dev/zero bs=512 count=2048 of=disk - name: test
- losetup /dev/loop0 disk - name: deploy
if: branch = master

View File

@ -27,16 +27,17 @@ cheap and can be very granular. For NOR flash specifically, byte-level
programs are quite common. Erasing, however, requires an expensive operation programs are quite common. Erasing, however, requires an expensive operation
that forces the state of large blocks of memory to reset in a destructive that forces the state of large blocks of memory to reset in a destructive
reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory) reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory)
has more information if you are interesting in how this works. has more information if you are interested in how this works.
This leaves us with an interesting set of limitations that can be simplified This leaves us with an interesting set of limitations that can be simplified
to three strong requirements: to three strong requirements:
1. **Power-loss resilient** - This is the main goal of the littlefs and the 1. **Power-loss resilient** - This is the main goal of the littlefs and the
focus of this project. Embedded systems are usually designed without a focus of this project.
shutdown routine and a notable lack of user interface for recovery, so
filesystems targeting embedded systems must be prepared to lose power at Embedded systems are usually designed without a shutdown routine and a
any given time. notable lack of user interface for recovery, so filesystems targeting
embedded systems must be prepared to lose power at any given time.
Despite this state of things, there are very few embedded filesystems that Despite this state of things, there are very few embedded filesystems that
handle power loss in a reasonable manner, and most can become corrupted if handle power loss in a reasonable manner, and most can become corrupted if
@ -52,7 +53,8 @@ to three strong requirements:
which stores a file allocation table (FAT) at a specific offset from the which stores a file allocation table (FAT) at a specific offset from the
beginning of disk. Every block allocation will update this table, and after beginning of disk. Every block allocation will update this table, and after
100,000 updates, the block will likely go bad, rendering the filesystem 100,000 updates, the block will likely go bad, rendering the filesystem
unusable even if there are many more erase cycles available on the storage. unusable even if there are many more erase cycles available on the storage
as a whole.
3. **Bounded RAM/ROM** - Even with the design difficulties presented by the 3. **Bounded RAM/ROM** - Even with the design difficulties presented by the
previous two limitations, we have already seen several flash filesystems previous two limitations, we have already seen several flash filesystems
@ -72,7 +74,7 @@ to three strong requirements:
## Existing designs? ## Existing designs?
There are of course, many different existing filesystem. Heres a very rough There are of course, many different existing filesystem. Here is a very rough
summary of the general ideas behind some of them. summary of the general ideas behind some of them.
Most of the existing filesystems fall into the one big category of filesystem Most of the existing filesystems fall into the one big category of filesystem
@ -80,7 +82,7 @@ designed in the early days of spinny magnet disks. While there is a vast amount
of interesting technology and ideas in this area, the nature of spinny magnet of interesting technology and ideas in this area, the nature of spinny magnet
disks encourage properties, such as grouping writes near each other, that don't disks encourage properties, such as grouping writes near each other, that don't
make as much sense on recent storage types. For instance, on flash, write make as much sense on recent storage types. For instance, on flash, write
locality is not important and can actually increase wear destructively. locality is not important and can actually increase wear.
One of the most popular designs for flash filesystems is called the One of the most popular designs for flash filesystems is called the
[logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system). [logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system).
@ -94,7 +96,7 @@ filesystem can easily be designed to be resilient to power loss. The
journaling component of most modern day filesystems is actually a reduced journaling component of most modern day filesystems is actually a reduced
form of a logging filesystem. However, logging filesystems have a difficulty form of a logging filesystem. However, logging filesystems have a difficulty
scaling as the size of storage increases. And most filesystems compensate by scaling as the size of storage increases. And most filesystems compensate by
caching large parts of the filesystem in RAM, a strategy that is unavailable caching large parts of the filesystem in RAM, a strategy that is inappropriate
for embedded systems. for embedded systems.
Another interesting filesystem design technique is that of [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write). Another interesting filesystem design technique is that of [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write).
@ -107,14 +109,14 @@ where the COW data structures are synchronized.
## Metadata pairs ## Metadata pairs
The core piece of technology that provides the backbone for the littlefs is The core piece of technology that provides the backbone for the littlefs is
the concept of metadata pairs. The key idea here, is that any metadata that the concept of metadata pairs. The key idea here is that any metadata that
needs to be updated atomically is stored on a pair of blocks tagged with needs to be updated atomically is stored on a pair of blocks tagged with
a revision count and checksum. Every update alternates between these two a revision count and checksum. Every update alternates between these two
pairs, so that at any time there is always a backup containing the previous pairs, so that at any time there is always a backup containing the previous
state of the metadata. state of the metadata.
Consider a small example where each metadata pair has a revision count, Consider a small example where each metadata pair has a revision count,
a number as data and the xor of the block as a quick checksum. If a number as data, and the XOR of the block as a quick checksum. If
we update the data to a value of 9, and then to a value of 5, here is we update the data to a value of 9, and then to a value of 5, here is
what the pair of blocks may look like after each update: what the pair of blocks may look like after each update:
``` ```
@ -149,7 +151,7 @@ check our checksum, we notice that block 1 was corrupted. So we fall back to
block 2 and use the value 9. block 2 and use the value 9.
Using this concept, the littlefs is able to update metadata blocks atomically. Using this concept, the littlefs is able to update metadata blocks atomically.
There are a few other tweaks, such as using a 32 bit crc and using sequence There are a few other tweaks, such as using a 32 bit CRC and using sequence
arithmetic to handle revision count overflow, but the basic concept arithmetic to handle revision count overflow, but the basic concept
is the same. These metadata pairs define the backbone of the littlefs, and the is the same. These metadata pairs define the backbone of the littlefs, and the
rest of the filesystem is built on top of these atomic updates. rest of the filesystem is built on top of these atomic updates.
@ -184,7 +186,7 @@ Here is what updating a one-block file may look like:
update data in file update metadata pair update data in file update metadata pair
``` ```
It doesn't matter if we lose power while writing block 5 with the new data It doesn't matter if we lose power while writing new data to block 5,
because the old data remains unmodified in block 4. This example also because the old data remains unmodified in block 4. This example also
highlights how the atomic updates of the metadata blocks provide a highlights how the atomic updates of the metadata blocks provide a
synchronization barrier for the rest of the littlefs. synchronization barrier for the rest of the littlefs.
@ -206,10 +208,10 @@ files in filesystems. Of these, the littlefs uses a rather unique [COW](https://
data structure that allows the filesystem to reuse unmodified parts of the data structure that allows the filesystem to reuse unmodified parts of the
file without additional metadata pairs. file without additional metadata pairs.
First, let's consider storing files in a simple linked-list. What happens when First lets consider storing files in a simple linked-list. What happens when we
we append a block? We have to change the last block in the linked-list to point append a block? We have to change the last block in the linked-list to point
to this new block, which means we have to copy out the last block and change to this new block, which means we have to copy out the last block, and change
the second-to-last block and then the third-to-last and so on until we've the second-to-last block, and then the third-to-last, and so on until we've
copied out the entire file. copied out the entire file.
``` ```
@ -239,9 +241,9 @@ Exhibit B: A backwards linked-list
'--------' '--------' '--------' '--------' '--------' '--------' '--------' '--------' '--------' '--------' '--------' '--------'
``` ```
However, a backward linked-list does come with a rather glaring problem. However, a backwards linked-list does come with a rather glaring problem.
Iterating over a file _in order_ has a runtime of O(n^2). Gah! A quadratic Iterating over a file _in order_ has a runtime cost of O(n^2). Gah! A quadratic
runtime to just _read_ a file? That's awful. Keep in mind reading files are runtime to just _read_ a file? That's awful. Keep in mind reading files is
usually the most common filesystem operation. usually the most common filesystem operation.
To avoid this problem, the littlefs uses a multilayered linked-list. For To avoid this problem, the littlefs uses a multilayered linked-list. For
@ -266,7 +268,7 @@ Exhibit C: A backward CTZ skip-list
``` ```
The additional pointers allow us to navigate the data-structure on disk The additional pointers allow us to navigate the data-structure on disk
much more efficiently than in a single linked-list. much more efficiently than in a singly linked-list.
Taking exhibit C, for example, here is the path from data block 5 to data Taking exhibit C, for example, here is the path from data block 5 to data
block 1. You can see how data block 3 was completely skipped: block 1. You can see how data block 3 was completely skipped:
@ -291,13 +293,13 @@ We can find the runtime complexity by looking at the path to any block from
the block containing the most pointers. Every step along the path divides the block containing the most pointers. Every step along the path divides
the search space for the block in half. This gives us a runtime of O(log n). the search space for the block in half. This gives us a runtime of O(log n).
To get to the block with the most pointers, we can perform the same steps To get to the block with the most pointers, we can perform the same steps
backward, which puts the runtime at O(2logn) = O(logn). The interesting backwards, which puts the runtime at O(2 log n) = O(log n). The interesting
part about this data structure is that this optimal path occurs naturally part about this data structure is that this optimal path occurs naturally
if we greedily choose the pointer that covers the most distance without passing if we greedily choose the pointer that covers the most distance without passing
our target block. our target block.
So now we have a representation of files that can be appended trivially with So now we have a representation of files that can be appended trivially with
a runtime of O(1) and can be read with a worst case runtime of O(nlogn). a runtime of O(1), and can be read with a worst case runtime of O(n log n).
Given that the the runtime is also divided by the amount of data we can store Given that the the runtime is also divided by the amount of data we can store
in a block, this is pretty reasonable. in a block, this is pretty reasonable.
@ -379,8 +381,8 @@ unintuitive property:
![mindblown](https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29&plus;1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29) ![mindblown](https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29&plus;1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29)
where: where:
ctz(i) = the number of trailing bits that are 0 in i ctz(x) = the number of trailing bits that are 0 in x
popcount(i) = the number of bits that are 1 in i popcount(x) = the number of bits that are 1 in x
It's a bit bewildering that these two seemingly unrelated bitwise instructions It's a bit bewildering that these two seemingly unrelated bitwise instructions
are related by this property. But if we start to dissect this equation, we can are related by this property. But if we start to dissect this equation, we can
@ -410,8 +412,7 @@ a bit to avoid integer overflow:
![formulaforoff](https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29) ![formulaforoff](https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29)
The solution involves quite a bit of math, but computers are very good at math. The solution involves quite a bit of math, but computers are very good at math.
We can now solve for the block index plus offset while only needed to store the Now we can solve for both the block index and offset from the file size in O(1).
file size in O(1).
Here is what it might look like to update a file stored with a CTZ skip-list: Here is what it might look like to update a file stored with a CTZ skip-list:
``` ```
@ -500,6 +501,7 @@ scanned to find the most recent free list, but once the list is found, the
state of all free blocks becomes known. state of all free blocks becomes known.
However, this approach had several issues: However, this approach had several issues:
- There was a lot of nuanced logic for adding blocks to the free list without - There was a lot of nuanced logic for adding blocks to the free list without
modifying the blocks because the blocks remain active until the metadata is modifying the blocks because the blocks remain active until the metadata is
updated. updated.
@ -773,7 +775,7 @@ deorphan step that simply iterates through every directory in the linked-list
and checks it against every directory entry in the filesystem to see if it and checks it against every directory entry in the filesystem to see if it
has a parent. The deorphan step occurs on the first block allocation after has a parent. The deorphan step occurs on the first block allocation after
boot, so orphans should never cause the littlefs to run out of storage boot, so orphans should never cause the littlefs to run out of storage
prematurely. Note that the deorphan step never needs to run in a readonly prematurely. Note that the deorphan step never needs to run in a read-only
filesystem. filesystem.
## The move problem ## The move problem
@ -883,7 +885,7 @@ a power loss will occur during filesystem activity. We still need to handle
the condition, but runtime during a power loss takes a back seat to the runtime the condition, but runtime during a power loss takes a back seat to the runtime
during normal operations. during normal operations.
So what littlefs does is unelegantly simple. When littlefs moves a file, it So what littlefs does is inelegantly simple. When littlefs moves a file, it
marks the file as "moving". This is stored as a single bit in the directory marks the file as "moving". This is stored as a single bit in the directory
entry and doesn't take up much space. Then littlefs moves the directory, entry and doesn't take up much space. Then littlefs moves the directory,
finishing with the complete remove of the "moving" directory entry. finishing with the complete remove of the "moving" directory entry.
@ -979,7 +981,7 @@ if it exists elsewhere in the filesystem.
So now that we have all of the pieces of a filesystem, we can look at a more So now that we have all of the pieces of a filesystem, we can look at a more
subtle attribute of embedded storage: The wear down of flash blocks. subtle attribute of embedded storage: The wear down of flash blocks.
The first concern for the littlefs is that prefectly valid blocks can suddenly The first concern for the littlefs, is that perfectly valid blocks can suddenly
become unusable. As a nice side-effect of using a COW data-structure for files, become unusable. As a nice side-effect of using a COW data-structure for files,
we can simply move on to a different block when a file write fails. All we can simply move on to a different block when a file write fails. All
modifications to files are performed in copies, so we will only replace the modifications to files are performed in copies, so we will only replace the
@ -1152,8 +1154,8 @@ develops errors and needs to be moved.
## Wear leveling ## Wear leveling
The second concern for the littlefs is that blocks in the filesystem may wear The second concern for the littlefs is that blocks in the filesystem may wear
unevenly. In this situation, a filesystem may meet an early demise whe, unevenly. In this situation, a filesystem may meet an early demise where
there are no more noncorrupted blocks that aren't in use. It's common to there are no more non-corrupted blocks that aren't in use. It's common to
have files that were written once and left unmodified, wasting the potential have files that were written once and left unmodified, wasting the potential
erase cycles of the blocks it sits on. erase cycles of the blocks it sits on.
@ -1171,7 +1173,7 @@ of wear leveling:
In littlefs's case, it's possible to use the revision count on metadata pairs In littlefs's case, it's possible to use the revision count on metadata pairs
to approximate the wear of a metadata block. And combined with the COW nature to approximate the wear of a metadata block. And combined with the COW nature
of files, littlefs could provide your usually implementation of dynamic wear of files, littlefs could provide your usual implementation of dynamic wear
leveling. leveling.
However, the littlefs does not. This is for a few reasons. Most notably, even However, the littlefs does not. This is for a few reasons. Most notably, even
@ -1206,12 +1208,12 @@ So, to summarize:
1. The littlefs is composed of directory blocks. 1. The littlefs is composed of directory blocks.
2. Each directory is a linked-list of metadata pairs. 2. Each directory is a linked-list of metadata pairs.
3. These metadata pairs can be updated atomically by alternating which 3. These metadata pairs can be updated atomically by alternating which
metadata block is active. metadata block is active
4. Directory blocks contain either references to other directories or files. 4. Directory blocks contain either references to other directories or files
5. Files are represented by copy-on-write CTZ skip-lists, which support O(1) 5. Files are represented by copy-on-write CTZ skip-lists which support O(1)
append and O(nlogn) reading. append and O(n log n) reading
6. Blocks are allocated by scanning the filesystem for used blocks in a 6. Blocks are allocated by scanning the filesystem for used blocks in a
fixed-size lookahead region is that stored in a bit-vector. fixed-size lookahead region that is stored in a bit-vector
7. To facilitate scanning the filesystem, all directories are part of a 7. To facilitate scanning the filesystem, all directories are part of a
linked-list that is threaded through the entire filesystem. linked-list that is threaded through the entire filesystem.
8. If a block develops an error, the littlefs allocates a new block and 8. If a block develops an error, the littlefs allocates a new block and

View File

@ -1,8 +1,8 @@
TARGET = lfs TARGET = lfs
CC = gcc CC ?= gcc
AR = ar AR ?= ar
SIZE = size SIZE ?= size
SRC += $(wildcard *.c emubd/*.c) SRC += $(wildcard *.c emubd/*.c)
OBJ := $(SRC:.c=.o) OBJ := $(SRC:.c=.o)
@ -14,15 +14,15 @@ TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*))
SHELL = /bin/bash -o pipefail SHELL = /bin/bash -o pipefail
ifdef DEBUG ifdef DEBUG
CFLAGS += -O0 -g3 override CFLAGS += -O0 -g3
else else
CFLAGS += -Os override CFLAGS += -Os
endif endif
ifdef WORD ifdef WORD
CFLAGS += -m$(WORD) override CFLAGS += -m$(WORD)
endif endif
CFLAGS += -I. override CFLAGS += -I.
CFLAGS += -std=c99 -Wall -pedantic override CFLAGS += -std=c99 -Wall -pedantic
all: $(TARGET) all: $(TARGET)
@ -33,11 +33,11 @@ size: $(OBJ)
$(SIZE) -t $^ $(SIZE) -t $^
.SUFFIXES: .SUFFIXES:
test: test_format test_dirs test_files test_seek test_parallel \ test: test_format test_dirs test_files test_seek test_truncate test_parallel \
test_alloc test_paths test_orphan test_move test_corrupt test_alloc test_paths test_orphan test_move test_corrupt
test_%: tests/test_%.sh test_%: tests/test_%.sh
ifdef QUIET ifdef QUIET
./$< | sed -n '/^[-=]/p' @./$< | sed -n '/^[-=]/p'
else else
./$< ./$<
endif endif

View File

@ -16,7 +16,7 @@ of memory. Recursion is avoided, and dynamic memory is limited to configurable
buffers that can be provided statically. buffers that can be provided statically.
**Power-loss resilient** - The littlefs is designed for systems that may have **Power-loss resilient** - The littlefs is designed for systems that may have
random power failures. The littlefs has strong copy-on-write guaruntees, and random power failures. The littlefs has strong copy-on-write guarantees, and
storage on disk is always kept in a valid state. storage on disk is always kept in a valid state.
**Wear leveling** - Because the most common form of embedded storage is erodible **Wear leveling** - Because the most common form of embedded storage is erodible
@ -88,7 +88,7 @@ int main(void) {
## Usage ## Usage
Detailed documentation (or at least as much detail as is currently available) Detailed documentation (or at least as much detail as is currently available)
can be cound in the comments in [lfs.h](lfs.h). can be found in the comments in [lfs.h](lfs.h).
As you may have noticed, littlefs takes in a configuration structure that As you may have noticed, littlefs takes in a configuration structure that
defines how the filesystem operates. The configuration struct provides the defines how the filesystem operates. The configuration struct provides the
@ -105,7 +105,7 @@ Once mounted, the littlefs provides a full set of POSIX-like file and
directory functions, with the deviation that the allocation of filesystem directory functions, with the deviation that the allocation of filesystem
structures must be provided by the user. structures must be provided by the user.
All POSIX operations, such as remove and rename, are atomic, even in the event All POSIX operations, such as remove and rename, are atomic, even in event
of power loss. Additionally, no file updates are actually committed to the of power loss. Additionally, no file updates are actually committed to the
filesystem until sync or close is called on the file. filesystem until sync or close is called on the file.
@ -141,8 +141,14 @@ make test
## Related projects ## Related projects
[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) -
The easiest way to get started with littlefs is to jump into [Mbed](https://os.mbed.com/),
which already has block device drivers for most forms of embedded storage. The
littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html)
class.
[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) [littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse)
wrapper for littlefs. The project allows you to mount littlefs directly in a wrapper for littlefs. The project allows you to mount littlefs directly on a
Linux machine. Can be useful for debugging littlefs if you have an SD card Linux machine. Can be useful for debugging littlefs if you have an SD card
handy. handy.

View File

@ -46,7 +46,7 @@ Here's the layout of metadata blocks on disk:
| 0x04 | 32 bits | dir size | | 0x04 | 32 bits | dir size |
| 0x08 | 64 bits | tail pointer | | 0x08 | 64 bits | tail pointer |
| 0x10 | size-16 bytes | dir entries | | 0x10 | size-16 bytes | dir entries |
| 0x00+s | 32 bits | crc | | 0x00+s | 32 bits | CRC |
**Revision count** - Incremented every update, only the uncorrupted **Revision count** - Incremented every update, only the uncorrupted
metadata-block with the most recent revision count contains the valid metadata. metadata-block with the most recent revision count contains the valid metadata.
@ -75,7 +75,7 @@ Here's an example of a simple directory stored on disk:
(32 bits) revision count = 10 (0x0000000a) (32 bits) revision count = 10 (0x0000000a)
(32 bits) dir size = 154 bytes, end of dir (0x0000009a) (32 bits) dir size = 154 bytes, end of dir (0x0000009a)
(64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024) (64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024)
(32 bits) crc = 0xc86e3106 (32 bits) CRC = 0xc86e3106
00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00 ........%...$... 00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00 ........%...$...
00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22 "...........tea" 00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22 "...........tea"
@ -142,8 +142,8 @@ attributes are system specific, there is not much guarantee on the values in
this section, and systems are expected to work even when it is empty. See the this section, and systems are expected to work even when it is empty. See the
[attributes](#entry-attributes) section for more details. [attributes](#entry-attributes) section for more details.
**Name length** - Length of the entry name. Entry names are stored as utf8, **Name length** - Length of the entry name. Entry names are stored as UTF8,
though most systems will probably only support ascii. Entry names can not though most systems will probably only support ASCII. Entry names can not
contain '/' and can not be '.' or '..' because these are a part of the syntax of contain '/' and can not be '.' or '..' because these are a part of the syntax of
filesystem paths. filesystem paths.
@ -222,7 +222,7 @@ Here's an example of a complete superblock:
(32 bits) block count = 1024 blocks (0x00000400) (32 bits) block count = 1024 blocks (0x00000400)
(32 bits) version = 1.1 (0x00010001) (32 bits) version = 1.1 (0x00010001)
(8 bytes) magic string = littlefs (8 bytes) magic string = littlefs
(32 bits) crc = 0xc50b74fa (32 bits) CRC = 0xc50b74fa
00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4........... 00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4...........
00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................ 00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................

View File

@ -190,13 +190,13 @@ int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
} }
if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) { if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) {
int err = unlink(emu->path); err = unlink(emu->path);
if (err) { if (err) {
return -errno; return -errno;
} }
} }
if (errno == ENOENT || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) { if (err || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) {
FILE *f = fopen(emu->path, "w"); FILE *f = fopen(emu->path, "w");
if (!f) { if (!f) {
return -errno; return -errno;

View File

@ -18,17 +18,13 @@
#include "lfs.h" #include "lfs.h"
#include "lfs_util.h" #include "lfs_util.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
/// Caching block device operations /// /// Caching block device operations ///
static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache,
const lfs_cache_t *pcache, lfs_block_t block, const lfs_cache_t *pcache, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) { lfs_off_t off, void *buffer, lfs_size_t size) {
uint8_t *data = buffer; uint8_t *data = buffer;
assert(block < lfs->cfg->block_count); LFS_ASSERT(block < lfs->cfg->block_count);
while (size > 0) { while (size > 0) {
if (pcache && block == pcache->block && off >= pcache->off && if (pcache && block == pcache->block && off >= pcache->off &&
@ -153,7 +149,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
lfs_cache_t *rcache, lfs_block_t block, lfs_cache_t *rcache, 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) {
const uint8_t *data = buffer; const uint8_t *data = buffer;
assert(block < lfs->cfg->block_count); LFS_ASSERT(block < lfs->cfg->block_count);
while (size > 0) { while (size > 0) {
if (block == pcache->block && off >= pcache->off && if (block == pcache->block && off >= pcache->off &&
@ -180,7 +176,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache,
// pcache must have been flushed, either by programming and // pcache must have been flushed, either by programming and
// entire block or manually flushing the pcache // entire block or manually flushing the pcache
assert(pcache->block == 0xffffffff); LFS_ASSERT(pcache->block == 0xffffffff);
if (off % lfs->cfg->prog_size == 0 && if (off % lfs->cfg->prog_size == 0 &&
size >= lfs->cfg->prog_size) { size >= lfs->cfg->prog_size) {
@ -323,6 +319,48 @@ static void lfs_alloc_ack(lfs_t *lfs) {
} }
/// Endian swapping functions ///
static void lfs_dir_fromle32(struct lfs_disk_dir *d) {
d->rev = lfs_fromle32(d->rev);
d->size = lfs_fromle32(d->size);
d->tail[0] = lfs_fromle32(d->tail[0]);
d->tail[1] = lfs_fromle32(d->tail[1]);
}
static void lfs_dir_tole32(struct lfs_disk_dir *d) {
d->rev = lfs_tole32(d->rev);
d->size = lfs_tole32(d->size);
d->tail[0] = lfs_tole32(d->tail[0]);
d->tail[1] = lfs_tole32(d->tail[1]);
}
static void lfs_entry_fromle32(struct lfs_disk_entry *d) {
d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
}
static void lfs_entry_tole32(struct lfs_disk_entry *d) {
d->u.dir[0] = lfs_tole32(d->u.dir[0]);
d->u.dir[1] = lfs_tole32(d->u.dir[1]);
}
static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) {
d->root[0] = lfs_fromle32(d->root[0]);
d->root[1] = lfs_fromle32(d->root[1]);
d->block_size = lfs_fromle32(d->block_size);
d->block_count = lfs_fromle32(d->block_count);
d->version = lfs_fromle32(d->version);
}
static void lfs_superblock_tole32(struct lfs_disk_superblock *d) {
d->root[0] = lfs_tole32(d->root[0]);
d->root[1] = lfs_tole32(d->root[1]);
d->block_size = lfs_tole32(d->block_size);
d->block_count = lfs_tole32(d->block_count);
d->version = lfs_tole32(d->version);
}
/// Metadata pair and directory operations /// /// Metadata pair and directory operations ///
static inline void lfs_pairswap(lfs_block_t pair[2]) { static inline void lfs_pairswap(lfs_block_t pair[2]) {
lfs_block_t t = pair[0]; lfs_block_t t = pair[0];
@ -364,6 +402,7 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
// rather than clobbering one of the blocks we just pretend // rather than clobbering one of the blocks we just pretend
// the revision may be valid // the revision may be valid
int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4); int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4);
dir->d.rev = lfs_fromle32(dir->d.rev);
if (err) { if (err) {
return err; return err;
} }
@ -389,6 +428,7 @@ static int lfs_dir_fetch(lfs_t *lfs,
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
struct lfs_disk_dir test; struct lfs_disk_dir test;
int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
lfs_dir_fromle32(&test);
if (err) { if (err) {
return err; return err;
} }
@ -403,7 +443,9 @@ static int lfs_dir_fetch(lfs_t *lfs,
} }
uint32_t crc = 0xffffffff; uint32_t crc = 0xffffffff;
lfs_dir_tole32(&test);
lfs_crc(&crc, &test, sizeof(test)); lfs_crc(&crc, &test, sizeof(test));
lfs_dir_fromle32(&test);
err = lfs_bd_crc(lfs, tpair[i], sizeof(test), err = lfs_bd_crc(lfs, tpair[i], sizeof(test),
(0x7fffffff & test.size) - sizeof(test), &crc); (0x7fffffff & test.size) - sizeof(test), &crc);
if (err) { if (err) {
@ -463,8 +505,10 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
} }
uint32_t crc = 0xffffffff; uint32_t crc = 0xffffffff;
lfs_dir_tole32(&dir->d);
lfs_crc(&crc, &dir->d, sizeof(dir->d)); lfs_crc(&crc, &dir->d, sizeof(dir->d));
err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d));
lfs_dir_fromle32(&dir->d);
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
goto relocate; goto relocate;
@ -478,7 +522,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
while (newoff < (0x7fffffff & dir->d.size)-4) { while (newoff < (0x7fffffff & dir->d.size)-4) {
if (i < count && regions[i].oldoff == oldoff) { if (i < count && regions[i].oldoff == oldoff) {
lfs_crc(&crc, regions[i].newdata, regions[i].newlen); lfs_crc(&crc, regions[i].newdata, regions[i].newlen);
int err = lfs_bd_prog(lfs, dir->pair[0], err = lfs_bd_prog(lfs, dir->pair[0],
newoff, regions[i].newdata, regions[i].newlen); newoff, regions[i].newdata, regions[i].newlen);
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
@ -492,7 +536,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
i += 1; i += 1;
} else { } else {
uint8_t data; uint8_t data;
int err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1);
if (err) { if (err) {
return err; return err;
} }
@ -511,7 +555,9 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
} }
} }
crc = lfs_tole32(crc);
err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4);
crc = lfs_fromle32(crc);
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
goto relocate; goto relocate;
@ -584,11 +630,14 @@ relocate:
} }
static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir,
const lfs_entry_t *entry, const void *data) { lfs_entry_t *entry, const void *data) {
return lfs_dir_commit(lfs, dir, (struct lfs_region[]){ lfs_entry_tole32(&entry->d);
int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
{entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)},
{entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen} {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen}
}, data ? 2 : 1); }, data ? 2 : 1);
lfs_entry_fromle32(&entry->d);
return err;
} }
static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
@ -597,10 +646,14 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
while (true) { while (true) {
if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
entry->off = dir->d.size - 4; entry->off = dir->d.size - 4;
return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
lfs_entry_tole32(&entry->d);
int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
{entry->off, 0, &entry->d, sizeof(entry->d)}, {entry->off, 0, &entry->d, sizeof(entry->d)},
{entry->off, 0, data, entry->d.nlen} {entry->off, 0, data, entry->d.nlen}
}, 2); }, 2);
lfs_entry_fromle32(&entry->d);
return err;
} }
// we need to allocate a new dir block // we need to allocate a new dir block
@ -614,10 +667,12 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
newdir.d.tail[0] = dir->d.tail[0]; newdir.d.tail[0] = dir->d.tail[0];
newdir.d.tail[1] = dir->d.tail[1]; newdir.d.tail[1] = dir->d.tail[1];
entry->off = newdir.d.size - 4; entry->off = newdir.d.size - 4;
lfs_entry_tole32(&entry->d);
err = lfs_dir_commit(lfs, &newdir, (struct lfs_region[]){ err = lfs_dir_commit(lfs, &newdir, (struct lfs_region[]){
{entry->off, 0, &entry->d, sizeof(entry->d)}, {entry->off, 0, &entry->d, sizeof(entry->d)},
{entry->off, 0, data, entry->d.nlen} {entry->off, 0, data, entry->d.nlen}
}, 2); }, 2);
lfs_entry_fromle32(&entry->d);
if (err) { if (err) {
return err; return err;
} }
@ -703,6 +758,7 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
int err = lfs_bd_read(lfs, dir->pair[0], dir->off, int err = lfs_bd_read(lfs, dir->pair[0], dir->off,
&entry->d, sizeof(entry->d)); &entry->d, sizeof(entry->d));
lfs_entry_fromle32(&entry->d);
if (err) { if (err) {
return err; return err;
} }
@ -1002,7 +1058,7 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
return LFS_ERR_INVAL; return LFS_ERR_INVAL;
} }
int err = lfs_dir_fetch(lfs, dir, dir->d.tail); err = lfs_dir_fetch(lfs, dir, dir->d.tail);
if (err) { if (err) {
return err; return err;
} }
@ -1013,6 +1069,7 @@ int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
} }
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
(void)lfs;
return dir->pos; return dir->pos;
} }
@ -1064,11 +1121,12 @@ static int lfs_ctz_find(lfs_t *lfs,
lfs_ctz(current)); lfs_ctz(current));
int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4);
head = lfs_fromle32(head);
if (err) { if (err) {
return err; return err;
} }
assert(head >= 2 && head <= lfs->cfg->block_count); LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
current -= 1 << skip; current -= 1 << skip;
} }
@ -1088,7 +1146,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
if (err) { if (err) {
return err; return err;
} }
assert(nblock >= 2 && nblock <= lfs->cfg->block_count); LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
if (true) { if (true) {
err = lfs_bd_erase(lfs, nblock); err = lfs_bd_erase(lfs, nblock);
@ -1113,7 +1171,7 @@ static int lfs_ctz_extend(lfs_t *lfs,
if (size != lfs->cfg->block_size) { if (size != lfs->cfg->block_size) {
for (lfs_off_t i = 0; i < size; i++) { for (lfs_off_t i = 0; i < size; i++) {
uint8_t data; uint8_t data;
int err = lfs_cache_read(lfs, rcache, NULL, err = lfs_cache_read(lfs, rcache, NULL,
head, i, &data, 1); head, i, &data, 1);
if (err) { if (err) {
return err; return err;
@ -1139,8 +1197,10 @@ static int lfs_ctz_extend(lfs_t *lfs,
lfs_size_t skips = lfs_ctz(index) + 1; lfs_size_t skips = lfs_ctz(index) + 1;
for (lfs_off_t i = 0; i < skips; i++) { for (lfs_off_t i = 0; i < skips; i++) {
int err = lfs_cache_prog(lfs, pcache, rcache, head = lfs_tole32(head);
err = lfs_cache_prog(lfs, pcache, rcache,
nblock, 4*i, &head, 4); nblock, 4*i, &head, 4);
head = lfs_fromle32(head);
if (err) { if (err) {
if (err == LFS_ERR_CORRUPT) { if (err == LFS_ERR_CORRUPT) {
goto relocate; goto relocate;
@ -1151,12 +1211,13 @@ static int lfs_ctz_extend(lfs_t *lfs,
if (i != skips-1) { if (i != skips-1) {
err = lfs_cache_read(lfs, rcache, NULL, err = lfs_cache_read(lfs, rcache, NULL,
head, 4*i, &head, 4); head, 4*i, &head, 4);
head = lfs_fromle32(head);
if (err) { if (err) {
return err; return err;
} }
} }
assert(head >= 2 && head <= lfs->cfg->block_count); LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
} }
*block = nblock; *block = nblock;
@ -1192,12 +1253,24 @@ static int lfs_ctz_traverse(lfs_t *lfs,
return 0; return 0;
} }
err = lfs_cache_read(lfs, rcache, pcache, head, 0, &head, 4); lfs_block_t heads[2];
int count = 2 - (index & 1);
err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4);
heads[0] = lfs_fromle32(heads[0]);
heads[1] = lfs_fromle32(heads[1]);
if (err) { if (err) {
return err; return err;
} }
index -= 1; for (int i = 0; i < count-1; i++) {
err = cb(data, heads[i]);
if (err) {
return err;
}
}
head = heads[count-1];
index -= count;
} }
} }
@ -1270,12 +1343,12 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
if (lfs->cfg->file_buffer) { if (lfs->cfg->file_buffer) {
file->cache.buffer = lfs->cfg->file_buffer; file->cache.buffer = lfs->cfg->file_buffer;
} else if ((file->flags & 3) == LFS_O_RDONLY) { } else if ((file->flags & 3) == LFS_O_RDONLY) {
file->cache.buffer = malloc(lfs->cfg->read_size); file->cache.buffer = lfs_malloc(lfs->cfg->read_size);
if (!file->cache.buffer) { if (!file->cache.buffer) {
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
} else { } else {
file->cache.buffer = malloc(lfs->cfg->prog_size); file->cache.buffer = lfs_malloc(lfs->cfg->prog_size);
if (!file->cache.buffer) { if (!file->cache.buffer) {
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
@ -1301,7 +1374,7 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
// clean up memory // clean up memory
if (!lfs->cfg->file_buffer) { if (!lfs->cfg->file_buffer) {
free(file->cache.buffer); lfs_free(file->cache.buffer);
} }
return err; return err;
@ -1437,7 +1510,7 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
!lfs_pairisnull(file->pair)) { !lfs_pairisnull(file->pair)) {
// update dir entry // update dir entry
lfs_dir_t cwd; lfs_dir_t cwd;
int err = lfs_dir_fetch(lfs, &cwd, file->pair); err = lfs_dir_fetch(lfs, &cwd, file->pair);
if (err) { if (err) {
return err; return err;
} }
@ -1445,15 +1518,12 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
lfs_entry_t entry = {.off = file->poff}; lfs_entry_t entry = {.off = file->poff};
err = lfs_bd_read(lfs, cwd.pair[0], entry.off, err = lfs_bd_read(lfs, cwd.pair[0], entry.off,
&entry.d, sizeof(entry.d)); &entry.d, sizeof(entry.d));
lfs_entry_fromle32(&entry.d);
if (err) { if (err) {
return err; return err;
} }
if (entry.d.type != LFS_TYPE_REG) { LFS_ASSERT(entry.d.type == LFS_TYPE_REG);
// sanity check valid entry
return LFS_ERR_INVAL;
}
entry.d.u.file.head = file->head; entry.d.u.file.head = file->head;
entry.d.u.file.size = file->size; entry.d.u.file.size = file->size;
@ -1474,7 +1544,7 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
lfs_size_t nsize = size; lfs_size_t nsize = size;
if ((file->flags & 3) == LFS_O_WRONLY) { if ((file->flags & 3) == LFS_O_WRONLY) {
return LFS_ERR_INVAL; return LFS_ERR_BADF;
} }
if (file->flags & LFS_F_WRITING) { if (file->flags & LFS_F_WRITING) {
@ -1530,7 +1600,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
lfs_size_t nsize = size; lfs_size_t nsize = size;
if ((file->flags & 3) == LFS_O_RDONLY) { if ((file->flags & 3) == LFS_O_RDONLY) {
return LFS_ERR_INVAL; return LFS_ERR_BADF;
} }
if (file->flags & LFS_F_READING) { if (file->flags & LFS_F_READING) {
@ -1651,7 +1721,60 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
return file->pos; return file->pos;
} }
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
if ((file->flags & 3) == LFS_O_RDONLY) {
return LFS_ERR_BADF;
}
lfs_off_t oldsize = lfs_file_size(lfs, file);
if (size < oldsize) {
// need to flush since directly changing metadata
int err = lfs_file_flush(lfs, file);
if (err) {
return err;
}
// lookup new head in ctz skip list
err = lfs_ctz_find(lfs, &file->cache, NULL,
file->head, file->size,
size, &file->head, &(lfs_off_t){0});
if (err) {
return err;
}
file->size = size;
file->flags |= LFS_F_DIRTY;
} else if (size > oldsize) {
lfs_off_t pos = file->pos;
// flush+seek if not already at end
if (file->pos != oldsize) {
int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END);
if (err < 0) {
return err;
}
}
// fill with zeros
while (file->pos < size) {
lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
if (res < 0) {
return res;
}
}
// restore pos
int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
if (err < 0) {
return err;
}
}
return 0;
}
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
(void)lfs;
return file->pos; return file->pos;
} }
@ -1665,11 +1788,16 @@ int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
} }
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
(void)lfs;
if (file->flags & LFS_F_WRITING) {
return lfs_max(file->pos, file->size); return lfs_max(file->pos, file->size);
} else {
return file->size;
}
} }
/// General fs oprations /// /// General fs operations ///
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
// check for root, can only be something like '/././../.' // check for root, can only be something like '/././../.'
if (strspn(path, "/.") == strlen(path)) { if (strspn(path, "/.") == strlen(path)) {
@ -1733,11 +1861,11 @@ int lfs_remove(lfs_t *lfs, const char *path) {
// must be empty before removal, checking size // must be empty before removal, checking size
// without masking top bit checks for any case where // without masking top bit checks for any case where
// dir is not empty // dir is not empty
int err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir);
if (err) { if (err) {
return err; return err;
} else if (dir.d.size != sizeof(dir.d)+4) { } else if (dir.d.size != sizeof(dir.d)+4) {
return LFS_ERR_INVAL; return LFS_ERR_NOTEMPTY;
} }
} }
@ -1754,11 +1882,11 @@ int lfs_remove(lfs_t *lfs, const char *path) {
return res; return res;
} }
assert(res); // must have pred LFS_ASSERT(res); // must have pred
cwd.d.tail[0] = dir.d.tail[0]; cwd.d.tail[0] = dir.d.tail[0];
cwd.d.tail[1] = dir.d.tail[1]; cwd.d.tail[1] = dir.d.tail[1];
int err = lfs_dir_commit(lfs, &cwd, NULL, 0); err = lfs_dir_commit(lfs, &cwd, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@ -1807,7 +1935,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
// must have same type // must have same type
if (prevexists && preventry.d.type != oldentry.d.type) { if (prevexists && preventry.d.type != oldentry.d.type) {
return LFS_ERR_INVAL; return LFS_ERR_ISDIR;
} }
lfs_dir_t dir; lfs_dir_t dir;
@ -1815,11 +1943,11 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
// must be empty before removal, checking size // must be empty before removal, checking size
// without masking top bit checks for any case where // without masking top bit checks for any case where
// dir is not empty // dir is not empty
int err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir);
if (err) { if (err) {
return err; return err;
} else if (dir.d.size != sizeof(dir.d)+4) { } else if (dir.d.size != sizeof(dir.d)+4) {
return LFS_ERR_INVAL; return LFS_ERR_NOTEMPTY;
} }
} }
@ -1842,12 +1970,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
newentry.d.nlen = strlen(newpath); newentry.d.nlen = strlen(newpath);
if (prevexists) { if (prevexists) {
int err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); err = lfs_dir_update(lfs, &newcwd, &newentry, newpath);
if (err) { if (err) {
return err; return err;
} }
} else { } else {
int err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); err = lfs_dir_append(lfs, &newcwd, &newentry, newpath);
if (err) { if (err) {
return err; return err;
} }
@ -1871,11 +1999,11 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
return res; return res;
} }
assert(res); // must have pred LFS_ASSERT(res); // must have pred
newcwd.d.tail[0] = dir.d.tail[0]; newcwd.d.tail[0] = dir.d.tail[0];
newcwd.d.tail[1] = dir.d.tail[1]; newcwd.d.tail[1] = dir.d.tail[1];
int err = lfs_dir_commit(lfs, &newcwd, NULL, 0); err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
if (err) { if (err) {
return err; return err;
} }
@ -1894,7 +2022,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
if (lfs->cfg->read_buffer) { if (lfs->cfg->read_buffer) {
lfs->rcache.buffer = lfs->cfg->read_buffer; lfs->rcache.buffer = lfs->cfg->read_buffer;
} else { } else {
lfs->rcache.buffer = malloc(lfs->cfg->read_size); lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size);
if (!lfs->rcache.buffer) { if (!lfs->rcache.buffer) {
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
@ -1905,26 +2033,30 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
if (lfs->cfg->prog_buffer) { if (lfs->cfg->prog_buffer) {
lfs->pcache.buffer = lfs->cfg->prog_buffer; lfs->pcache.buffer = lfs->cfg->prog_buffer;
} else { } else {
lfs->pcache.buffer = malloc(lfs->cfg->prog_size); lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size);
if (!lfs->pcache.buffer) { if (!lfs->pcache.buffer) {
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
} }
// setup lookahead, round down to nearest 32-bits // setup lookahead, round down to nearest 32-bits
assert(lfs->cfg->lookahead % 32 == 0); LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
assert(lfs->cfg->lookahead > 0); LFS_ASSERT(lfs->cfg->lookahead > 0);
if (lfs->cfg->lookahead_buffer) { if (lfs->cfg->lookahead_buffer) {
lfs->free.buffer = lfs->cfg->lookahead_buffer; lfs->free.buffer = lfs->cfg->lookahead_buffer;
} else { } else {
lfs->free.buffer = malloc(lfs->cfg->lookahead/8); lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
if (!lfs->free.buffer) { if (!lfs->free.buffer) {
return LFS_ERR_NOMEM; return LFS_ERR_NOMEM;
} }
} }
// check that program and read sizes are multiples of the block size
LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0);
LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0);
// check that the block size is large enough to fit ctz pointers // check that the block size is large enough to fit ctz pointers
assert(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
<= lfs->cfg->block_size); <= lfs->cfg->block_size);
// setup default state // setup default state
@ -1940,15 +2072,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
static int lfs_deinit(lfs_t *lfs) { static int lfs_deinit(lfs_t *lfs) {
// free allocated memory // free allocated memory
if (!lfs->cfg->read_buffer) { if (!lfs->cfg->read_buffer) {
free(lfs->rcache.buffer); lfs_free(lfs->rcache.buffer);
} }
if (!lfs->cfg->prog_buffer) { if (!lfs->cfg->prog_buffer) {
free(lfs->pcache.buffer); lfs_free(lfs->pcache.buffer);
} }
if (!lfs->cfg->lookahead_buffer) { if (!lfs->cfg->lookahead_buffer) {
free(lfs->free.buffer); lfs_free(lfs->free.buffer);
} }
return 0; return 0;
@ -1995,7 +2127,7 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
.d.type = LFS_TYPE_SUPERBLOCK, .d.type = LFS_TYPE_SUPERBLOCK,
.d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4,
.d.nlen = sizeof(superblock.d.magic), .d.nlen = sizeof(superblock.d.magic),
.d.version = 0x00010001, .d.version = LFS_DISK_VERSION,
.d.magic = {"littlefs"}, .d.magic = {"littlefs"},
.d.block_size = lfs->cfg->block_size, .d.block_size = lfs->cfg->block_size,
.d.block_count = lfs->cfg->block_count, .d.block_count = lfs->cfg->block_count,
@ -2006,9 +2138,10 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4;
// write both pairs to be safe // write both pairs to be safe
lfs_superblock_tole32(&superblock.d);
bool valid = false; bool valid = false;
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
int err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){ err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){
{sizeof(superdir.d), sizeof(superblock.d), {sizeof(superdir.d), sizeof(superblock.d),
&superblock.d, sizeof(superblock.d)} &superblock.d, sizeof(superblock.d)}
}, 1); }, 1);
@ -2054,8 +2187,9 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
} }
if (!err) { if (!err) {
int err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
&superblock.d, sizeof(superblock.d)); &superblock.d, sizeof(superblock.d));
lfs_superblock_fromle32(&superblock.d);
if (err) { if (err) {
return err; return err;
} }
@ -2069,10 +2203,11 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
return LFS_ERR_CORRUPT; return LFS_ERR_CORRUPT;
} }
if (superblock.d.version > (0x00010001 | 0x0000ffff)) { uint16_t major_version = (0xffff & (superblock.d.version >> 16));
LFS_ERROR("Invalid version %ld.%ld", uint16_t minor_version = (0xffff & (superblock.d.version >> 0));
0xffff & (superblock.d.version >> 16), if ((major_version != LFS_DISK_VERSION_MAJOR ||
0xffff & (superblock.d.version >> 0)); minor_version > LFS_DISK_VERSION_MINOR)) {
LFS_ERROR("Invalid version %ld.%ld", major_version, minor_version);
return LFS_ERR_INVAL; return LFS_ERR_INVAL;
} }
@ -2110,15 +2245,16 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
// iterate over contents // iterate over contents
while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
int err = lfs_bd_read(lfs, dir.pair[0], dir.off, err = lfs_bd_read(lfs, dir.pair[0], dir.off,
&entry.d, sizeof(entry.d)); &entry.d, sizeof(entry.d));
lfs_entry_fromle32(&entry.d);
if (err) { if (err) {
return err; return err;
} }
dir.off += lfs_entry_size(&entry); dir.off += lfs_entry_size(&entry);
if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
int err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL,
entry.d.u.file.head, entry.d.u.file.size, cb, data); entry.d.u.file.head, entry.d.u.file.size, cb, data);
if (err) { if (err) {
return err; return err;
@ -2172,7 +2308,7 @@ static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) {
return true; return true;
} }
int err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); err = lfs_dir_fetch(lfs, pdir, pdir->d.tail);
if (err) { if (err) {
return err; return err;
} }
@ -2198,7 +2334,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
} }
while (true) { while (true) {
int err = lfs_dir_next(lfs, parent, entry); err = lfs_dir_next(lfs, parent, entry);
if (err && err != LFS_ERR_NOENT) { if (err && err != LFS_ERR_NOENT) {
return err; return err;
} }
@ -2232,13 +2368,13 @@ static int lfs_moved(lfs_t *lfs, const void *e) {
// iterate over all directory directory entries // iterate over all directory directory entries
lfs_entry_t entry; lfs_entry_t entry;
while (!lfs_pairisnull(cwd.d.tail)) { while (!lfs_pairisnull(cwd.d.tail)) {
int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
if (err) { if (err) {
return err; return err;
} }
while (true) { while (true) {
int err = lfs_dir_next(lfs, &cwd, &entry); err = lfs_dir_next(lfs, &cwd, &entry);
if (err && err != LFS_ERR_NOENT) { if (err && err != LFS_ERR_NOENT) {
return err; return err;
} }
@ -2369,7 +2505,7 @@ int lfs_deorphan(lfs_t *lfs) {
// check entries for moves // check entries for moves
lfs_entry_t entry; lfs_entry_t entry;
while (true) { while (true) {
int err = lfs_dir_next(lfs, &cwd, &entry); err = lfs_dir_next(lfs, &cwd, &entry);
if (err && err != LFS_ERR_NOENT) { if (err && err != LFS_ERR_NOENT) {
return err; return err;
} }
@ -2388,7 +2524,7 @@ int lfs_deorphan(lfs_t *lfs) {
if (moved) { if (moved) {
LFS_DEBUG("Found move %ld %ld", LFS_DEBUG("Found move %ld %ld",
entry.d.u.dir[0], entry.d.u.dir[1]); entry.d.u.dir[0], entry.d.u.dir[1]);
int err = lfs_dir_remove(lfs, &cwd, &entry); err = lfs_dir_remove(lfs, &cwd, &entry);
if (err) { if (err) {
return err; return err;
} }
@ -2396,7 +2532,7 @@ int lfs_deorphan(lfs_t *lfs) {
LFS_DEBUG("Found partial move %ld %ld", LFS_DEBUG("Found partial move %ld %ld",
entry.d.u.dir[0], entry.d.u.dir[1]); entry.d.u.dir[0], entry.d.u.dir[1]);
entry.d.type &= ~0x80; entry.d.type &= ~0x80;
int err = lfs_dir_update(lfs, &cwd, &entry, NULL); err = lfs_dir_update(lfs, &cwd, &entry, NULL);
if (err) { if (err) {
return err; return err;
} }

View File

@ -22,6 +22,23 @@
#include <stdbool.h> #include <stdbool.h>
/// Version info ///
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x00010003
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_DISK_VERSION 0x00010001
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
/// Definitions /// /// Definitions ///
// Type definitions // Type definitions
@ -48,6 +65,8 @@ enum lfs_error {
LFS_ERR_EXIST = -17, // Entry already exists LFS_ERR_EXIST = -17, // Entry already exists
LFS_ERR_NOTDIR = -20, // Entry is not a dir LFS_ERR_NOTDIR = -20, // Entry is not a dir
LFS_ERR_ISDIR = -21, // Entry is a dir LFS_ERR_ISDIR = -21, // Entry is a dir
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
LFS_ERR_BADF = -9, // Bad file number
LFS_ERR_INVAL = -22, // Invalid parameter LFS_ERR_INVAL = -22, // Invalid parameter
LFS_ERR_NOSPC = -28, // No space left on device LFS_ERR_NOSPC = -28, // No space left on device
LFS_ERR_NOMEM = -12, // No more memory available LFS_ERR_NOMEM = -12, // No more memory available
@ -99,14 +118,14 @@ struct lfs_config {
// Program a region in a block. The block must have previously // Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user. // been erased. Negative error codes are propogated to the user.
// The prog function must return LFS_ERR_CORRUPT if the block should // May return LFS_ERR_CORRUPT if the block should be considered bad.
// be considered bad.
int (*prog)(const struct lfs_config *c, lfs_block_t block, int (*prog)(const struct lfs_config *c, 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);
// Erase a block. A block must be erased before being programmed. // Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes // The state of an erased block is undefined. Negative error codes
// are propogated to the user. // are propogated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs_config *c, lfs_block_t block); int (*erase)(const struct lfs_config *c, lfs_block_t block);
// Sync the state of the underlying block device. Negative error codes // Sync the state of the underlying block device. Negative error codes
@ -121,11 +140,13 @@ struct lfs_config {
// Minimum size of a block program. This determines the size of program // Minimum size of a block program. This determines the size of program
// buffers. This may be larger than the physical program size to improve // buffers. This may be larger than the physical program size to improve
// performance by caching more of the block device. // performance by caching more of the block device.
// Must be a multiple of the read size.
lfs_size_t prog_size; lfs_size_t prog_size;
// Size of an erasable block. This does not impact ram consumption and // Size of an erasable block. This does not impact ram consumption and
// may be larger than the physical erase size. However, this should be // may be larger than the physical erase size. However, this should be
// kept small as each file currently takes up an entire block. // kept small as each file currently takes up an entire block.
// Must be a multiple of the program size.
lfs_size_t block_size; lfs_size_t block_size;
// Number of erasable blocks on the device. // Number of erasable blocks on the device.
@ -362,6 +383,11 @@ 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);
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
// Return the position of the file // Return the position of the file
// //
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)

View File

@ -18,75 +18,24 @@
#ifndef LFS_UTIL_H #ifndef LFS_UTIL_H
#define LFS_UTIL_H #define LFS_UTIL_H
#include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <string.h>
#ifndef LFS_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS_NO_INFO) || !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR)
#include <stdio.h> #include <stdio.h>
#ifdef __ICCARM__
#include <intrinsics.h>
#endif #endif
// Builtin functions, these may be replaced by more // Macros, may be replaced by system specific wrappers. Arguments to these
// efficient implementations in the system // macros must not have side-effects as the macros can be removed for a smaller
static inline uint32_t lfs_max(uint32_t a, uint32_t b) { // code footprint
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
static inline uint32_t lfs_ctz(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return __builtin_ctz(a);
#elif defined(__ICCARM__) && defined(__CLZ)
return __CLZ(__RBIT(a));
#else
uint32_t r = 32;
a &= -a;
if (a) r -= 1;
if (a & 0x0000ffff) r -= 16;
if (a & 0x00ff00ff) r -= 8;
if (a & 0x0f0f0f0f) r -= 4;
if (a & 0x33333333) r -= 2;
if (a & 0x55555555) r -= 1;
return r;
#endif
}
static inline uint32_t lfs_npw2(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return 32 - __builtin_clz(a-1);
#elif defined(__ICCARM__) && defined(__CLZ)
return 32 - __CLZ(a-1);
#else
uint32_t r = 0;
uint32_t s;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return r | (a >> 1);
#endif
}
static inline uint32_t lfs_popc(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// CRC-32 with polynomial = 0x04c11db7
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
// Logging functions // Logging functions
#ifdef __MBED__ #ifdef __MBED__
@ -98,37 +47,145 @@ void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
#define MBED_LFS_ENABLE_ERROR true #define MBED_LFS_ENABLE_ERROR true
#endif #endif
#if MBED_LFS_ENABLE_INFO #if !defined(LFS_NO_INFO) && MBED_LFS_ENABLE_INFO
#define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__) #define LFS_INFO(fmt, ...) printf("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_INFO) #elif !defined(LFS_NO_INFO) && !defined(MBED_LFS_ENABLE_INFO)
#define LFS_INFO(fmt, ...) debug("lfs info: " fmt "\n", __VA_ARGS__) #define LFS_INFO(fmt, ...) debug("lfs info:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else #else
#define LFS_INFO(fmt, ...) #define LFS_INFO(fmt, ...)
#endif #endif
#if MBED_LFS_ENABLE_DEBUG #if !defined(LFS_NO_DEBUG) && MBED_LFS_ENABLE_DEBUG
#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__) #define LFS_DEBUG(fmt, ...) printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_DEBUG) #elif !defined(LFS_NO_DEBUG) && !defined(MBED_LFS_ENABLE_DEBUG)
#define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__) #define LFS_DEBUG(fmt, ...) debug("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else #else
#define LFS_DEBUG(fmt, ...) #define LFS_DEBUG(fmt, ...)
#endif #endif
#if MBED_LFS_ENABLE_WARN #if !defined(LFS_NO_WARN) && MBED_LFS_ENABLE_WARN
#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__) #define LFS_WARN(fmt, ...) printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_WARN) #elif !defined(LFS_NO_WARN) && !defined(MBED_LFS_ENABLE_WARN)
#define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__) #define LFS_WARN(fmt, ...) debug("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else #else
#define LFS_WARN(fmt, ...) #define LFS_WARN(fmt, ...)
#endif #endif
#if MBED_LFS_ENABLE_ERROR #if !defined(LFS_NO_ERROR) && MBED_LFS_ENABLE_ERROR
#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__) #define LFS_ERROR(fmt, ...) printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_ERROR) #elif !defined(LFS_NO_ERROR) && !defined(MBED_LFS_ENABLE_ERROR)
#define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__) #define LFS_ERROR(fmt, ...) debug("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
#else #else
#define LFS_ERROR(fmt, ...) #define LFS_ERROR(fmt, ...)
#endif #endif
// Runtime assertions
#ifndef LFS_NO_ASSERT
#define LFS_ASSERT(test) assert(test)
#else
#define LFS_ASSERT(test)
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Find the next smallest power of 2 less than or equal to a
static inline uint32_t lfs_npw2(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs_ctz(0) may be undefined
static inline uint32_t lfs_ctz(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs_popc(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert from 32-bit little-endian to native order
static inline uint32_t lfs_fromle32(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return a;
#elif !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return (((uint8_t*)&a)[0] << 0) |
(((uint8_t*)&a)[1] << 8) |
(((uint8_t*)&a)[2] << 16) |
(((uint8_t*)&a)[3] << 24);
#endif
}
// Convert to 32-bit little-endian from native order
static inline uint32_t lfs_tole32(uint32_t a) {
return lfs_fromle32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
// Allocate memory, only used if buffers are not provided to littlefs
static inline void *lfs_malloc(size_t size) {
#ifndef LFS_NO_MALLOC
return malloc(size);
#else
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p) {
#ifndef LFS_NO_MALLOC
free(p);
#endif
}
#endif #endif

View File

@ -7,11 +7,11 @@
// test stuff // test stuff
void test_log(const char *s, uintmax_t v) {{ static void test_log(const char *s, uintmax_t v) {{
printf("%s: %jd\n", s, v); printf("%s: %jd\n", s, v);
}} }}
void test_assert(const char *file, unsigned line, static void test_assert(const char *file, unsigned line,
const char *s, uintmax_t v, uintmax_t e) {{ const char *s, uintmax_t v, uintmax_t e) {{
static const char *last[6] = {{0, 0}}; static const char *last[6] = {{0, 0}};
if (v != e || !(last[0] == s || last[1] == s || if (v != e || !(last[0] == s || last[1] == s ||
@ -37,7 +37,8 @@ void test_assert(const char *file, unsigned line,
// utility functions for traversals // utility functions for traversals
int test_count(void *p, lfs_block_t b) {{ static int __attribute__((used)) test_count(void *p, lfs_block_t b) {{
(void)b;
unsigned *u = (unsigned*)p; unsigned *u = (unsigned*)p;
*u += 1; *u += 1;
return 0; return 0;
@ -58,7 +59,7 @@ lfs_size_t size;
lfs_size_t wsize; lfs_size_t wsize;
lfs_size_t rsize; lfs_size_t rsize;
uintmax_t res; uintmax_t test;
#ifndef LFS_READ_SIZE #ifndef LFS_READ_SIZE
#define LFS_READ_SIZE 16 #define LFS_READ_SIZE 16
@ -96,7 +97,7 @@ const struct lfs_config cfg = {{
// Entry point // Entry point
int main() {{ int main(void) {{
lfs_emubd_create(&cfg, "blocks"); lfs_emubd_create(&cfg, "blocks");
{tests} {tests}

View File

@ -14,21 +14,33 @@ def generate(test):
match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE) match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
if match: if match:
tab, test, expect = match.groups() tab, test, expect = match.groups()
lines.append(tab+'res = {test};'.format(test=test.strip())) lines.append(tab+'test = {test};'.format(test=test.strip()))
lines.append(tab+'test_assert("{name}", res, {expect});'.format( lines.append(tab+'test_assert("{name}", test, {expect});'.format(
name = re.match('\w*', test.strip()).group(), name = re.match('\w*', test.strip()).group(),
expect = expect.strip())) expect = expect.strip()))
else: else:
lines.append(line) lines.append(line)
# Create test file
with open('test.c', 'w') as file: with open('test.c', 'w') as file:
file.write(template.format(tests='\n'.join(lines))) file.write(template.format(tests='\n'.join(lines)))
# Remove build artifacts to force rebuild
try:
os.remove('test.o')
os.remove('lfs')
except OSError:
pass
def compile(): def compile():
os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + ' -Werror' subprocess.check_call([
subprocess.check_call(['make', '--no-print-directory', '-s'], env=os.environ) os.environ.get('MAKE', 'make'),
'--no-print-directory', '-s'])
def execute(): def execute():
if 'EXEC' in os.environ:
subprocess.check_call([os.environ['EXEC'], "./lfs"])
else:
subprocess.check_call(["./lfs"]) subprocess.check_call(["./lfs"])
def main(test=None): def main(test=None):

View File

@ -126,7 +126,7 @@ TEST
echo "--- Directory remove ---" echo "--- Directory remove ---"
tests/test.py << TEST tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "potato") => LFS_ERR_INVAL; lfs_remove(&lfs, "potato") => LFS_ERR_NOTEMPTY;
lfs_remove(&lfs, "potato/sweet") => 0; lfs_remove(&lfs, "potato/sweet") => 0;
lfs_remove(&lfs, "potato/baked") => 0; lfs_remove(&lfs, "potato/baked") => 0;
lfs_remove(&lfs, "potato/fried") => 0; lfs_remove(&lfs, "potato/fried") => 0;
@ -220,7 +220,7 @@ tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "warmpotato") => 0; lfs_mkdir(&lfs, "warmpotato") => 0;
lfs_mkdir(&lfs, "warmpotato/mushy") => 0; lfs_mkdir(&lfs, "warmpotato/mushy") => 0;
lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_INVAL; lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_NOTEMPTY;
lfs_remove(&lfs, "warmpotato/mushy") => 0; lfs_remove(&lfs, "warmpotato/mushy") => 0;
lfs_rename(&lfs, "hotpotato", "warmpotato") => 0; lfs_rename(&lfs, "hotpotato", "warmpotato") => 0;
@ -255,7 +255,7 @@ tests/test.py << TEST
lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0; lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0;
lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0; lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0;
lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0; lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0;
lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL; lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
lfs_remove(&lfs, "warmpotato") => 0; lfs_remove(&lfs, "warmpotato") => 0;
lfs_unmount(&lfs) => 0; lfs_unmount(&lfs) => 0;
TEST TEST
@ -285,7 +285,7 @@ TEST
echo "--- Recursive remove ---" echo "--- Recursive remove ---"
tests/test.py << TEST tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL; lfs_remove(&lfs, "coldpotato") => LFS_ERR_NOTEMPTY;
lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0; lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1; lfs_dir_read(&lfs, &dir[0], &info) => 1;
@ -328,7 +328,7 @@ TEST
echo "--- Multi-block remove ---" echo "--- Multi-block remove ---"
tests/test.py << TEST tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0; lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "cactus") => LFS_ERR_INVAL; lfs_remove(&lfs, "cactus") => LFS_ERR_NOTEMPTY;
for (int i = 0; i < $LARGESIZE; i++) { for (int i = 0; i < $LARGESIZE; i++) {
sprintf((char*)buffer, "cactus/test%d", i); sprintf((char*)buffer, "cactus/test%d", i);

View File

@ -0,0 +1,158 @@
#!/bin/bash
set -eu
SMALLSIZE=32
MEDIUMSIZE=2048
LARGESIZE=8192
echo "=== Truncate tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
truncate_test() {
STARTSIZES="$1"
STARTSEEKS="$2"
HOTSIZES="$3"
COLDSIZES="$4"
tests/test.py << TEST
static const lfs_off_t startsizes[] = {$STARTSIZES};
static const lfs_off_t startseeks[] = {$STARTSEEKS};
static const lfs_off_t hotsizes[] = {$HOTSIZES};
lfs_mount(&lfs, &cfg) => 0;
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
sprintf((char*)buffer, "hairyhead%d", i);
lfs_file_open(&lfs, &file[0], (const char*)buffer,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0;
strcpy((char*)buffer, "hair");
size = strlen((char*)buffer);
for (int j = 0; j < startsizes[i]; j += size) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_size(&lfs, &file[0]) => startsizes[i];
if (startseeks[i] != startsizes[i]) {
lfs_file_seek(&lfs, &file[0],
startseeks[i], LFS_SEEK_SET) => startseeks[i];
}
lfs_file_truncate(&lfs, &file[0], hotsizes[i]) => 0;
lfs_file_size(&lfs, &file[0]) => hotsizes[i];
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
static const lfs_off_t startsizes[] = {$STARTSIZES};
static const lfs_off_t hotsizes[] = {$HOTSIZES};
static const lfs_off_t coldsizes[] = {$COLDSIZES};
lfs_mount(&lfs, &cfg) => 0;
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
sprintf((char*)buffer, "hairyhead%d", i);
lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDWR) => 0;
lfs_file_size(&lfs, &file[0]) => hotsizes[i];
size = strlen("hair");
int j = 0;
for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
for (; j < hotsizes[i]; j += size) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "\0\0\0\0", size) => 0;
}
lfs_file_truncate(&lfs, &file[0], coldsizes[i]) => 0;
lfs_file_size(&lfs, &file[0]) => coldsizes[i];
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
static const lfs_off_t startsizes[] = {$STARTSIZES};
static const lfs_off_t hotsizes[] = {$HOTSIZES};
static const lfs_off_t coldsizes[] = {$COLDSIZES};
lfs_mount(&lfs, &cfg) => 0;
for (int i = 0; i < sizeof(startsizes)/sizeof(startsizes[0]); i++) {
sprintf((char*)buffer, "hairyhead%d", i);
lfs_file_open(&lfs, &file[0], (const char*)buffer, LFS_O_RDONLY) => 0;
lfs_file_size(&lfs, &file[0]) => coldsizes[i];
size = strlen("hair");
int j = 0;
for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
j += size) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "hair", size) => 0;
}
for (; j < coldsizes[i]; j += size) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "\0\0\0\0", size) => 0;
}
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
}
echo "--- Cold shrinking truncate ---"
truncate_test \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE"
echo "--- Cold expanding truncate ---"
truncate_test \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
echo "--- Warm shrinking truncate ---"
truncate_test \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, 0, 0, 0, 0"
echo "--- Warm expanding truncate ---"
truncate_test \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
echo "--- Mid-file shrinking truncate ---"
truncate_test \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
" $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE, $LARGESIZE" \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, 0, 0, 0, 0"
echo "--- Mid-file expanding truncate ---"
truncate_test \
" 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE, 2*$LARGESIZE" \
" 0, 0, $SMALLSIZE, $MEDIUMSIZE, $LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE" \
"2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE, 2*$LARGESIZE"
echo "--- Results ---"
tests/stats.py