mirror of
https://github.com/eledio-devices/thirdparty-littlefs.git
synced 2025-11-01 08:48:31 +01:00
Compare commits
12 Commits
v1.0
...
rev-sum-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b24ddac95e | ||
|
|
539409e2fb | ||
|
|
2936514b5e | ||
|
|
ac9766ee39 | ||
|
|
9db1a86498 | ||
|
|
984340225b | ||
|
|
273cb7c9c8 | ||
|
|
d9367e05ce | ||
|
|
a83b2fe463 | ||
|
|
a8fa5e6571 | ||
|
|
26dd49aa04 | ||
|
|
0982020fb3 |
36
.travis.yml
36
.travis.yml
@@ -9,6 +9,40 @@ script:
|
||||
-include stdio.h -Werror' make all size
|
||||
|
||||
# run tests
|
||||
- CFLAGS="-DLFS_READ_SIZE=16 -DLFS_PROG_SIZE=16" make test
|
||||
- make test
|
||||
|
||||
# run tests with a few different configurations
|
||||
- CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test
|
||||
- CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test
|
||||
- CFLAGS="-DLFS_BLOCK_COUNT=1023" make test
|
||||
- CFLAGS="-DLFS_LOOKAHEAD=2047" make test
|
||||
|
||||
# self-host with littlefs-fuse for fuzz test
|
||||
- make -C littlefs-fuse
|
||||
|
||||
- littlefs-fuse/lfs --format /dev/loop0
|
||||
- littlefs-fuse/lfs /dev/loop0 mount
|
||||
|
||||
- ls mount
|
||||
- mkdir mount/littlefs
|
||||
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
|
||||
- cd mount/littlefs
|
||||
- ls
|
||||
- make -B test_dirs
|
||||
|
||||
before_install:
|
||||
- fusermount -V
|
||||
- gcc --version
|
||||
|
||||
install:
|
||||
- sudo apt-get install libfuse-dev
|
||||
- git clone --depth 1 https://github.com/geky/littlefs-fuse
|
||||
|
||||
before_script:
|
||||
- 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
|
||||
|
||||
2
Makefile
2
Makefile
@@ -32,7 +32,7 @@ size: $(OBJ)
|
||||
|
||||
.SUFFIXES:
|
||||
test: test_format test_dirs test_files test_seek test_parallel \
|
||||
test_alloc test_paths test_orphan test_corrupt
|
||||
test_alloc test_paths test_orphan test_move test_corrupt
|
||||
test_%: tests/test_%.sh
|
||||
./$<
|
||||
|
||||
|
||||
@@ -131,7 +131,8 @@ the littlefs was developed with the goal of learning more about filesystem
|
||||
design by tackling the relative unsolved problem of managing a robust
|
||||
filesystem resilient to power loss on devices with limited RAM and ROM.
|
||||
More detail on the solutions and tradeoffs incorporated into this filesystem
|
||||
can be found in [DESIGN.md](DESIGN.md).
|
||||
can be found in [DESIGN.md](DESIGN.md). The specification for the layout
|
||||
of the filesystem on disk can be found in [SPEC.md](SPEC.md).
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
361
SPEC.md
Normal file
361
SPEC.md
Normal file
@@ -0,0 +1,361 @@
|
||||
## The little filesystem technical specification
|
||||
|
||||
This is the technical specification of the little filesystem. This document
|
||||
covers the technical details of how the littlefs is stored on disk for
|
||||
introspection and tooling development. This document assumes you are
|
||||
familiar with the design of the littlefs, for more info on how littlefs
|
||||
works check out [DESIGN.md](DESIGN.md).
|
||||
|
||||
```
|
||||
| | | .---._____
|
||||
.-----. | |
|
||||
--|o |---| littlefs |
|
||||
--| |---| |
|
||||
'-----' '----------'
|
||||
| | |
|
||||
```
|
||||
|
||||
## Some important details
|
||||
|
||||
- The littlefs is a block-based filesystem. This is, the disk is divided into
|
||||
an array of evenly sized blocks that are used as the logical unit of storage
|
||||
in littlefs. Block pointers are stored in 32 bits.
|
||||
|
||||
- There is no explicit free-list stored on disk, the littlefs only knows what
|
||||
is in use in the filesystem.
|
||||
|
||||
- The littlefs uses the value of 0xffffffff to represent a null block-pointer.
|
||||
|
||||
- All values in littlefs are stored in little-endian byte order.
|
||||
|
||||
## Directories / Metadata pairs
|
||||
|
||||
Metadata pairs form the backbone of the littlefs and provide a system for
|
||||
atomic updates. Even the superblock is stored in a metadata pair.
|
||||
|
||||
As their name suggests, a metadata pair is stored in two blocks, with one block
|
||||
acting as a redundant backup in case the other is corrupted. These two blocks
|
||||
could be anywhere in the disk and may not be next to each other, so any
|
||||
pointers to directory pairs need to be stored as two block pointers.
|
||||
|
||||
Here's the layout of metadata blocks on disk:
|
||||
|
||||
| offset | size | description |
|
||||
|--------|---------------|----------------|
|
||||
| 0x00 | 32 bits | revision count |
|
||||
| 0x04 | 32 bits | dir size |
|
||||
| 0x08 | 64 bits | tail pointer |
|
||||
| 0x10 | size-16 bytes | dir entries |
|
||||
| 0x00+s | 32 bits | crc |
|
||||
|
||||
**Revision count** - Incremented every update, only the uncorrupted
|
||||
metadata-block with the most recent revision count contains the valid metadata.
|
||||
Comparison between revision counts must use sequence comparison since the
|
||||
revision counts may overflow.
|
||||
|
||||
**Dir size** - Size in bytes of the contents in the current metadata block,
|
||||
including the metadata-pair metadata. Additionally, the highest bit of the
|
||||
dir size may be set to indicate that the directory's contents continue on the
|
||||
next metadata-pair pointed to by the tail pointer.
|
||||
|
||||
**Tail pointer** - Pointer to the next metadata-pair in the filesystem.
|
||||
A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list.
|
||||
If the highest bit in the dir size is set, this points to the next
|
||||
metadata-pair in the current directory, otherwise it points to an arbitrary
|
||||
metadata-pair. Starting with the superblock, the tail-pointers form a
|
||||
linked-list containing all metadata-pairs in the filesystem.
|
||||
|
||||
**CRC** - 32 bit CRC used to detect corruption from power-lost, from block
|
||||
end-of-life, or just from noise on the storage bus. The CRC is appended to
|
||||
the end of each metadata-block. The littlefs uses the standard CRC-32, which
|
||||
uses a polynomial of 0x04c11db7, initialized with 0xffffffff.
|
||||
|
||||
Here's an example of a simple directory stored on disk:
|
||||
```
|
||||
(32 bits) revision count = 10 (0x0000000a)
|
||||
(32 bits) dir size = 154 bytes, end of dir (0x0000009a)
|
||||
(64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024)
|
||||
(32 bits) crc = 0xc86e3106
|
||||
|
||||
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"
|
||||
00000020: 08 00 06 07 00 00 00 06 00 00 00 63 6f 66 66 65 ...........coffe
|
||||
00000030: 65 22 08 00 04 09 00 00 00 08 00 00 00 73 6f 64 e"...........sod
|
||||
00000040: 61 22 08 00 05 1d 00 00 00 1c 00 00 00 6d 69 6c a"...........mil
|
||||
00000050: 6b 31 22 08 00 05 1f 00 00 00 1e 00 00 00 6d 69 k1"...........mi
|
||||
00000060: 6c 6b 32 22 08 00 05 21 00 00 00 20 00 00 00 6d lk2"...!... ...m
|
||||
00000070: 69 6c 6b 33 22 08 00 05 23 00 00 00 22 00 00 00 ilk3"...#..."...
|
||||
00000080: 6d 69 6c 6b 34 22 08 00 05 25 00 00 00 24 00 00 milk4"...%...$..
|
||||
00000090: 00 6d 69 6c 6b 35 06 31 6e c8 .milk5.1n.
|
||||
```
|
||||
|
||||
A note about the tail pointer linked-list: Normally, this linked-list is
|
||||
threaded through the entire filesystem. However, after power-loss this
|
||||
linked-list may become out of sync with the rest of the filesystem.
|
||||
- The linked-list may contain a directory that has actually been removed
|
||||
- The linked-list may contain a metadata pair that has not been updated after
|
||||
a block in the pair has gone bad.
|
||||
|
||||
The threaded linked-list must be checked for these errors before it can be
|
||||
used reliably. Fortunately, the threaded linked-list can simply be ignored
|
||||
if littlefs is mounted read-only.
|
||||
|
||||
## Entries
|
||||
|
||||
Each metadata block contains a series of entries that follow a standard
|
||||
layout. An entry contains the type of the entry, along with a section for
|
||||
entry-specific data, attributes, and a name.
|
||||
|
||||
Here's the layout of entries on disk:
|
||||
|
||||
| offset | size | description |
|
||||
|---------|------------------------|----------------------------|
|
||||
| 0x0 | 8 bits | entry type |
|
||||
| 0x1 | 8 bits | entry length |
|
||||
| 0x2 | 8 bits | attribute length |
|
||||
| 0x3 | 8 bits | name length |
|
||||
| 0x4 | entry length bytes | entry-specific data |
|
||||
| 0x4+e | attribute length bytes | system-specific attributes |
|
||||
| 0x4+e+a | name length bytes | entry name |
|
||||
|
||||
**Entry type** - Type of the entry, currently this is limited to the following:
|
||||
- 0x11 - file entry
|
||||
- 0x22 - directory entry
|
||||
- 0xe2 - superblock entry
|
||||
|
||||
Additionally, the type is broken into two 4 bit nibbles, with the lower nibble
|
||||
specifying the type's data structure used when scanning the filesystem. The
|
||||
upper nibble clarifies the type further when multiple entries share the same
|
||||
data structure.
|
||||
|
||||
**Entry length** - Length in bytes of the entry-specific data. This does
|
||||
not include the entry type size, attributes, or name. The full size in bytes
|
||||
of the entry is 4 + entry length + attribute length + name length.
|
||||
|
||||
**Attribute length** - Length of system-specific attributes in bytes. Since
|
||||
attributes are system specific, there is not much garuntee on the values in
|
||||
this section, and systems are expected to work even when it is empty. See the
|
||||
[attributes](#entry-attributes) section for more details.
|
||||
|
||||
**Name length** - Length of the entry name. Entry names are stored as utf8,
|
||||
although most systems will probably only support ascii. Entry names can not
|
||||
contain '/' and can not be '.' or '..' as these are a part of the syntax of
|
||||
filesystem paths.
|
||||
|
||||
Here's an example of a simple entry stored on disk:
|
||||
```
|
||||
(8 bits) entry type = file (0x11)
|
||||
(8 bits) entry length = 8 bytes (0x08)
|
||||
(8 bits) attribute length = 0 bytes (0x00)
|
||||
(8 bits) name length = 12 bytes (0x0c)
|
||||
(8 bytes) entry data = 05 00 00 00 20 00 00 00
|
||||
(12 bytes) entry name = smallavacado
|
||||
|
||||
00000000: 11 08 00 0c 05 00 00 00 20 00 00 00 73 6d 61 6c ........ ...smal
|
||||
00000010: 6c 61 76 61 63 61 64 6f lavacado
|
||||
```
|
||||
|
||||
## Superblock
|
||||
|
||||
The superblock is the anchor for the littlefs. The superblock is stored as
|
||||
a metadata pair containing a single superblock entry. It is through the
|
||||
superblock that littlefs can access the rest of the filesystem.
|
||||
|
||||
The superblock can always be found in blocks 0 and 1, however fetching the
|
||||
superblock requires knowing the block size. The block size can be guessed by
|
||||
searching the beginning of disk for the string "littlefs", although currently
|
||||
the filesystems relies on the user providing the correct block size.
|
||||
|
||||
The superblock is the most valuable block in the filesystem. It is updated
|
||||
very rarely, only during format or when the root directory must be moved. It
|
||||
is encouraged to always write out both superblock pairs even though it is not
|
||||
required.
|
||||
|
||||
Here's the layout of the superblock entry:
|
||||
|
||||
| offset | size | description |
|
||||
|--------|------------------------|----------------------------------------|
|
||||
| 0x00 | 8 bits | entry type (0xe2 for superblock entry) |
|
||||
| 0x01 | 8 bits | entry length (20 bytes) |
|
||||
| 0x02 | 8 bits | attribute length |
|
||||
| 0x03 | 8 bits | name length (8 bytes) |
|
||||
| 0x04 | 64 bits | root directory |
|
||||
| 0x0c | 32 bits | block size |
|
||||
| 0x10 | 32 bits | block count |
|
||||
| 0x14 | 32 bits | version |
|
||||
| 0x18 | attribute length bytes | system-specific attributes |
|
||||
| 0x18+a | 8 bytes | magic string ("littlefs") |
|
||||
|
||||
**Root directory** - Pointer to the root directory's metadata pair.
|
||||
|
||||
**Block size** - Size of the logical block size used by the filesystem.
|
||||
|
||||
**Block count** - Number of blocks in the filesystem.
|
||||
|
||||
**Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits
|
||||
encodes the major version, which is incremented when a breaking-change is
|
||||
introduced in the filesystem specification. The lower 16 bits encodes the
|
||||
minor version, which is incremented when a backwards-compatible change is
|
||||
introduced. Non-standard Attribute changes do not change the version. This
|
||||
specification describes version 1.1 (0x00010001), which is the first version
|
||||
of littlefs.
|
||||
|
||||
**Magic string** - The magic string "littlefs" takes the place of an entry
|
||||
name.
|
||||
|
||||
Here's an example of a complete superblock:
|
||||
```
|
||||
(32 bits) revision count = 3 (0x00000003)
|
||||
(32 bits) dir size = 52 bytes, end of dir (0x00000034)
|
||||
(64 bits) tail pointer = 3, 2 (0x00000003, 0x00000002)
|
||||
(8 bits) entry type = superblock (0xe2)
|
||||
(8 bits) entry length = 20 bytes (0x14)
|
||||
(8 bits) attribute length = 0 bytes (0x00)
|
||||
(8 bits) name length = 8 bytes (0x08)
|
||||
(64 bits) root directory = 3, 2 (0x00000003, 0x00000002)
|
||||
(32 bits) block size = 512 bytes (0x00000200)
|
||||
(32 bits) block count = 1024 blocks (0x00000400)
|
||||
(32 bits) version = 1.1 (0x00010001)
|
||||
(8 bytes) magic string = littlefs
|
||||
(32 bits) crc = 0xc50b74fa
|
||||
|
||||
00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4...........
|
||||
00000010: e2 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................
|
||||
00000020: 00 04 00 00 01 00 01 00 6c 69 74 74 6c 65 66 73 ........littlefs
|
||||
00000030: fa 74 0b c5 .t..
|
||||
```
|
||||
|
||||
## Directory entries
|
||||
|
||||
Directories are stored in entries with a pointer to the first metadata pair
|
||||
in the directory. Keep in mind that a directory may be composed of multiple
|
||||
metadata pairs connected by the tail pointer when the highest bit in the dir
|
||||
size is set.
|
||||
|
||||
Here's the layout of a directory entry:
|
||||
|
||||
| offset | size | description |
|
||||
|--------|------------------------|-----------------------------------------|
|
||||
| 0x0 | 8 bits | entry type (0x22 for directory entries) |
|
||||
| 0x1 | 8 bits | entry length (8 bytes) |
|
||||
| 0x2 | 8 bits | attribute length |
|
||||
| 0x3 | 8 bits | name length |
|
||||
| 0x4 | 64 bits | directory pointer |
|
||||
| 0xc | attribute length bytes | system-specific attributes |
|
||||
| 0xc+a | name length bytes | directory name |
|
||||
|
||||
**Directory pointer** - Pointer to the first metadata pair in the directory.
|
||||
|
||||
Here's an example of a directory entry:
|
||||
```
|
||||
(8 bits) entry type = directory (0x22)
|
||||
(8 bits) entry length = 8 bytes (0x08)
|
||||
(8 bits) attribute length = 0 bytes (0x00)
|
||||
(8 bits) name length = 3 bytes (0x03)
|
||||
(64 bits) directory pointer = 5, 4 (0x00000005, 0x00000004)
|
||||
(3 bytes) name = tea
|
||||
|
||||
00000000: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 "...........tea
|
||||
```
|
||||
|
||||
## File entries
|
||||
|
||||
Files are stored in entries with a pointer to the head of the file and the
|
||||
size of the file. This is enough information to determine the state of the
|
||||
CTZ linked-list that is being referenced.
|
||||
|
||||
How files are actually stored on disk is a bit complicated. The full
|
||||
explanation of CTZ linked-lists can be found in [DESIGN.md](DESIGN.md#ctz-linked-lists).
|
||||
|
||||
A terribly quick summary: For every nth block where n is divisible by 2^x,
|
||||
the block contains a pointer that points x blocks towards the beginning of the
|
||||
file. These pointers are stored in order of x in each block of the file
|
||||
immediately before the data in the block.
|
||||
|
||||
Here's the layout of a file entry:
|
||||
|
||||
| offset | size | description |
|
||||
|--------|------------------------|------------------------------------|
|
||||
| 0x0 | 8 bits | entry type (0x11 for file entries) |
|
||||
| 0x1 | 8 bits | entry length (8 bytes) |
|
||||
| 0x2 | 8 bits | attribute length |
|
||||
| 0x3 | 8 bits | name length |
|
||||
| 0x4 | 32 bits | file head |
|
||||
| 0x8 | 32 bits | file size |
|
||||
| 0xc | attribute length bytes | system-specific attributes |
|
||||
| 0xc+a | name length bytes | directory name |
|
||||
|
||||
**File head** - Pointer to the block that is the head of the file's CTZ
|
||||
linked-list.
|
||||
|
||||
**File size** - Size of file in bytes.
|
||||
|
||||
Here's an example of a file entry:
|
||||
```
|
||||
(8 bits) entry type = file (0x11)
|
||||
(8 bits) entry length = 8 bytes (0x08)
|
||||
(8 bits) attribute length = 0 bytes (0x00)
|
||||
(8 bits) name length = 12 bytes (0x03)
|
||||
(32 bits) file head = 543 (0x0000021f)
|
||||
(32 bits) file size = 256 KB (0x00040000)
|
||||
(12 bytes) name = largeavacado
|
||||
|
||||
00000000: 11 08 00 0c 1f 02 00 00 00 00 04 00 6c 61 72 67 ............larg
|
||||
00000010: 65 61 76 61 63 61 64 6f eavacado
|
||||
```
|
||||
|
||||
## Entry attributes
|
||||
|
||||
Each dir entry can have up to 256 bytes of system-specific attributes. Since
|
||||
these attributes are system-specific, they may not be portable between
|
||||
different systems. For this reason, all attributes must be optional. A minimal
|
||||
littlefs driver must be able to get away with supporting no attributes at all.
|
||||
|
||||
For some level of portability, littlefs has a simple scheme for attributes.
|
||||
Each attribute is prefixes with an 8-bit type that indicates what the attribute
|
||||
is. The length of attributes may also be determined from this type. Attributes
|
||||
in an entry should be sorted based on portability, since attribute parsing
|
||||
will end when it hits the first attribute it does not understand.
|
||||
|
||||
Each system should choose a 4-bit value to prefix all attribute types with to
|
||||
avoid conflicts with other systems. Additionally, littlefs drivers that support
|
||||
attributes should provide a "ignore attributes" flag to users in case attribute
|
||||
conflicts do occur.
|
||||
|
||||
Attribute types prefixes with 0x0 and 0xf are currently reserved for future
|
||||
standard attributes. Standard attributes will be added to this document in
|
||||
that case.
|
||||
|
||||
Here's an example of non-standard time attribute:
|
||||
```
|
||||
(8 bits) attribute type = time (0xc1)
|
||||
(72 bits) time in seconds = 1506286115 (0x0059c81a23)
|
||||
|
||||
00000000: c1 23 1a c8 59 00 .#..Y.
|
||||
```
|
||||
|
||||
Here's an example of non-standard permissions attribute:
|
||||
```
|
||||
(8 bits) attribute type = permissions (0xc2)
|
||||
(16 bits) permission bits = rw-rw-r-- (0x01b4)
|
||||
|
||||
00000000: c2 b4 01 ...
|
||||
```
|
||||
|
||||
Here's what a dir entry may look like with these attributes:
|
||||
```
|
||||
(8 bits) entry type = file (0x11)
|
||||
(8 bits) entry length = 8 bytes (0x08)
|
||||
(8 bits) attribute length = 9 bytes (0x09)
|
||||
(8 bits) name length = 12 bytes (0x0c)
|
||||
(8 bytes) entry data = 05 00 00 00 20 00 00 00
|
||||
(8 bits) attribute type = time (0xc1)
|
||||
(72 bits) time in seconds = 1506286115 (0x0059c81a23)
|
||||
(8 bits) attribute type = permissions (0xc2)
|
||||
(16 bits) permission bits = rw-rw-r-- (0x01b4)
|
||||
(12 bytes) entry name = smallavacado
|
||||
|
||||
00000000: 11 08 09 0c 05 00 00 00 20 00 00 00 c1 23 1a c8 ........ ....#..
|
||||
00000010: 59 00 c2 b4 01 73 6d 61 6c 6c 61 76 61 63 61 64 Y....smallavacad
|
||||
00000020: 6f o
|
||||
```
|
||||
485
lfs.c
485
lfs.c
@@ -253,6 +253,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
|
||||
static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir);
|
||||
static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
||||
lfs_dir_t *parent, lfs_entry_t *entry);
|
||||
static int lfs_moved(lfs_t *lfs, const void *e);
|
||||
static int lfs_relocate(lfs_t *lfs,
|
||||
const lfs_block_t oldpair[2], const lfs_block_t newpair[2]);
|
||||
int lfs_deorphan(lfs_t *lfs);
|
||||
@@ -262,50 +263,45 @@ int lfs_deorphan(lfs_t *lfs);
|
||||
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
|
||||
lfs_t *lfs = p;
|
||||
|
||||
lfs_block_t off = (block - lfs->free.start) % lfs->cfg->block_count;
|
||||
if (off < lfs->cfg->lookahead) {
|
||||
lfs->free.lookahead[off / 32] |= 1U << (off % 32);
|
||||
lfs_block_t off = (((lfs_soff_t)(block - lfs->free.begin)
|
||||
% (lfs_soff_t)(lfs->cfg->block_count))
|
||||
+ lfs->cfg->block_count) % lfs->cfg->block_count;
|
||||
|
||||
if (off < lfs->free.lookahead) {
|
||||
lfs->free.buffer[off / 32] |= 1U << (off % 32);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||
// deorphan if we haven't yet, only needed once after poweron
|
||||
if (!lfs->deorphaned) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while (true) {
|
||||
// check if we have looked at all blocks since last ack
|
||||
if (lfs->free.start + lfs->free.off == lfs->free.end) {
|
||||
if (lfs->free.begin + lfs->free.off == lfs->free.end) {
|
||||
LFS_WARN("No more free space %d", lfs->free.end);
|
||||
return LFS_ERR_NOSPC;
|
||||
}
|
||||
|
||||
if (lfs->free.off >= lfs->cfg->lookahead) {
|
||||
if (lfs->free.off >= lfs->free.lookahead) {
|
||||
break;
|
||||
}
|
||||
|
||||
lfs_block_t off = lfs->free.off;
|
||||
lfs->free.off += 1;
|
||||
|
||||
if (!(lfs->free.lookahead[off / 32] & (1U << (off % 32)))) {
|
||||
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
|
||||
// found a free block
|
||||
*block = (lfs->free.start + off) % lfs->cfg->block_count;
|
||||
*block = (lfs->free.begin + off) % lfs->cfg->block_count;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
lfs->free.start += lfs->cfg->lookahead;
|
||||
lfs->free.begin += lfs->free.lookahead;
|
||||
lfs->free.off = 0;
|
||||
|
||||
// find mask of free blocks from tree
|
||||
memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8);
|
||||
memset(lfs->free.buffer, 0, lfs->free.lookahead/8);
|
||||
int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -314,7 +310,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
||||
}
|
||||
|
||||
static void lfs_alloc_ack(lfs_t *lfs) {
|
||||
lfs->free.end = lfs->free.start + lfs->free.off + lfs->cfg->block_count;
|
||||
lfs->free.end = lfs->free.begin + lfs->free.off + lfs->cfg->block_count;
|
||||
}
|
||||
|
||||
|
||||
@@ -343,6 +339,10 @@ static inline bool lfs_pairsync(
|
||||
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
||||
}
|
||||
|
||||
static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) {
|
||||
return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
|
||||
}
|
||||
|
||||
static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
|
||||
// allocate pair of dir blocks
|
||||
for (int i = 0; i < 2; i++) {
|
||||
@@ -358,9 +358,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) {
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
lfs->sum += dir->d.rev;
|
||||
|
||||
// set defaults
|
||||
dir->d.rev += 1;
|
||||
dir->d.size = sizeof(dir->d)+4;
|
||||
dir->d.tail[0] = -1;
|
||||
dir->d.tail[1] = -1;
|
||||
@@ -431,7 +431,12 @@ struct lfs_region {
|
||||
|
||||
static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
|
||||
const struct lfs_region *regions, int count) {
|
||||
dir->d.rev += 1;
|
||||
// increment rev such that, even == stable, odd == unstable
|
||||
uint32_t diff = 1 + ((lfs->sum & 0x1) ^ !lfs->unstable);
|
||||
dir->d.rev += diff;
|
||||
lfs->sum += diff;
|
||||
|
||||
// keep pairs in order such that pair[0] is most recent
|
||||
lfs_pairswap(dir->pair);
|
||||
for (int i = 0; i < count; i++) {
|
||||
dir->d.size += regions[i].newlen - regions[i].oldlen;
|
||||
@@ -568,8 +573,7 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
|
||||
lfs_entry_t *entry, const void *data) {
|
||||
// check if we fit, if top bit is set we do not and move on
|
||||
while (true) {
|
||||
if (dir->d.size + 4+entry->d.elen+entry->d.alen+entry->d.nlen
|
||||
<= lfs->cfg->block_size) {
|
||||
if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) {
|
||||
entry->off = dir->d.size - 4;
|
||||
return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
|
||||
{entry->off, 0, &entry->d, sizeof(entry->d)},
|
||||
@@ -611,7 +615,8 @@ static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir,
|
||||
|
||||
static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
|
||||
// either shift out the one entry or remove the whole dir block
|
||||
if (dir->d.size == sizeof(dir->d)+4) {
|
||||
if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4
|
||||
+ lfs_entry_size(entry)) {
|
||||
lfs_dir_t pdir;
|
||||
int res = lfs_pred(lfs, dir->pair, &pdir);
|
||||
if (res < 0) {
|
||||
@@ -620,19 +625,36 @@ static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
|
||||
|
||||
if (!(pdir.d.size & 0x80000000)) {
|
||||
return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
|
||||
{entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen,
|
||||
NULL, 0},
|
||||
}, 1);
|
||||
{entry->off, lfs_entry_size(entry), NULL, 0},
|
||||
}, 1);
|
||||
} else {
|
||||
lfs->sum -= dir->d.rev;
|
||||
pdir.d.size &= dir->d.size | 0x7fffffff;
|
||||
pdir.d.tail[0] = dir->d.tail[0];
|
||||
pdir.d.tail[1] = dir->d.tail[1];
|
||||
return lfs_dir_commit(lfs, dir, NULL, 0);
|
||||
return lfs_dir_commit(lfs, &pdir, NULL, 0);
|
||||
}
|
||||
} else {
|
||||
return lfs_dir_commit(lfs, dir, (struct lfs_region[]){
|
||||
{entry->off, 4+entry->d.elen+entry->d.alen+entry->d.nlen,
|
||||
NULL, 0},
|
||||
}, 1);
|
||||
int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){
|
||||
{entry->off, lfs_entry_size(entry), NULL, 0},
|
||||
}, 1);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// shift over any files that are affected
|
||||
for (lfs_file_t *f = lfs->files; f; f = f->next) {
|
||||
if (lfs_paircmp(f->pair, dir->pair) == 0) {
|
||||
if (f->poff == entry->off) {
|
||||
f->pair[0] = 0xffffffff;
|
||||
f->pair[1] = 0xffffffff;
|
||||
} else if (f->poff > entry->off) {
|
||||
f->poff -= lfs_entry_size(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,8 +681,8 @@ static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) {
|
||||
}
|
||||
|
||||
entry->off = dir->off;
|
||||
dir->off += 4+entry->d.elen+entry->d.alen+entry->d.nlen;
|
||||
dir->pos += 4+entry->d.elen+entry->d.alen+entry->d.nlen;
|
||||
dir->off += lfs_entry_size(entry);
|
||||
dir->pos += lfs_entry_size(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -706,6 +728,9 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
|
||||
suffix += sufflen;
|
||||
}
|
||||
|
||||
// update what we've found
|
||||
*path = pathname;
|
||||
|
||||
// find path
|
||||
while (true) {
|
||||
int err = lfs_dir_next(lfs, dir, entry);
|
||||
@@ -713,8 +738,8 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((entry->d.type != LFS_TYPE_REG &&
|
||||
entry->d.type != LFS_TYPE_DIR) ||
|
||||
if (((0x7f & entry->d.type) != LFS_TYPE_REG &&
|
||||
(0x7f & entry->d.type) != LFS_TYPE_DIR) ||
|
||||
entry->d.nlen != pathlen) {
|
||||
continue;
|
||||
}
|
||||
@@ -732,6 +757,16 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
|
||||
}
|
||||
}
|
||||
|
||||
// check that entry has not been moved
|
||||
if (entry->d.type & 0x80) {
|
||||
int moved = lfs_moved(lfs, &entry->d.u);
|
||||
if (moved < 0 || moved) {
|
||||
return (moved < 0) ? moved : LFS_ERR_NOENT;
|
||||
}
|
||||
|
||||
entry->d.type &= ~0x80;
|
||||
}
|
||||
|
||||
pathname += pathlen;
|
||||
pathname += strspn(pathname, "/");
|
||||
if (pathname[0] == '\0') {
|
||||
@@ -747,8 +782,6 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*path = pathname;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -757,6 +790,14 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
|
||||
|
||||
/// Top level directory operations ///
|
||||
int lfs_mkdir(lfs_t *lfs, const char *path) {
|
||||
// deorphan if we haven't yet, needed at most once after poweron
|
||||
if (lfs->unstable) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch parent directory
|
||||
lfs_dir_t cwd;
|
||||
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
|
||||
@@ -766,7 +807,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) {
|
||||
|
||||
lfs_entry_t entry;
|
||||
err = lfs_dir_find(lfs, &cwd, &entry, &path);
|
||||
if (err != LFS_ERR_NOENT) {
|
||||
if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) {
|
||||
return err ? err : LFS_ERR_EXISTS;
|
||||
}
|
||||
|
||||
@@ -814,8 +855,8 @@ int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// check for root, can only be something like '/././../.'
|
||||
if (strspn(path, "/.") == strlen(path)) {
|
||||
// can only be something like '/././../.'
|
||||
dir->head[0] = dir->pair[0];
|
||||
dir->head[1] = dir->pair[1];
|
||||
dir->pos = sizeof(dir->d) - 2;
|
||||
@@ -873,10 +914,26 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
|
||||
return (err == LFS_ERR_NOENT) ? 0 : err;
|
||||
}
|
||||
|
||||
if (entry.d.type == LFS_TYPE_REG ||
|
||||
entry.d.type == LFS_TYPE_DIR) {
|
||||
break;
|
||||
if ((0x7f & entry.d.type) != LFS_TYPE_REG &&
|
||||
(0x7f & entry.d.type) != LFS_TYPE_DIR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check that entry has not been moved
|
||||
if (entry.d.type & 0x80) {
|
||||
int moved = lfs_moved(lfs, &entry.d.u);
|
||||
if (moved < 0) {
|
||||
return moved;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.d.type &= ~0x80;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
info->type = entry.d.type;
|
||||
@@ -975,6 +1032,7 @@ static int lfs_index_find(lfs_t *lfs,
|
||||
return err;
|
||||
}
|
||||
|
||||
assert(head >= 2 && head <= lfs->cfg->block_count);
|
||||
current -= 1 << skip;
|
||||
}
|
||||
|
||||
@@ -993,6 +1051,7 @@ static int lfs_index_extend(lfs_t *lfs,
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
assert(*block >= 2 && *block <= lfs->cfg->block_count);
|
||||
|
||||
err = lfs_bd_erase(lfs, *block);
|
||||
if (err) {
|
||||
@@ -1054,6 +1113,8 @@ static int lfs_index_extend(lfs_t *lfs,
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
assert(head >= 2 && head <= lfs->cfg->block_count);
|
||||
}
|
||||
|
||||
*off = 4*skips;
|
||||
@@ -1102,6 +1163,14 @@ static int lfs_index_traverse(lfs_t *lfs,
|
||||
/// Top level file operations ///
|
||||
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
|
||||
const char *path, int flags) {
|
||||
// deorphan if we haven't yet, needed at most once after poweron
|
||||
if ((flags & 3) != LFS_O_RDONLY && lfs->unstable) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// allocate entry for file if it doesn't exist
|
||||
lfs_dir_t cwd;
|
||||
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
|
||||
@@ -1111,7 +1180,7 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
|
||||
|
||||
lfs_entry_t entry;
|
||||
err = lfs_dir_find(lfs, &cwd, &entry, &path);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1369,6 +1438,11 @@ lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
|
||||
}
|
||||
}
|
||||
|
||||
if (file->pos >= file->size) {
|
||||
// eof if past end
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = lfs_min(size, file->size - file->pos);
|
||||
nsize = size;
|
||||
|
||||
@@ -1424,22 +1498,34 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
|
||||
file->pos = file->size;
|
||||
}
|
||||
|
||||
if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) {
|
||||
// fill with zeros
|
||||
lfs_off_t pos = file->pos;
|
||||
file->pos = file->size;
|
||||
|
||||
while (file->pos < pos) {
|
||||
lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (nsize > 0) {
|
||||
// check if we need a new block
|
||||
if (!(file->flags & LFS_F_WRITING) ||
|
||||
file->off == lfs->cfg->block_size) {
|
||||
if (!(file->flags & LFS_F_WRITING)) {
|
||||
if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
|
||||
// find out which block we're extending from
|
||||
int err = lfs_index_find(lfs, &file->cache, NULL,
|
||||
file->head, file->size,
|
||||
file->pos, &file->block, &file->off);
|
||||
file->pos-1, &file->block, &file->off);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// mark cache as dirty since we may have read data into it
|
||||
file->cache.block = 0xffffffff;
|
||||
file->flags |= LFS_F_WRITING;
|
||||
}
|
||||
|
||||
// extend file with new blocks
|
||||
@@ -1450,6 +1536,8 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
file->flags |= LFS_F_WRITING;
|
||||
}
|
||||
|
||||
// program as much as we can in current block
|
||||
@@ -1492,17 +1580,23 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
|
||||
}
|
||||
|
||||
// update pos
|
||||
lfs_off_t pos = file->pos;
|
||||
|
||||
if (whence == LFS_SEEK_SET) {
|
||||
file->pos = off;
|
||||
} else if (whence == LFS_SEEK_CUR) {
|
||||
if (-off > file->pos) {
|
||||
return LFS_ERR_INVAL;
|
||||
}
|
||||
|
||||
file->pos = file->pos + off;
|
||||
} else if (whence == LFS_SEEK_END) {
|
||||
if (-off > file->size) {
|
||||
return LFS_ERR_INVAL;
|
||||
}
|
||||
|
||||
file->pos = file->size + off;
|
||||
}
|
||||
|
||||
return pos;
|
||||
return file->pos;
|
||||
}
|
||||
|
||||
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
|
||||
@@ -1525,6 +1619,14 @@ lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
|
||||
|
||||
/// General fs oprations ///
|
||||
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
|
||||
// check for root, can only be something like '/././../.'
|
||||
if (strspn(path, "/.") == strlen(path)) {
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->type = LFS_TYPE_DIR;
|
||||
strcpy(info->name, "/");
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_dir_t cwd;
|
||||
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
|
||||
if (err) {
|
||||
@@ -1554,6 +1656,14 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
|
||||
}
|
||||
|
||||
int lfs_remove(lfs_t *lfs, const char *path) {
|
||||
// deorphan if we haven't yet, needed at most once after poweron
|
||||
if (lfs->unstable) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
lfs_dir_t cwd;
|
||||
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
|
||||
if (err) {
|
||||
@@ -1580,27 +1690,26 @@ int lfs_remove(lfs_t *lfs, const char *path) {
|
||||
}
|
||||
|
||||
// remove the entry
|
||||
lfs->unstable += (entry.d.type == LFS_TYPE_DIR);
|
||||
err = lfs_dir_remove(lfs, &cwd, &entry);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// shift over any files that are affected
|
||||
for (lfs_file_t *f = lfs->files; f; f = f->next) {
|
||||
if (lfs_paircmp(f->pair, cwd.pair) == 0) {
|
||||
if (f->poff == entry.off) {
|
||||
f->pair[0] = 0xffffffff;
|
||||
f->pair[1] = 0xffffffff;
|
||||
} else if (f->poff > entry.off) {
|
||||
f->poff -= 4 + entry.d.elen + entry.d.alen + entry.d.nlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we were a directory, just run a deorphan step, this should
|
||||
// collect us, although is expensive
|
||||
// if we were a directory, find pred, replace tail
|
||||
if (entry.d.type == LFS_TYPE_DIR) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
int res = lfs_pred(lfs, dir.pair, &cwd);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
assert(res); // must have pred
|
||||
lfs->sum -= dir.d.rev;
|
||||
cwd.d.tail[0] = dir.d.tail[0];
|
||||
cwd.d.tail[1] = dir.d.tail[1];
|
||||
|
||||
lfs->unstable -= 1;
|
||||
int err = lfs_dir_commit(lfs, &cwd, NULL, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -1610,6 +1719,14 @@ int lfs_remove(lfs_t *lfs, const char *path) {
|
||||
}
|
||||
|
||||
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||
// deorphan if we haven't yet, needed at most once after poweron
|
||||
if (lfs->unstable) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// find old entry
|
||||
lfs_dir_t oldcwd;
|
||||
int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root);
|
||||
@@ -1632,10 +1749,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||
|
||||
lfs_entry_t preventry;
|
||||
err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) {
|
||||
return err;
|
||||
}
|
||||
|
||||
bool prevexists = (err != LFS_ERR_NOENT);
|
||||
bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
|
||||
|
||||
// must have same type
|
||||
if (prevexists && preventry.d.type != oldentry.d.type) {
|
||||
@@ -1655,9 +1774,23 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||
}
|
||||
}
|
||||
|
||||
// mark as moving
|
||||
lfs->unstable += 1 + (prevexists && preventry.d.type == LFS_TYPE_DIR);
|
||||
oldentry.d.type |= 0x80;
|
||||
err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// update pair if newcwd == oldcwd
|
||||
if (samepair) {
|
||||
newcwd = oldcwd;
|
||||
}
|
||||
|
||||
// move to new location
|
||||
lfs_entry_t newentry = preventry;
|
||||
newentry.d = oldentry.d;
|
||||
newentry.d.type &= ~0x80;
|
||||
newentry.d.nlen = strlen(newpath);
|
||||
|
||||
if (prevexists) {
|
||||
@@ -1672,39 +1805,32 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
||||
}
|
||||
}
|
||||
|
||||
// fetch again in case newcwd == oldcwd
|
||||
err = lfs_dir_fetch(lfs, &oldcwd, oldcwd.pair);
|
||||
if (err) {
|
||||
return err;
|
||||
// update pair if newcwd == oldcwd
|
||||
if (samepair) {
|
||||
oldcwd = newcwd;
|
||||
}
|
||||
|
||||
err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// remove from old location
|
||||
// remove old entry
|
||||
lfs->unstable -= 1;
|
||||
err = lfs_dir_remove(lfs, &oldcwd, &oldentry);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// shift over any files that are affected
|
||||
for (lfs_file_t *f = lfs->files; f; f = f->next) {
|
||||
if (lfs_paircmp(f->pair, oldcwd.pair) == 0) {
|
||||
if (f->poff == oldentry.off) {
|
||||
f->pair[0] = 0xffffffff;
|
||||
f->pair[1] = 0xffffffff;
|
||||
} else if (f->poff > oldentry.off) {
|
||||
f->poff -= 4+oldentry.d.elen+oldentry.d.alen+oldentry.d.nlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we were a directory, just run a deorphan step, this should
|
||||
// collect us, although is expensive
|
||||
// if we were a directory, find pred, replace tail
|
||||
if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
|
||||
int err = lfs_deorphan(lfs);
|
||||
int res = lfs_pred(lfs, dir.pair, &newcwd);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
assert(res); // must have pred
|
||||
lfs->sum -= dir.d.rev;
|
||||
newcwd.d.tail[0] = dir.d.tail[0];
|
||||
newcwd.d.tail[1] = dir.d.tail[1];
|
||||
|
||||
lfs->unstable -= 1;
|
||||
int err = lfs_dir_commit(lfs, &newcwd, NULL, 0);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -1740,12 +1866,15 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
// setup lookahead
|
||||
// setup lookahead, round down to nearest 32-bits
|
||||
lfs->free.lookahead = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
|
||||
lfs->free.lookahead = 32 * (lfs->free.lookahead / 32);
|
||||
assert(lfs->free.lookahead > 0);
|
||||
if (lfs->cfg->lookahead_buffer) {
|
||||
lfs->free.lookahead = lfs->cfg->lookahead_buffer;
|
||||
lfs->free.buffer = lfs->cfg->lookahead_buffer;
|
||||
} else {
|
||||
lfs->free.lookahead = malloc(lfs->cfg->lookahead/8);
|
||||
if (!lfs->free.lookahead) {
|
||||
lfs->free.buffer = malloc(lfs->free.lookahead/8);
|
||||
if (!lfs->free.buffer) {
|
||||
return LFS_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
@@ -1754,7 +1883,8 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||
lfs->root[0] = 0xffffffff;
|
||||
lfs->root[1] = 0xffffffff;
|
||||
lfs->files = NULL;
|
||||
lfs->deorphaned = false;
|
||||
lfs->unstable = false;
|
||||
lfs->sum = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1770,7 +1900,7 @@ static int lfs_deinit(lfs_t *lfs) {
|
||||
}
|
||||
|
||||
if (!lfs->cfg->lookahead_buffer) {
|
||||
free(lfs->free.lookahead);
|
||||
free(lfs->free.buffer);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1783,10 +1913,10 @@ int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||
}
|
||||
|
||||
// create free lookahead
|
||||
memset(lfs->free.lookahead, 0, lfs->cfg->lookahead/8);
|
||||
lfs->free.start = 0;
|
||||
memset(lfs->free.buffer, 0, lfs->free.lookahead/8);
|
||||
lfs->free.begin = 0;
|
||||
lfs->free.off = 0;
|
||||
lfs->free.end = lfs->free.start + lfs->cfg->block_count;
|
||||
lfs->free.end = lfs->free.begin + lfs->cfg->block_count;
|
||||
|
||||
// create superblock dir
|
||||
lfs_alloc_ack(lfs);
|
||||
@@ -1862,26 +1992,34 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||
}
|
||||
|
||||
// setup free lookahead
|
||||
lfs->free.start = -lfs->cfg->lookahead;
|
||||
lfs->free.off = lfs->cfg->lookahead;
|
||||
lfs->free.end = lfs->free.start + lfs->cfg->block_count;
|
||||
lfs->free.begin = -lfs->free.lookahead;
|
||||
lfs->free.off = lfs->free.lookahead;
|
||||
lfs->free.end = lfs->free.begin + lfs->cfg->block_count;
|
||||
|
||||
// load superblock
|
||||
lfs_dir_t dir;
|
||||
lfs_superblock_t superblock;
|
||||
err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
|
||||
if (err && err != LFS_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
|
||||
int err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d),
|
||||
&superblock.d, sizeof(superblock.d));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs->root[0] = superblock.d.root[0];
|
||||
lfs->root[1] = superblock.d.root[1];
|
||||
}
|
||||
|
||||
if (err == LFS_ERR_CORRUPT ||
|
||||
memcmp(superblock.d.magic, "littlefs", 8) != 0) {
|
||||
if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
|
||||
LFS_ERROR("Invalid superblock at %d %d", dir.pair[0], dir.pair[1]);
|
||||
return LFS_ERR_CORRUPT;
|
||||
} else if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (superblock.d.version > (0x00010001 | 0x0000ffff)) {
|
||||
@@ -1891,7 +2029,23 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
|
||||
return LFS_ERR_INVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
// sum rev counts in fs, even = stable, odd = unstable
|
||||
lfs->sum += dir.d.rev;
|
||||
while (!lfs_pairisnull(dir.d.tail)) {
|
||||
int err = lfs_dir_fetch(lfs, &dir, dir.d.tail);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs->sum += dir.d.rev;
|
||||
}
|
||||
|
||||
if (lfs->sum & 0x1) {
|
||||
LFS_DEBUG("Power-loss detected %x", lfs->sum);
|
||||
lfs->unstable += 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs_unmount(lfs_t *lfs) {
|
||||
@@ -1931,8 +2085,8 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
|
||||
return err;
|
||||
}
|
||||
|
||||
dir.off += 4+entry.d.elen+entry.d.alen+entry.d.nlen;
|
||||
if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) {
|
||||
dir.off += lfs_entry_size(&entry);
|
||||
if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
|
||||
int err = lfs_index_traverse(lfs, &lfs->rcache, NULL,
|
||||
entry.d.u.file.head, entry.d.u.file.size, cb, data);
|
||||
if (err) {
|
||||
@@ -2022,7 +2176,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
||||
break;
|
||||
}
|
||||
|
||||
if (((0xf & entry->d.type) == (0xf & LFS_TYPE_DIR)) &&
|
||||
if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) &&
|
||||
lfs_paircmp(entry->d.u.dir, dir) == 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -2032,6 +2186,46 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
||||
return false;
|
||||
}
|
||||
|
||||
static int lfs_moved(lfs_t *lfs, const void *e) {
|
||||
if (lfs_pairisnull(lfs->root)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// skip superblock
|
||||
lfs_dir_t cwd;
|
||||
int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// iterate over all directory directory entries
|
||||
lfs_entry_t entry;
|
||||
while (!lfs_pairisnull(cwd.d.tail)) {
|
||||
int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
int err = lfs_dir_next(lfs, &cwd, &entry);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(0x80 & entry.d.type) &&
|
||||
memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int lfs_relocate(lfs_t *lfs,
|
||||
const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) {
|
||||
// find parent
|
||||
@@ -2047,6 +2241,7 @@ static int lfs_relocate(lfs_t *lfs,
|
||||
entry.d.u.dir[0] = newpair[0];
|
||||
entry.d.u.dir[1] = newpair[1];
|
||||
|
||||
lfs->unstable += 1;
|
||||
int err = lfs_dir_update(lfs, &parent, &entry, NULL);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -2072,7 +2267,7 @@ static int lfs_relocate(lfs_t *lfs,
|
||||
if (res) {
|
||||
// just replace bad pair, no desync can occur
|
||||
parent.d.tail[0] = newpair[0];
|
||||
parent.d.tail[0] = newpair[0];
|
||||
parent.d.tail[1] = newpair[1];
|
||||
|
||||
return lfs_dir_commit(lfs, &parent, NULL, 0);
|
||||
}
|
||||
@@ -2082,28 +2277,21 @@ static int lfs_relocate(lfs_t *lfs,
|
||||
}
|
||||
|
||||
int lfs_deorphan(lfs_t *lfs) {
|
||||
lfs->deorphaned = true;
|
||||
if (lfs_pairisnull(lfs->root)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
lfs_dir_t pdir;
|
||||
lfs_dir_t cdir;
|
||||
lfs_dir_t pdir = {.d.size = 0x80000000};
|
||||
lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1};
|
||||
|
||||
// skip superblock
|
||||
int err = lfs_dir_fetch(lfs, &pdir, (const lfs_block_t[2]){0, 1});
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// iterate over all directories
|
||||
while (!lfs_pairisnull(pdir.d.tail)) {
|
||||
int err = lfs_dir_fetch(lfs, &cdir, pdir.d.tail);
|
||||
// iterate over all directory directory entries
|
||||
while (!lfs_pairisnull(cwd.d.tail)) {
|
||||
int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// only check head blocks
|
||||
// check head blocks for orphans
|
||||
if (!(0x80000000 & pdir.d.size)) {
|
||||
// check if we have a parent
|
||||
lfs_dir_t parent;
|
||||
@@ -2115,10 +2303,11 @@ int lfs_deorphan(lfs_t *lfs) {
|
||||
|
||||
if (!res) {
|
||||
// we are an orphan
|
||||
LFS_DEBUG("Orphan %d %d", pdir.d.tail[0], pdir.d.tail[1]);
|
||||
LFS_DEBUG("Found orphan %d %d",
|
||||
pdir.d.tail[0], pdir.d.tail[1]);
|
||||
|
||||
pdir.d.tail[0] = cdir.d.tail[0];
|
||||
pdir.d.tail[1] = cdir.d.tail[1];
|
||||
pdir.d.tail[0] = cwd.d.tail[0];
|
||||
pdir.d.tail[1] = cwd.d.tail[1];
|
||||
|
||||
err = lfs_dir_commit(lfs, &pdir, NULL, 0);
|
||||
if (err) {
|
||||
@@ -2130,7 +2319,8 @@ int lfs_deorphan(lfs_t *lfs) {
|
||||
|
||||
if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) {
|
||||
// we have desynced
|
||||
LFS_DEBUG("Desync %d %d", entry.d.u.dir[0], entry.d.u.dir[1]);
|
||||
LFS_DEBUG("Found desync %d %d",
|
||||
entry.d.u.dir[0], entry.d.u.dir[1]);
|
||||
|
||||
pdir.d.tail[0] = entry.d.u.dir[0];
|
||||
pdir.d.tail[1] = entry.d.u.dir[1];
|
||||
@@ -2144,9 +2334,48 @@ int lfs_deorphan(lfs_t *lfs) {
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&pdir, &cdir, sizeof(pdir));
|
||||
// check entries for moves
|
||||
lfs_entry_t entry;
|
||||
while (true) {
|
||||
int err = lfs_dir_next(lfs, &cwd, &entry);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (err == LFS_ERR_NOENT) {
|
||||
break;
|
||||
}
|
||||
|
||||
// found moved entry
|
||||
if (entry.d.type & 0x80) {
|
||||
int moved = lfs_moved(lfs, &entry.d.u);
|
||||
if (moved < 0) {
|
||||
return moved;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
LFS_DEBUG("Found move %d %d",
|
||||
entry.d.u.dir[0], entry.d.u.dir[1]);
|
||||
int err = lfs_dir_remove(lfs, &cwd, &entry);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
LFS_DEBUG("Found partial move %d %d",
|
||||
entry.d.u.dir[0], entry.d.u.dir[1]);
|
||||
entry.d.type &= ~0x80;
|
||||
int err = lfs_dir_update(lfs, &cwd, &entry, NULL);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&pdir, &cwd, sizeof(pdir));
|
||||
}
|
||||
|
||||
lfs->unstable -= 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
13
lfs.h
13
lfs.h
@@ -46,7 +46,7 @@ enum lfs_error {
|
||||
enum lfs_type {
|
||||
LFS_TYPE_REG = 0x11,
|
||||
LFS_TYPE_DIR = 0x22,
|
||||
LFS_TYPE_SUPERBLOCK = 0xe2,
|
||||
LFS_TYPE_SUPERBLOCK = 0x2e,
|
||||
};
|
||||
|
||||
// File open flags
|
||||
@@ -225,10 +225,11 @@ typedef struct lfs_superblock {
|
||||
} lfs_superblock_t;
|
||||
|
||||
typedef struct lfs_free {
|
||||
lfs_size_t lookahead;
|
||||
lfs_block_t begin;
|
||||
lfs_block_t end;
|
||||
lfs_block_t start;
|
||||
lfs_block_t off;
|
||||
uint32_t *lookahead;
|
||||
uint32_t *buffer;
|
||||
} lfs_free_t;
|
||||
|
||||
// The littlefs type
|
||||
@@ -237,12 +238,13 @@ typedef struct lfs {
|
||||
|
||||
lfs_block_t root[2];
|
||||
lfs_file_t *files;
|
||||
bool deorphaned;
|
||||
|
||||
lfs_cache_t rcache;
|
||||
lfs_cache_t pcache;
|
||||
|
||||
lfs_free_t free;
|
||||
uint8_t unstable;
|
||||
uint8_t sum;
|
||||
} lfs_t;
|
||||
|
||||
|
||||
@@ -433,5 +435,8 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
|
||||
// Returns a negative error code on failure.
|
||||
int lfs_deorphan(lfs_t *lfs);
|
||||
|
||||
// TODO doc
|
||||
int lfs_deduplicate(lfs_t *lfs);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
// Builtin functions
|
||||
// Builtin functions, these may be replaced by more
|
||||
// efficient implementations in the system
|
||||
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? a : b;
|
||||
}
|
||||
@@ -33,10 +34,12 @@ 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, these may be replaced by system-specific
|
||||
// logging functions
|
||||
#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__)
|
||||
#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__)
|
||||
#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__)
|
||||
|
||||
@@ -27,8 +27,8 @@ void test_assert(const char *file, unsigned line,
|
||||
}}
|
||||
|
||||
if (v != e) {{
|
||||
printf("\033[31m%s:%u: assert %s failed, expected %jd\033[0m\n",
|
||||
file, line, s, e);
|
||||
fprintf(stderr, "\033[31m%s:%u: assert %s failed with %jd, "
|
||||
"expected %jd\033[0m\n", file, line, s, v, e);
|
||||
exit(-2);
|
||||
}}
|
||||
}}
|
||||
|
||||
@@ -124,7 +124,6 @@ tests/test.py << TEST
|
||||
TEST
|
||||
|
||||
echo "--- Directory remove ---"
|
||||
# TESTING HERE
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_remove(&lfs, "potato") => LFS_ERR_INVAL;
|
||||
@@ -283,5 +282,38 @@ tests/test.py << TEST
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Multi-block remove ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_remove(&lfs, "cactus") => LFS_ERR_INVAL;
|
||||
|
||||
for (int i = 0; i < $LARGESIZE; i++) {
|
||||
sprintf((char*)buffer, "cactus/test%d", i);
|
||||
lfs_remove(&lfs, (char*)buffer) => 0;
|
||||
}
|
||||
|
||||
lfs_remove(&lfs, "cactus") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "/") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "burito") => 0;
|
||||
info.type => LFS_TYPE_REG;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "coldpotato") => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Results ---"
|
||||
tests/stats.py
|
||||
|
||||
236
tests/test_move.sh
Executable file
236
tests/test_move.sh
Executable file
@@ -0,0 +1,236 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
echo "=== Move tests ==="
|
||||
rm -rf blocks
|
||||
tests/test.py << TEST
|
||||
lfs_format(&lfs, &cfg) => 0;
|
||||
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_mkdir(&lfs, "a") => 0;
|
||||
lfs_mkdir(&lfs, "b") => 0;
|
||||
lfs_mkdir(&lfs, "c") => 0;
|
||||
lfs_mkdir(&lfs, "d") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "a/hi") => 0;
|
||||
lfs_mkdir(&lfs, "a/hi/hola") => 0;
|
||||
lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
|
||||
lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
|
||||
|
||||
lfs_file_open(&lfs, &file[0], "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
|
||||
lfs_file_write(&lfs, &file[0], "hola\n", 5) => 5;
|
||||
lfs_file_write(&lfs, &file[0], "bonjour\n", 8) => 8;
|
||||
lfs_file_write(&lfs, &file[0], "ohayo\n", 6) => 6;
|
||||
lfs_file_close(&lfs, &file[0]) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move file ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "a/hello", "b/hello") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "a") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hi") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "b") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hello") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move file corrupt source ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "b/hello", "c/hello") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
rm -v blocks/7
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "b") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "c") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hello") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move file corrupt source and dest ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "c/hello", "d/hello") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
rm -v blocks/8
|
||||
rm -v blocks/a
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "c") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hello") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "d") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move dir ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "a/hi", "b/hi") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "a") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "b") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hi") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move dir corrupt source ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "b/hi", "c/hi") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
rm -v blocks/7
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "b") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "c") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hello") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hi") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move dir corrupt source and dest ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_rename(&lfs, "c/hi", "d/hi") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
rm -v blocks/9
|
||||
rm -v blocks/a
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "c") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hello") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hi") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
lfs_dir_open(&lfs, &dir[0], "d") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Move check ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
|
||||
lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
|
||||
lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT;
|
||||
lfs_dir_open(&lfs, &dir[0], "d/hi") => LFS_ERR_NOENT;
|
||||
|
||||
lfs_dir_open(&lfs, &dir[0], "c/hi") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, ".") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "..") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "hola") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "bonjour") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 1;
|
||||
strcmp(info.name, "ohayo") => 0;
|
||||
lfs_dir_read(&lfs, &dir[0], &info) => 0;
|
||||
lfs_dir_close(&lfs, &dir[0]) => 0;
|
||||
|
||||
lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
|
||||
lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT;
|
||||
lfs_dir_open(&lfs, &dir[0], "d/hello") => LFS_ERR_NOENT;
|
||||
|
||||
lfs_file_open(&lfs, &file[0], "c/hello", LFS_O_RDONLY) => 0;
|
||||
lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
|
||||
memcmp(buffer, "hola\n", 5) => 0;
|
||||
lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
|
||||
memcmp(buffer, "bonjour\n", 8) => 0;
|
||||
lfs_file_read(&lfs, &file[0], buffer, 6) => 6;
|
||||
memcmp(buffer, "ohayo\n", 6) => 0;
|
||||
lfs_file_close(&lfs, &file[0]) => 0;
|
||||
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
|
||||
echo "--- Results ---"
|
||||
tests/stats.py
|
||||
@@ -31,6 +31,10 @@ tests/test.py << TEST
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
lfs_stat(&lfs, "/tea/hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "/milk1") => 0;
|
||||
lfs_stat(&lfs, "/milk1", &info) => 0;
|
||||
strcmp(info.name, "milk1") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
@@ -43,6 +47,10 @@ tests/test.py << TEST
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
lfs_stat(&lfs, "///tea///hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "///milk2") => 0;
|
||||
lfs_stat(&lfs, "///milk2", &info) => 0;
|
||||
strcmp(info.name, "milk2") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
@@ -57,6 +65,10 @@ tests/test.py << TEST
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
lfs_stat(&lfs, "/./tea/./hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "/./milk3") => 0;
|
||||
lfs_stat(&lfs, "/./milk3", &info) => 0;
|
||||
strcmp(info.name, "milk3") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
@@ -71,6 +83,10 @@ tests/test.py << TEST
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
lfs_stat(&lfs, "coffee/../soda/../tea/hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "coffee/../milk4") => 0;
|
||||
lfs_stat(&lfs, "coffee/../milk4", &info) => 0;
|
||||
strcmp(info.name, "milk4") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
@@ -79,6 +95,27 @@ tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs_mkdir(&lfs, "coffee/../../../../../../milk5") => 0;
|
||||
lfs_stat(&lfs, "coffee/../../../../../../milk5", &info) => 0;
|
||||
strcmp(info.name, "milk5") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Root tests ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_stat(&lfs, "/", &info) => 0;
|
||||
info.type => LFS_TYPE_DIR;
|
||||
strcmp(info.name, "/") => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Sketchy path tests ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT;
|
||||
lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
|
||||
@@ -133,15 +133,15 @@ tests/test.py << TEST
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
@@ -174,15 +174,15 @@ tests/test.py << TEST
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
@@ -211,7 +211,7 @@ tests/test.py << TEST
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
@@ -219,11 +219,11 @@ tests/test.py << TEST
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
@@ -254,7 +254,7 @@ tests/test.py << TEST
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
@@ -262,11 +262,11 @@ tests/test.py << TEST
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => size;
|
||||
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) => pos+size;
|
||||
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
@@ -277,5 +277,69 @@ tests/test.py << TEST
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Boundary seek and write ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
|
||||
|
||||
size = strlen("hedgehoghog");
|
||||
const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
|
||||
|
||||
for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
|
||||
lfs_soff_t off = offsets[i];
|
||||
memcpy(buffer, "hedgehoghog", size);
|
||||
lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
|
||||
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||
lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_SET) => 0;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs_file_sync(&lfs, &file[0]) => 0;
|
||||
}
|
||||
|
||||
lfs_file_close(&lfs, &file[0]) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Out-of-bounds seek ---"
|
||||
tests/test.py << TEST
|
||||
lfs_mount(&lfs, &cfg) => 0;
|
||||
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
|
||||
|
||||
size = strlen("kittycatcat");
|
||||
lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size;
|
||||
lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
|
||||
LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => 0;
|
||||
|
||||
memcpy(buffer, "porcupineee", size);
|
||||
lfs_file_write(&lfs, &file[0], buffer, size) => size;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
|
||||
LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "porcupineee", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], $LARGESIZE*size,
|
||||
LFS_SEEK_SET) => $LARGESIZE*size;
|
||||
lfs_file_read(&lfs, &file[0], buffer, size) => size;
|
||||
memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+$SMALLSIZE)*size),
|
||||
LFS_SEEK_CUR) => LFS_ERR_INVAL;
|
||||
lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
|
||||
|
||||
lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+2*$SMALLSIZE)*size),
|
||||
LFS_SEEK_END) => LFS_ERR_INVAL;
|
||||
lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
|
||||
|
||||
lfs_file_close(&lfs, &file[0]) => 0;
|
||||
lfs_unmount(&lfs) => 0;
|
||||
TEST
|
||||
|
||||
echo "--- Results ---"
|
||||
tests/stats.py
|
||||
|
||||
Reference in New Issue
Block a user