Commit Graph

369 Commits

Author SHA1 Message Date
Christopher Haster
bf78b09d37 Added directory list for synchronizing in flight directories
As it was, if a user operated on a directory while at the same
time iterating over the directory, the directory objects could
fall out of sync. In the best case, files may be skipped while
removing everything in a file, in the worst case, a very poorly
timed directory relocate could be missed.

Simple fix is to add the same directory tracking that is currently
in use for files, at a small code+complexity cost.
2017-11-22 14:49:43 -06:00
Christopher Haster
f9f4f5ccec Fixed standard name mismatch LFS_ERR_EXISTS -> LFS_ERR_EXIST
Matches the standard EEXIST name found on most systems. Other than
this name, all other common constant names were consistent in this
manner.
2017-11-16 17:50:14 -06:00
Christopher Haster
843e3c6c75 Added sticky-bit for preventing file syncs after write errors
Short story, files are no longer committed to directories during
file sync/close if the last write did not complete successfully.
This avoids a set of interesting user-experience issues related
to the end-of-life behaviour of the filesystem.

As a filesystem approaches end-of-life, the chances of running into
LFS_ERR_NOSPC grows rather quickly. Since this condition occurs after
at the end of a devices life, it's likely that operating in these
conditions hasn't been tested thoroughly.

In the specific case of file-writes, you can hit an LFS_ERR_NOSPC after
parts of the file have been written out. If the program simply continues
and closes the file, the file is written out half completed. Since
littlefs has a strong garuntee the prevents half-writes, it's unlikely
this state of the file would be expected.

To make things worse, since close is also responsible for memory
cleanup, it's actually _impossible_ to continue working as it was
without leaking memory.

By prevent the file commits, end-of-life behaviour should at least retain
a previous copy of the filesystem without any surprises.
2017-11-16 17:25:41 -06:00
Christopher Haster
2612e1b3fa Modified lfs_ctz_extend to be a little bit safer
Specifically around error handling. As is, incorrectly handled
errors could cause higher code to get uninitialized blocks,
potentially leading to writes to arbitray blocks on storage.
2017-11-16 15:10:17 -06:00
Christopher Haster
6664723e18 Fixed issue with committing directories to bad-blocks that are stuck
This is only an issue in the weird case that are worn down block is
left in the odd state of not being able to change the data that resides
on the block. That being said, this does pop up often when simulating
wear on block devices.

Currently, directory commits checked if the write succeeded by crcing the
block to avoid the additional RAM cost for another buffer. However,
before this commit, directory commits just checked if the block crc was
valid, rather than comparing to the expected crc. This would usually
work, unless the block was stuck in a state with valid crc.

The fix is to simply compare with the expected crc to find errors.
2017-11-16 14:53:45 -06:00
Christopher Haster
3f31c8cba3 Fixed corner case with immediate exhaustion and lookahead==block_count
The previous math for determining if we scanned all of disk wasn't set
up correctly in the lfs_mount function. If lookahead == block_count
the lfs_alloc function would think we had already searched the entire
disk.

This is only an issue if we manage to exhaust a block on the first
pass after mount, since lfs_alloc_ack resets the lookahead region
into a valid state after a succesful block allocation.
2017-11-10 10:53:33 -06:00
Christopher Haster
f4aeb8331a Fixed issue with aggressively rounding down lookahead configuration
The littlefs allows buffers to be passed statically in the case
that a system does not have a heap. Unfortunately, this means we
can't round up in the case of an unaligned lookahead buffer.

Double unfortunately, rounding down after clamping to the block device
size could result in a lookahead of zero for block devices < 32 blocks
large.

The assert in littlefs does catch this case, but rounding down prevents
support for < 32 block devices.

The solution is to simply require a 32-bit aligned buffer with an
assert. This avoids runtime problems while allowing a user to pass
in the correct buffer for < 32 block devices. Rounding up can be
handled at higher API levels.
2017-11-10 10:53:30 -06:00
Christopher Haster
db51a395ba Removed stray newline in LFS_ERROR for version 2017-11-09 19:44:23 -06:00
Christopher Haster
2ab150cc50 Removed toolchain specific warnings
- Comparisons with differently signed integer types
- Incorrectly signed constant
- Unreachable default returns
- Leaked uninitialized variables in relocate goto statements
2017-10-30 16:55:45 -05:00
Christopher Haster
0825d34f3d Adopted alternative implementation for lfs_ctz_index
Same runtime cost, however reduces the logic and avoids one of
the two big branches. See the DESIGN.md for more info.

Now uses these equations instead of the messy guess and correct method:
n = (N - w/8(popcount(N/(B-2w/8)) + 2)) / (B-2w/8)
off = N - (B-w2/8)n - w/8popcount(n)
2017-10-30 12:03:33 -05:00
Christopher Haster
46e22b2a38 Adopted lfs_ctz_index implementation using popcount
This reduces the O(n^2logn) runtime to read a file to only O(nlog).
The extra O(n) did not touch the disk, so it isn't a problem until the
files become very large, but this solution comes with very little cost.

Long story short, you can find the block index + offset pair for a
CTZ linked-list with this series of formulas:

n' = floor(N / (B - 2w/8))
N' = (B - 2w/8)n' + (w/8)popcount(n')
off' = N - N'
n, off =
  n'-1, off'+B                if off' <  0
  n',   off'+(w/8)(ctz(n')+1) if off' >= 0

For the long story, you will need to see the updated DESIGN.md
2017-10-18 00:44:30 -05:00
Christopher Haster
4fdca15a0d Slight name change with ctz skip-list functions
changed:
lfs_index -> lfs_ctz_index
lfs_index_find -> lfs_ctz_find
lfs_index_append -> lfs_ctz_append
lfs_index_traverse -> lfs_ctz_traverse
2017-10-18 00:41:43 -05:00
Christopher Haster
f3578e3250 Removed clamping to block size in ctz linked-list
Initially, I was concerned that the number of pointers in the ctz
linked-list could exceed the storage in a block. Long story short
this isn't really possible outside of extremely small block sizes.

Since clamping impacts the layout of files on disk, removing the
block size removed quite a bit of logic and corner cases. Replaced
with an assert on block size during initialization.

---

Long story long, the minimum block size needed to store all ctz
pointers in a filesystem can be found with this formula:

B = (w/8)*log2(2^w / (B - 2*(w/8)))

where:
B = block size in bytes
w = pointer width in bits

It's not a very pretty formula, but does give us some useful info
if we apply some math:

min block size:
32 bit ctz linked-list = 104 bytes
64 bit ctz linked-list = 448 bytes

For littlefs, 128 bytes is a perfectly reasonable minimum block size.
2017-10-12 20:31:33 -05:00
Christopher Haster
83d4c614a0 Updated copyright
Due to employee contract
Per ARM license remains under Apache 2.0
2017-10-12 20:29:10 -05:00
Christopher Haster
539409e2fb Refactored deduplicate/deorphan step to single deorphan step
Deduplication and deorphan steps aren't required under indentical
conditions, but they can be processed in the same iteration of the
filesystem. Since lfs_alloc (requires deorphan) occurs on most write
calls to the filesystem (requires deduplication), it was simpler to
just compine the steps into a single lfs_deorphan step.

Also traded out the places where lfs_rename/lfs_remove just defer
operations to the deorphan step. This adds a bit of code, but also
significantly speeds up directory operations.
2017-10-10 17:14:46 -05:00
Christopher Haster
2936514b5e Added atomic move using dirty tag in entry type
The "move problem" has been present in littlefs for a while, but I haven't
come across a solution worth implementing for various reasons.

The problem is simple: how do we move directory entries across
directories atomically? Since multiple directory entries are involved,
we can't rely entirely on the atomic block updates. It ends up being
a bit of a puzzle.

To make the problem more complicated, any directory block update can
fail due to wear, and cause the directory block to need to be relocated.
This happens rarely, but brings a large number of corner cases.

---

The solution in this patch is simple:
1. Mark source as "moving"
2. Copy source to destination
3. Remove source

If littlefs ever runs into a "moving" entry, that means a power loss
occured during a move. Either the destination entry exists or it
doesn't. In this case we just search the entire filesystem for the
destination entry.

This is expensive, however the chance of a power loss during a move
is relatively low.
2017-10-10 06:18:46 -05:00
Christopher Haster
984340225b Fixed incorrect return value from lfs_file_seek
lfs_file_seek returned the _previous_ file offset on success, where
most standards return the _calculated_ offset on success.

This just falls into me not noticing a mistake, and shows why it's
always helpful to have a second set of eyes on code.
2017-09-26 19:50:39 -05:00
Christopher Haster
273cb7c9c8 Fixed problem with lookaheads larger than block device
Simply limiting the lookahead region to the size of
the block device fixes the problem.

Also added logic to limit the allocated region and
floor to nearest word, since the additional memory
couldn't really be used effectively.
2017-09-18 21:20:33 -05:00
Christopher Haster
d9367e05ce Fixed collection of multiblock directories
Moslty just a hole in testing. Dir blocks were not being
correctly collected when removing entries from very large
files due to forgetting about the tail-bit in the directory
block size. The test hole has now been filled.

Also added lfs_entry_size to avoid having to repeat that
expression since it is a bit ridiculous
2017-09-17 20:38:54 -05:00
Christopher Haster
a83b2fe463 Added checks for out-of-bound seeks
- out-of-bound read results in eof
- out-of-bound write will fill missing area with zeros

The write behaviour matches expected posix behaviour, but was
under consideration for not being dropped, since littlefs does
not support holes, and support of out-of-band seeks adds complexity.

However, it turned out filling with zeros was trivial, and only
cost an extra 74 bytes of flash (0.48%).
2017-09-17 18:07:08 -05:00
Christopher Haster
a8fa5e6571 Fixed some corner cases with paths
- Added handling for root to lfs_stat
- Corrected lfs_dir_find to update path even on failures
- Added more checks for missing directories in path
2017-09-17 16:51:07 -05:00
Christopher Haster
26dd49aa04 Fixed issue with negative modulo with unaligned lookaheads
When the lookahead buffer wraps around in an unaligned filesystem, it's
possible for blocks at the beginning of the disk to have a negative distance
from the lookahead, but still reside in the lookahead buffer.

Switching to signed modulo doesn't quite work due to how negative modulo
is implemented in C, so the simple solution is to shift the region to be
positive.
2017-09-17 16:51:07 -05:00
Christopher Haster
0982020fb3 Fixed issue with cold-write after seek to block boundary
This off-by-one error was caused by a slight difference between the
pos argument to lfs_index_find and lfs_index_extend. When pos is on
a block boundary, lfs_index_extend expects block to point before pos,
as it would when writing a file linearly. But when seeking to that
pos, the lfs_index_find to warm things up just supplies the block it
expects pos to be in.

Fixed the off-by-one error and added a test case for several of these
cold seek+writes.
2017-09-17 16:51:04 -05:00
Christopher Haster
c2283a2753 Extended entry tag to support attributes
Zero attributes are actually supported at the moment, but this change
will allow entry attribute to be added in a backwards compatible manner.

Each dir entry is now prefixed with a 32 bit tag:
4b - entry type
4b - data structure
8b - entry len
8b - attribute len
8b - name len

A full entry on disk looks a bit like this:
[-  8 -|-  8 -|-  8 -|-  8 -|-- elen --|-- alen --|-- nlen --]
[ type | elen | alen | nlen |  entry   |  attrs   |   name   ]

The actually contents of the attributes section is a bit handwavey
until the first attributes are implemented, but to put plans in place:
Each attribute will be prefixed with only a byte that indicates the type
of attribute. Attributes should be sorted based on portability, since
unknown attributes will force attribute parsing to stop.
2017-07-18 02:09:35 -05:00
Christopher Haster
663e953a50 Adopted the Apache 2.0 license 2017-07-08 11:49:40 -05:00
Christopher Haster
8a9b9baa12 Modified entry head to include name length
This provides a path for adding inlined files in the future, which
requires multiple lengths to distinguish between the file data and name.

As an extra bonus, the directory can now be iterated over even if the
types are unknown, since the name's representation is consistent on all
entry types.

This does come at the cost of reducing types from 16-bits to 8-bits, but
I doubt this will become a problem.
2017-06-28 21:46:34 -05:00
Christopher Haster
931442a784 Adopted redundant cache read in lfs_file_relocate
Previously had some custom logic that could be reduced
2017-06-28 21:46:02 -05:00
Christopher Haster
0e1022a86c Fixed missing erase during file relocation
This was an easy fix, but highlighted the fact that the current testing
framework doesn't detect when a block is written to without an
associated erase.

Added a quick solution that creates an empty file during an erase.
2017-06-28 21:45:45 -05:00
Christopher Haster
a1138a41ce Fixed dirty rcache during directory commit
An interesting side-effect of adding internal checks to the littlefs
for block errors, is that the littlefs starts to cover up its own
flaws. Probably out of embarrassment.

In this case, the relocation logic for directories left the littlefs
rcache dirty with invalid data. The littlefs detected the error,
treated it as a corrupted write, and just moved the "corrupted" block
to a new block, which as a side-effect flushes the rcache.

Since committing a dir will end up flushing the rcache to check for
errors anyways, we can just drop the rcache in lfs_bd_sync.
2017-06-28 21:45:28 -05:00
Christopher Haster
11664160a8 Fixed relocation bug when a file is closed with lingering caches
This bug required larger cache sizes to notice, since block errors
usually get detected in the early stages of writing to files.

Since the fix here requires both lfs_file_write and lfs_file_flush
relocate file blocks, the relocation logic was moved out into a
seperate function.
2017-06-28 21:44:56 -05:00
Christopher Haster
fe28ea0f93 Added internal check of data written to disk
Before, the littlefs relied on the underlying block device
to report corruption that occurs when writing data to disk.
This requirement is easy to miss or implement incorrectly, since
the error detection is only required when a block becomes corrupted,
which is very unlikely to happen until late in the block device's
lifetime.

The littlefs can detect corruption itself by reading back written data.
This requires a bit of care to reuse the available buffers, and may rely
on checksums to avoid additional RAM requirements.

This does have a runtime penalty with the extra read operations, but
should make the littlefs much more robust to different implementations.
2017-06-28 15:50:47 -05:00
Christopher Haster
1eeb2a6811 Shrinked on-disk directory program size
Directories still consume two full erase blocks, but now only program
the exact on-disk region to store the directory contents. This results
in a decent improvement in the amount of data written and read to the
device when doing directory operations.

Calculating the checksum of dynamically sized data is surprisingly
tricky, since the size of the data could also contain errors. For the
littlefs, we can assume the data size must fit in an erase block.
If the data size is invalid, we can just treat the block as corrupted.
2017-06-28 15:50:40 -05:00
Christopher Haster
fd1da602d7 Added support for handling corrupted blocks
This provides a limited form of wear leveling. While wear is
not actually balanced across blocks, the filesystem can recover
from corrupted blocks and extend the lifetime of a device nearly
as much as dynamic wear leveling.

For use-cases where wear is important, it would be better to use
a full form of dynamic wear-leveling at the block level. (or
consider a logging filesystem).

Corrupted block handling was simply added on top of the existing
logic in place for the filesystem, so it's a bit more noodly than
it may have to be, but it gets the work done.
2017-05-15 00:40:56 -05:00
Christopher Haster
b35d761196 Removed words variable from lfs struct 2017-05-08 00:53:08 -05:00
Christopher Haster
63b52c9f2e Added proper handling for removing open files
Conveniently we previously added a linked-list of files
for things like this. This should handle most of the corner
cases where files are open during strange operations.

This also brings up the point that we aren't doing anything similar
for directories and don't even have a dir linked-list. After thinking
about it for a while, I've decided to leave out this handling for dirs.
It will likely be very complicated, with little gains as directories
are less used in embedded systems. Additionally, dirs are only open
for reading, and corruption will probably just cause the dir iteration
to terminate. If needed, correct handling of open directories can be
added later.
2017-05-08 00:48:28 -05:00
Christopher Haster
8621b61f38 Adopted 0xffffffff as null pointer
- Default value of most flash-based storage
- Avoids 0 == superblock/dir issue
- Usually causes assertions in bd driver layer
- Easier to notice in hex dumps
2017-05-08 00:46:10 -05:00
Christopher Haster
4808e9ae26 Added caching with managed caches at the file level
This adds a fully independent layer between the rest of the filesystem
and the block device. This requires some additionally logic around cache
invalidation and flushing, but removes the need for any higher layer to
consider read/write sizes less than what is supported by the hardware.

Additionally, these caches can be used for possible speed improvements.
This is left up to the user to optimize for their use cases. For very
limited embedded systems with byte-level read/writes, the caches could
be omitted completely, or they could even be the size of a full block
for minimizing storage access.

(A full block may not be the best for speed, consider if only a small
portion of the read block is used, but I'll leave that evaluation as an
exercise for any consumers of this library)
2017-05-08 00:44:54 -05:00
Christopher Haster
6869b14694 Fixed memory leak for lookahead buffer 2017-05-08 00:38:20 -05:00
Christopher Haster
a30142e0e1 Fixed allocation bugs near the end of storage
Also added better testing specifically for these corner cases
around the end of storage
2017-05-08 00:37:58 -05:00
Christopher Haster
210b487325 Added file list for tracking in flight allocations
Needed primarily for tracking block allocations, unfortunately
this prevents the freedom for the user to bitwise copy files.
2017-05-08 00:32:32 -05:00
Christopher Haster
b55719bab1 Adopted more conventional buffer parameter ordering
Adopted buffer followed by size. The other order was original
chosen due to some other functions with a more complicated
parameter list.

This convention is important, as the bd api is one of the main
apis facing porting efforts.
2017-04-23 23:58:43 -05:00
Christopher Haster
0406442253 Fixed non-standard behaviour of rdwr streams
Originally had two seperate positions for reading/writing,
but this is inconsistent with the the posix standard, which
has a single position for reading and writing.

Also added proper handling of when the file is dirty, just
added an internal flag for this state.

Also moved the entry out of the file struct, and rearranged
some members to clean things up.
2017-04-23 23:39:50 -05:00
Christopher Haster
287b54876e Standardized error values
Now matches the commonly used errno codes in name with the value
encoded as the negative errno code
2017-04-23 22:10:16 -05:00
Christopher Haster
5790ec2ce4 Structured some of the bulk of the codebase
- Removed lfs_config.h, distributed between lfs.h and lfs_util.h
- Moved some functions that felt out of place
2017-04-23 21:40:03 -05:00
Christopher Haster
ba8afb9d92 Added support for full seek operations
A rather involved upgrade for both files and directories, seek and
related functions are now completely supported:
- lfs_file_seek
- lfs_file_tell
- lfs_file_rewind
- lfs_file_size
- lfs_dir_seek
- lfs_dir_tell
- lfs_dir_rewind

This change also highlighted the concern that lfs_off_t is unsigned,
whereas off_t is traditionally signed. Unfortunately, lfs_off_t is
already used intensively through the codebase, so in focusing on
moving forward and avoiding getting bogged down by details, I'm going to
keep it as is and use the signed type lfs_soff_t where necessary.
2017-04-23 02:06:48 -05:00
Christopher Haster
a1d8a76b36 Added correct handling of file syncing around overwrites
Now all of the open flags are correctly handled

Even annoying cases where we can't trust the blocks that are already
on file, such as appending existing files and writing to the middle
of files.
2017-04-22 21:42:22 -05:00
Christopher Haster
a4e9132d7f Removed a layer of indirection for index-list lookup
Files are now stored directly in the index-list, instead of being
referenced by pointers that used to live there. This somewhat reduces
the complexity around handling files, while still keeping the O(logn)
lookup cost.
2017-04-22 19:48:31 -05:00
Christopher Haster
aa872657d2 Cleaned up block allocator
Removed scanning for stride
- Adds complexity with questionable benefit
- Can be added as an optimization later

Fixed handling around device boundaries and where lookahead may not be a
factor of the device size (consider small devices with only a few
blocks)

Added support for configuration with optional dynamic memory as found in
the caching configuration
2017-04-22 16:00:45 -05:00
Christopher Haster
7050922623 Added optional block-level caching
This adds caching of the most recent read/program blocks, allowing
support of devices that don't have byte-level read+writes, along
with reduced device access on devices that do support byte-level
read+writes.

Note: The current implementation is a bit eager to drop caches where
it simplifies the cache layer. This layer is already complex enough.

Note: It may be worthwhile to add a compile switch for caching to
reduce code size, note sure.

Note: This does add a dependency on malloc, which could have a porting
layer, but I'm just using the functions from stdlib for now. These can be
overwritten with noops if the user controls the system, and keeps things
simple for now.
2017-04-22 16:00:40 -05:00
Christopher Haster
789286a257 Simplified config
Before, the lfs had multiple paths to determine config options:
- lfs_config struct passed during initialization
- lfs_bd_info struct passed during block device initialization
- compile time options

This allowed different developers to provide their own needs
to the filesystem, such as the block device capabilities and
the higher level user's own tweaks.

However, this comes with additional complexity and action required
when the configurations are incompatible.

For now, this has been reduced to all information (including block
device function pointers) being passed through the lfs_config struct.
We just defer more complicated handling of configuration options to
the top level user.

This simplifies configuration handling and gives the top level user
the responsibility to handle configuration, which they probably would
have wanted to do anyways.
2017-04-22 15:42:05 -05:00