diff --git a/fs/Kconfig b/fs/Kconfig index f2bfe381c9..8715d2fce5 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -88,6 +88,7 @@ source fs/smartfs/Kconfig source fs/binfs/Kconfig source fs/procfs/Kconfig source fs/spiffs/Kconfig +source fs/littlefs/Kconfig source fs/unionfs/Kconfig source fs/userfs/Kconfig source fs/hostfs/Kconfig diff --git a/fs/Makefile b/fs/Makefile index 86b809b1ce..f66b2ce7db 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -77,6 +77,7 @@ include spiffs/Make.defs include unionfs/Make.defs include userfs/Make.defs include hostfs/Make.defs +include littlefs/Make.defs endif endif diff --git a/fs/littlefs/DESIGN.md b/fs/littlefs/DESIGN.md new file mode 100644 index 0000000000..3afb0a2054 --- /dev/null +++ b/fs/littlefs/DESIGN.md @@ -0,0 +1,1226 @@ +## The design of the little filesystem + +A little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +For a bit of backstory, 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. + +The embedded systems the littlefs is targeting are usually 32 bit +microcontrollers with around 32KB of RAM and 512KB of ROM. These are +often paired with SPI NOR flash chips with about 4MB of flash storage. + +Flash itself is a very interesting piece of technology with quite a bit of +nuance. Unlike most other forms of storage, writing to flash requires two +operations: erasing and programming. The programming operation is relatively +cheap, and can be very granular. For NOR flash specifically, byte-level +programs are quite common. Erasing, however, requires an expensive operation +that forces the state of large blocks of memory to reset in a destructive +reaction that gives flash its name. The [Wikipedia entry](https://en.wikipedia.org/wiki/Flash_memory) +has more information if you are interested in how this works. + +This leaves us with an interesting set of limitations that can be simplified +to three strong requirements: + +1. **Power-loss resilient** - This is the main goal of the littlefs and the + focus of this project. + + Embedded systems are usually designed without a shutdown routine and a + notable lack of user interface for recovery, so filesystems targeting + embedded systems must be prepared to lose power at any given time. + + Despite this state of things, there are very few embedded filesystems that + handle power loss in a reasonable manner, and most can become corrupted if + the user is unlucky enough. + +2. **Wear leveling** - Due to the destructive nature of flash, most flash + chips have a limited number of erase cycles, usually in the order of around + 100,000 erases per block for NOR flash. Filesystems that don't take wear + into account can easily burn through blocks used to store frequently updated + metadata. + + Consider the [FAT filesystem](https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system), + which stores a file allocation table (FAT) at a specific offset from the + beginning of disk. Every block allocation will update this table, and after + 100,000 updates, the block will likely go bad, rendering the filesystem + unusable even if there are many more erase cycles available on the storage + as a whole. + +3. **Bounded RAM/ROM** - Even with the design difficulties presented by the + previous two limitations, we have already seen several flash filesystems + developed on PCs that handle power loss just fine, such as the + logging filesystems. However, these filesystems take advantage of the + relatively cheap access to RAM, and use some rather... opportunistic... + techniques, such as reconstructing the entire directory structure in RAM. + These operations make perfect sense when the filesystem's only concern is + erase cycles, but the idea is a bit silly on embedded systems. + + To cater to embedded systems, the littlefs has the simple limitation of + using only a bounded amount of RAM and ROM. That is, no matter what is + written to the filesystem, and no matter how large the underlying storage + is, the littlefs will always use the same amount of RAM and ROM. This + presents a very unique challenge, and makes presumably simple operations, + such as iterating through the directory tree, surprisingly difficult. + +## Existing designs? + +There are of course, many different existing filesystem. Here is a very rough +summary of the general ideas behind some of them. + +Most of the existing filesystems fall into the one big category of filesystem +designed in the early days of spinny magnet disks. While there is a vast amount +of interesting technology and ideas in this area, the nature of spinny magnet +disks encourage properties, such as grouping writes near each other, that don't +make as much sense on recent storage types. For instance, on flash, write +locality is not important and can actually increase wear. + +One of the most popular designs for flash filesystems is called the +[logging filesystem](https://en.wikipedia.org/wiki/Log-structured_file_system). +The flash filesystems [jffs](https://en.wikipedia.org/wiki/JFFS) +and [yaffs](https://en.wikipedia.org/wiki/YAFFS) are good examples. In a +logging filesystem, data is not stored in a data structure on disk, but instead +the changes to the files are stored on disk. This has several neat advantages, +such as the fact that the data is written in a cyclic log format and naturally +wear levels as a side effect. And, with a bit of error detection, the entire +filesystem can easily be designed to be resilient to power loss. The +journaling component of most modern day filesystems is actually a reduced +form of a logging filesystem. However, logging filesystems have a difficulty +scaling as the size of storage increases. And most filesystems compensate by +caching large parts of the filesystem in RAM, a strategy that is inappropriate +for embedded systems. + +Another interesting filesystem design technique is that of [copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write). +A good example of this is the [btrfs](https://en.wikipedia.org/wiki/Btrfs) +filesystem. COW filesystems can easily recover from corrupted blocks and have +natural protection against power loss. However, if they are not designed with +wear in mind, a COW filesystem could unintentionally wear down the root block +where the COW data structures are synchronized. + +## Metadata pairs + +The core piece of technology that provides the backbone for the littlefs is +the concept of metadata pairs. The key idea here is that any metadata that +needs to be updated atomically is stored on a pair of blocks tagged with +a revision count and checksum. Every update alternates between these two +pairs, so that at any time there is always a backup containing the previous +state of the metadata. + +Consider a small example where each metadata pair has a revision count, +a number as data, and the XOR of the block as a quick checksum. If +we update the data to a value of 9, and then to a value of 5, here is +what the pair of blocks may look like after each update: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 2 | | rev: 3 | rev: 2 | +| data: 3 | data: 0 | -> | data: 3 | data: 9 | -> | data: 5 | data: 9 | +| xor: 2 | xor: 0 | | xor: 2 | xor: 11 | | xor: 6 | xor: 11 | +'---------'---------' '---------'---------' '---------'---------' + let data = 9 let data = 5 +``` + +After each update, we can find the most up to date value of data by looking +at the revision count. + +Now consider what the blocks may look like if we suddenly lose power while +changing the value of data to 5: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 2 | | rev: 3 | rev: 2 | +| data: 3 | data: 0 | -> | data: 3 | data: 9 | -x | data: 3 | data: 9 | +| xor: 2 | xor: 0 | | xor: 2 | xor: 11 | | xor: 2 | xor: 11 | +'---------'---------' '---------'---------' '---------'---------' + let data = 9 let data = 5 + powerloss!!! +``` + +In this case, block 1 was partially written with a new revision count, but +the littlefs hadn't made it to updating the value of data. However, if we +check our checksum we notice that block 1 was corrupted. So we fall back to +block 2 and use the value 9. + +Using this concept, the littlefs is able to update metadata blocks atomically. +There are a few other tweaks, such as using a 32 bit CRC and using sequence +arithmetic to handle revision count overflow, but the basic concept +is the same. These metadata pairs define the backbone of the littlefs, and the +rest of the filesystem is built on top of these atomic updates. + +## Non-meta data + +Now, the metadata pairs do come with some drawbacks. Most notably, each pair +requires two blocks for each block of data. I'm sure users would be very +unhappy if their storage was suddenly cut in half! Instead of storing +everything in these metadata blocks, the littlefs uses a COW data structure +for files which is in turn pointed to by a metadata block. When +we update a file, we create copies of any blocks that are modified until +the metadata blocks are updated with the new copy. Once the metadata block +points to the new copy, we deallocate the old blocks that are no longer in use. + +Here is what updating a one-block file may look like: +``` + block 1 block 2 block 1 block 2 block 1 block 2 +.---------.---------. .---------.---------. .---------.---------. +| rev: 1 | rev: 0 | | rev: 1 | rev: 0 | | rev: 1 | rev: 2 | +| file: 4 | file: 0 | -> | file: 4 | file: 0 | -> | file: 4 | file: 5 | +| xor: 5 | xor: 0 | | xor: 5 | xor: 0 | | xor: 5 | xor: 7 | +'---------'---------' '---------'---------' '---------'---------' + | | | + v v v + block 4 block 4 block 5 block 4 block 5 +.--------. .--------. .--------. .--------. .--------. +| old | | old | | new | | old | | new | +| data | | data | | data | | data | | data | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' + update data in file update metadata pair +``` + +It doesn't matter if we lose power while writing new data to block 5, +since the old data remains unmodified in block 4. This example also +highlights how the atomic updates of the metadata blocks provide a +synchronization barrier for the rest of the littlefs. + +At this point, it may look like we are wasting an awfully large amount +of space on the metadata. Just looking at that example, we are using +three blocks to represent a file that fits comfortably in one! So instead +of giving each file a metadata pair, we actually store the metadata for +all files contained in a single directory in a single metadata block. + +Now we could just leave files here, copying the entire file on write +provides the synchronization without the duplicated memory requirements +of the metadata blocks. However, we can do a bit better. + +## CTZ skip-lists + +There are many different data structures for representing the actual +files in filesystems. Of these, the littlefs uses a rather unique [COW](https://upload.wikimedia.org/wikipedia/commons/0/0c/Cow_female_black_white.jpg) +data structure that allows the filesystem to reuse unmodified parts of the +file without additional metadata pairs. + +First lets consider storing files in a simple linked-list. What happens when we +append a block? We have to change the last block in the linked-list to point +to this new block, which means we have to copy out the last block, and change +the second-to-last block, and then the third-to-last, and so on until we've +copied out the entire file. + +``` +Exhibit A: A linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |->| data 1 |->| data 2 |->| data 4 |->| data 5 |->| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +To get around this, the littlefs, at its heart, stores files backwards. Each +block points to its predecessor, with the first block containing no pointers. +If you think about for a while, it starts to make a bit of sense. Appending +blocks just point to their predecessor and no other blocks need to be updated. +If we update a block in the middle, we will need to copy out the blocks that +follow, but can reuse the blocks before the modified block. Since most file +operations either reset the file each write or append to files, this design +avoids copying the file in the most common cases. + +``` +Exhibit B: A backwards linked-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 4 |<-| data 5 |<-| data 6 | +| | | | | | | | | | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +However, a backwards linked-list does come with a rather glaring problem. +Iterating over a file _in order_ has a runtime cost of O(n^2). Gah! A quadratic +runtime to just _read_ a file? That's awful. Keep in mind reading files is +usually the most common filesystem operation. + +To avoid this problem, the littlefs uses a multilayered linked-list. For +every nth block where n is divisible by 2^x, the block contains a pointer +to block n-2^x. So each block contains anywhere from 1 to log2(n) pointers +that skip to various sections of the preceding list. If you're familiar with +data-structures, you may have recognized that this is a type of deterministic +skip-list. + +The name comes from the use of the +[count trailing zeros (CTZ)](https://en.wikipedia.org/wiki/Count_trailing_zeros) +instruction, which allows us to calculate the power-of-two factors efficiently. +For a given block n, the block contains ctz(n)+1 pointers. + +``` +Exhibit C: A backwards CTZ skip-list +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 |<-| data 4 |<-| data 5 | +| |<-| |--| |<-| |--| | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The additional pointers allow us to navigate the data-structure on disk +much more efficiently than in a singly linked-list. + +Taking exhibit C for example, here is the path from data block 5 to data +block 1. You can see how data block 3 was completely skipped: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 |<-| data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | |<-| |--| | | | +| | | | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +The path to data block 0 is even more quick, requiring only two jumps: +``` +.--------. .--------. .--------. .--------. .--------. .--------. +| data 0 | | data 1 | | data 2 | | data 3 | | data 4 |<-| data 5 | +| | | | | | | | | | | | +| |<-| |--| |--| |--| | | | +'--------' '--------' '--------' '--------' '--------' '--------' +``` + +We can find the runtime complexity by looking at the path to any block from +the block containing the most pointers. Every step along the path divides +the search space for the block in half. This gives us a runtime of O(log n). +To get to the block with the most pointers, we can perform the same steps +backwards, which puts the runtime at O(2 log n) = O(log n). The interesting +part about this data structure is that this optimal path occurs naturally +if we greedily choose the pointer that covers the most distance without passing +our target block. + +So now we have a representation of files that can be appended trivially with +a runtime of O(1), and can be read with a worst case runtime of O(n log n). +Given that the the runtime is also divided by the amount of data we can store +in a block, this is pretty reasonable. + +Unfortunately, the CTZ skip-list comes with a few questions that aren't +straightforward to answer. What is the overhead? How do we handle more +pointers than we can store in a block? How do we store the skip-list in +a directory entry? + +One way to find the overhead per block is to look at the data structure as +multiple layers of linked-lists. Each linked-list skips twice as many blocks +as the previous linked-list. Another way of looking at it is that each +linked-list uses half as much storage per block as the previous linked-list. +As we approach infinity, the number of pointers per block forms a geometric +series. Solving this geometric series gives us an average of only 2 pointers +per block. + +![overhead_per_block](https://latex.codecogs.com/svg.latex?%5Clim_%7Bn%5Cto%5Cinfty%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bi%3D0%7D%5E%7Bn%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%20%5Csum_%7Bi%3D0%7D%5Cfrac%7B1%7D%7B2%5Ei%7D%20%3D%202) + +Finding the maximum number of pointers in a block is a bit more complicated, +but since our file size is limited by the integer width we use to store the +size, we can solve for it. Setting the overhead of the maximum pointers equal +to the block size we get the following equation. Note that a smaller block size +results in more pointers, and a larger word width results in larger pointers. + +![maximum overhead](https://latex.codecogs.com/svg.latex?B%20%3D%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%5Clceil%5Clog_2%5Cleft%28%5Cfrac%7B2%5Ew%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%29%5Cright%5Crceil) + +where: +B = block size in bytes +w = word width in bits + +Solving the equation for B gives us the minimum block size for various word +widths: +32 bit CTZ skip-list = minimum block size of 104 bytes +64 bit CTZ skip-list = minimum block size of 448 bytes + +Since littlefs uses a 32 bit word size, we are limited to a minimum block +size of 104 bytes. This is a perfectly reasonable minimum block size, with most +block sizes starting around 512 bytes. So we can avoid additional logic to +avoid overflowing our block's capacity in the CTZ skip-list. + +So, how do we store the skip-list in a directory entry? A naive approach would +be to store a pointer to the head of the skip-list, the length of the file +in bytes, the index of the head block in the skip-list, and the offset in the +head block in bytes. However this is a lot of information, and we can observe +that a file size maps to only one block index + offset pair. So it should be +sufficient to store only the pointer and file size. + +But there is one problem, calculating the block index + offset pair from a +file size doesn't have an obvious implementation. + +We can start by just writing down an equation. The first idea that comes to +mind is to just use a for loop to sum together blocks until we reach our +file size. We can write this equation as a summation: + +![summation1](https://latex.codecogs.com/svg.latex?N%20%3D%20%5Csum_i%5En%5Cleft%5BB-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%5Cright%5D) + +where: +B = block size in bytes +w = word width in bits +n = block index in skip-list +N = file size in bytes + +And this works quite well, but is not trivial to calculate. This equation +requires O(n) to compute, which brings the entire runtime of reading a file +to O(n^2 log n). Fortunately, the additional O(n) does not need to touch disk, +so it is not completely unreasonable. But if we could solve this equation into +a form that is easily computable, we can avoid a big slowdown. + +Unfortunately, the summation of the CTZ instruction presents a big challenge. +How would you even begin to reason about integrating a bitwise instruction? +Fortunately, there is a powerful tool I've found useful in these situations: +The [On-Line Encyclopedia of Integer Sequences (OEIS)](https://oeis.org/). +If we work out the first couple of values in our summation, we find that CTZ +maps to [A001511](https://oeis.org/A001511), and its partial summation maps +to [A005187](https://oeis.org/A005187), and surprisingly, both of these +sequences have relatively trivial equations! This leads us to a rather +unintuitive property: + +![mindblown](https://latex.codecogs.com/svg.latex?%5Csum_i%5En%5Cleft%28%5Ctext%7Bctz%7D%28i%29+1%5Cright%29%20%3D%202n-%5Ctext%7Bpopcount%7D%28n%29) + +where: +ctz(x) = the number of trailing bits that are 0 in x +popcount(x) = the number of bits that are 1 in x + +It's a bit bewildering that these two seemingly unrelated bitwise instructions +are related by this property. But if we start to dissect this equation we can +see that it does hold. As n approaches infinity, we do end up with an average +overhead of 2 pointers as we find earlier. And popcount seems to handle the +error from this average as it accumulates in the CTZ skip-list. + +Now we can substitute into the original equation to get a trivial equation +for a file size: + +![summation2](https://latex.codecogs.com/svg.latex?N%20%3D%20Bn%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Cleft%282n-%5Ctext%7Bpopcount%7D%28n%29%5Cright%29) + +Unfortunately, we're not quite done. The popcount function is non-injective, +so we can only find the file size from the block index, not the other way +around. However, we can solve for an n' block index that is greater than n +with an error bounded by the range of the popcount function. We can then +repeatedly substitute this n' into the original equation until the error +is smaller than the integer division. As it turns out, we only need to +perform this substitution once. Now we directly calculate our block index: + +![formulaforn](https://latex.codecogs.com/svg.latex?n%20%3D%20%5Cleft%5Clfloor%5Cfrac%7BN-%5Cfrac%7Bw%7D%7B8%7D%5Cleft%28%5Ctext%7Bpopcount%7D%5Cleft%28%5Cfrac%7BN%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D-1%5Cright%29+2%5Cright%29%7D%7BB-2%5Cfrac%7Bw%7D%7B8%7D%7D%5Cright%5Crfloor) + +Now that we have our block index n, we can just plug it back into the above +equation to find the offset. However, we do need to rearrange the equation +a bit to avoid integer overflow: + +![formulaforoff](https://latex.codecogs.com/svg.latex?%5Cmathit%7Boff%7D%20%3D%20N%20-%20%5Cleft%28B-2%5Cfrac%7Bw%7D%7B8%7D%5Cright%29n%20-%20%5Cfrac%7Bw%7D%7B8%7D%5Ctext%7Bpopcount%7D%28n%29) + +The solution involves quite a bit of math, but computers are very good at math. +Now we can solve for both the block index and offset from the file size in O(1). + +Here is what it might look like to update a file stored with a CTZ skip-list: +``` + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 | + | file: 6 | file: 0 | + | size: 4 | size: 0 | + | xor: 3 | xor: 0 | + '---------'---------' + | + v + block 3 block 4 block 5 block 6 +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| data 2 |<-| data 3 | +| |<-| |--| | | | +| | | | | | | | +'--------' '--------' '--------' '--------' + +| update data in file +v + + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 | + | file: 6 | file: 0 | + | size: 4 | size: 0 | + | xor: 3 | xor: 0 | + '---------'---------' + | + v + block 3 block 4 block 5 block 6 +.--------. .--------. .--------. .--------. +| data 0 |<-| data 1 |<-| old |<-| old | +| |<-| |--| data 2 | | data 3 | +| | | | | | | | +'--------' '--------' '--------' '--------' + ^ ^ ^ + | | | block 7 block 8 block 9 block 10 + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' + +| update metadata pair +v + + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 | + | file: 6 | file: 10| + | size: 4 | size: 6 | + | xor: 3 | xor: 14 | + '---------'---------' + | + | + block 3 block 4 block 5 block 6 | +.--------. .--------. .--------. .--------. | +| data 0 |<-| data 1 |<-| old |<-| old | | +| |<-| |--| data 2 | | data 3 | | +| | | | | | | | | +'--------' '--------' '--------' '--------' | + ^ ^ ^ v + | | | block 7 block 8 block 9 block 10 + | | | .--------. .--------. .--------. .--------. + | | '----| new |<-| new |<-| new |<-| new | + | '----------------| data 2 |<-| data 3 |--| data 4 | | data 5 | + '------------------| |--| |--| | | | + '--------' '--------' '--------' '--------' +``` + +## Block allocation + +So those two ideas provide the grounds for the filesystem. The metadata pairs +give us directories, and the CTZ skip-lists give us files. But this leaves +one big [elephant](https://upload.wikimedia.org/wikipedia/commons/3/37/African_Bush_Elephant.jpg) +of a question. How do we get those blocks in the first place? + +One common strategy is to store unallocated blocks in a big free list, and +initially the littlefs was designed with this in mind. By storing a reference +to the free list in every single metadata pair, additions to the free list +could be updated atomically at the same time the replacement blocks were +stored in the metadata pair. During boot, every metadata pair had to be +scanned to find the most recent free list, but once the list was found the +state of all free blocks becomes known. + +However, this approach had several issues: + +- There was a lot of nuanced logic for adding blocks to the free list without + modifying the blocks, since the blocks remain active until the metadata is + updated. +- The free list had to support both additions and removals in FIFO order while + minimizing block erases. +- The free list had to handle the case where the file system completely ran + out of blocks and may no longer be able to add blocks to the free list. +- If we used a revision count to track the most recently updated free list, + metadata blocks that were left unmodified were ticking time bombs that would + cause the system to go haywire if the revision count overflowed. +- Every single metadata block wasted space to store these free list references. + +Actually, to simplify, this approach had one massive glaring issue: complexity. + +> Complexity leads to fallibility. +> Fallibility leads to unmaintainability. +> Unmaintainability leads to suffering. + +Or at least, complexity leads to increased code size, which is a problem +for embedded systems. + +In the end, the littlefs adopted more of a "drop it on the floor" strategy. +That is, the littlefs doesn't actually store information about which blocks +are free on the storage. The littlefs already stores which files _are_ in +use, so to find a free block, the littlefs just takes all of the blocks that +exist and subtract the blocks that are in use. + +Of course, it's not quite that simple. Most filesystems that adopt this "drop +it on the floor" strategy either rely on some properties inherent to the +filesystem, such as the cyclic-buffer structure of logging filesystems, +or use a bitmap or table stored in RAM to track free blocks, which scales +with the size of storage and is problematic when you have limited RAM. You +could iterate through every single block in storage and check it against +every single block in the filesystem on every single allocation, but that +would have an abhorrent runtime. + +So the littlefs compromises. It doesn't store a bitmap the size of the storage, +but it does store a little bit-vector that contains a fixed set lookahead +for block allocations. During a block allocation, the lookahead vector is +checked for any free blocks. If there are none, the lookahead region jumps +forward and the entire filesystem is scanned for free blocks. + +Here's what it might look like to allocate 4 blocks on a decently busy +filesystem with a 32bit lookahead and a total of +128 blocks (512Kbytes of storage if blocks are 4Kbyte): +``` +boot... lookahead: + fs blocks: fffff9fffffffffeffffffffffff0000 +scanning... lookahead: fffff9ff + fs blocks: fffff9fffffffffeffffffffffff0000 +alloc = 21 lookahead: fffffdff + fs blocks: fffffdfffffffffeffffffffffff0000 +alloc = 22 lookahead: ffffffff + fs blocks: fffffffffffffffeffffffffffff0000 +scanning... lookahead: fffffffe + fs blocks: fffffffffffffffeffffffffffff0000 +alloc = 63 lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffffffff + fs blocks: ffffffffffffffffffffffffffff0000 +scanning... lookahead: ffff0000 + fs blocks: ffffffffffffffffffffffffffff0000 +alloc = 112 lookahead: ffff8000 + fs blocks: ffffffffffffffffffffffffffff8000 +``` + +While this lookahead approach still has an asymptotic runtime of O(n^2) to +scan all of storage, the lookahead reduces the practical runtime to a +reasonable amount. Bit-vectors are surprisingly compact, given only 16 bytes, +the lookahead could track 128 blocks. For a 4Mbyte flash chip with 4Kbyte +blocks, the littlefs would only need 8 passes to scan the entire storage. + +The real benefit of this approach is just how much it simplified the design +of the littlefs. Deallocating blocks is as simple as simply forgetting they +exist, and there is absolutely no concern of bugs in the deallocation code +causing difficult to detect memory leaks. + +## Directories + +Now we just need directories to store our files. Since we already have +metadata blocks that store information about files, lets just use these +metadata blocks as the directories. Maybe turn the directories into linked +lists of metadata blocks so it isn't limited by the number of files that fit +in a single block. Add entries that represent other nested directories. +Drop "." and ".." entries, cause who needs them. Dust off our hands and +we now have a directory tree. + +``` + .--------. + |root dir| + | pair 0 | + | | + '--------' + .-' '-------------------------. + v v + .--------. .--------. .--------. + | dir A |------->| dir A | | dir B | + | pair 0 | | pair 1 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +Unfortunately it turns out it's not that simple. See, iterating over a +directory tree isn't actually all that easy, especially when you're trying +to fit in a bounded amount of RAM, which rules out any recursive solution. +And since our block allocator involves iterating over the entire filesystem +tree, possibly multiple times in a single allocation, iteration needs to be +efficient. + +So, as a solution, the littlefs adopted a sort of threaded tree. Each +directory not only contains pointers to all of its children, but also a +pointer to the next directory. These pointers create a linked-list that +is threaded through all of the directories in the filesystem. Since we +only use this linked list to check for existence, the order doesn't actually +matter. As an added plus, we can repurpose the pointer for the individual +directory linked-lists and avoid using any additional space. + +``` + .--------. + |root dir|-. + | pair 0 | | + .--------| |-' + | '--------' + | .-' '-------------------------. + | v v + | .--------. .--------. .--------. + '->| dir A |------->| dir A |------->| dir B | + | pair 0 | | pair 1 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + .-' '-. | .-' '-. + v v v v v +.--------. .--------. .--------. .--------. .--------. +| file C | | file D | | file E | | file F | | file G | +| | | | | | | | | | +| | | | | | | | | | +'--------' '--------' '--------' '--------' '--------' +``` + +This threaded tree approach does come with a few tradeoffs. Now, anytime we +want to manipulate the directory tree, we find ourselves having to update two +pointers instead of one. For anyone familiar with creating atomic data +structures this should set off a whole bunch of red flags. + +But unlike the data structure guys, we can update a whole block atomically! So +as long as we're really careful (and cheat a little bit), we can still +manipulate the directory tree in a way that is resilient to power loss. + +Consider how we might add a new directory. Since both pointers that reference +it can come from the same directory, we only need a single atomic update to +finagle the directory into the filesystem: +``` + .--------. + |root dir|-. + | pair 0 | | +.--| |-' +| '--------' +| | +| v +| .--------. +'->| dir A | + | pair 0 | + | | + '--------' + +| create the new directory block +v + + .--------. + |root dir|-. + | pair 0 | | + .--| |-' + | '--------' + | | + | v + | .--------. +.--------. '->| dir A | +| dir B |---->| pair 0 | +| pair 0 | | | +| | '--------' +'--------' + +| update root to point to directory B +v + + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir B |->| dir A | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' +``` + +Note that even though directory B was added after directory A, we insert +directory B before directory A in the linked-list because it is convenient. + +Now how about removal: +``` + .--------. .--------. + |root dir|------->|root dir|-. + | pair 0 | | pair 1 | | +.--------| |--------| |-' +| '--------' '--------' +| .-' '-. | +| v v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + | pair 0 | | pair 0 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + +| update root to no longer contain directory B +v + + .--------. .--------. + |root dir|------------->|root dir|-. + | pair 0 | | pair 1 | | +.--| |--------------| |-' +| '--------' '--------' +| | | +| v v +| .--------. .--------. .--------. +'->| dir A |->| dir B |->| dir C | + | pair 0 | | pair 0 | | pair 0 | + | | | | | | + '--------' '--------' '--------' + +| remove directory B from the linked-list +v + + .--------. .--------. + |root dir|->|root dir|-. + | pair 0 | | pair 1 | | +.--| |--| |-' +| '--------' '--------' +| | | +| v v +| .--------. .--------. +'->| dir A |->| dir C | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' +``` + +Wait, wait, wait, that's not atomic at all! If power is lost after removing +directory B from the root, directory B is still in the linked-list. We've +just created a memory leak! + +And to be honest, I don't have a clever solution for this case. As a +side-effect of using multiple pointers in the threaded tree, the littlefs +can end up with orphan blocks that have no parents and should have been +removed. + +To keep these orphan blocks from becoming a problem, the littlefs has a +deorphan step that simply iterates through every directory in the linked-list +and checks it against every directory entry in the filesystem to see if it +has a parent. The deorphan step occurs on the first block allocation after +boot, so orphans should never cause the littlefs to run out of storage +prematurely. Note that the deorphan step never needs to run in a read-only +filesystem. + +## The move problem + +Now we have a real problem. How do we move things between directories while +remaining power resilient? Even looking at the problem from a high level, +it seems impossible. We can update directory blocks atomically, but atomically +updating two independent directory blocks is not an atomic operation. + +Here's the steps the filesystem may go through to move a directory: +``` + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update directory B to point to directory A +v + + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-----' '-. +| | v +| | .--------. +| | .->| dir B | +| | | | pair 0 | +| | | | | +| | | '--------' +| | .-------' +| v v | +| .--------. | +'->| dir A |-' + | pair 0 | + | | + '--------' + +| update root to no longer contain directory A +v + .--------. + |root dir|-. + | pair 0 | | +.----| |-' +| '--------' +| | +| v +| .--------. +| .->| dir B | +| | | pair 0 | +| '--| |-. +| '--------' | +| | | +| v | +| .--------. | +'--->| dir A |-' + | pair 0 | + | | + '--------' +``` + +We can leave any orphans up to the deorphan step to collect, but that doesn't +help the case where dir A has both dir B and the root dir as parents if we +lose power inconveniently. + +Initially, you might think this is fine. Dir A _might_ end up with two parents, +but the filesystem will still work as intended. But then this raises the +question of what do we do when the dir A wears out? For other directory blocks +we can update the parent pointer, but for a dir with two parents we would need +work out how to update both parents. And the check for multiple parents would +need to be carried out for every directory, even if the directory has never +been moved. + +It also presents a bad user-experience, since the condition of ending up with +two parents is rare, it's unlikely user-level code will be prepared. Just think +about how a user would recover from a multi-parented directory. They can't just +remove one directory, since remove would report the directory as "not empty". + +Other atomic filesystems simple COW the entire directory tree. But this +introduces a significant bit of complexity, which leads to code size, along +with a surprisingly expensive runtime cost during what most users assume is +a single pointer update. + +Another option is to update the directory block we're moving from to point +to the destination with a sort of predicate that we have moved if the +destination exists. Unfortunately, the omnipresent concern of wear could +cause any of these directory entries to change blocks, and changing the +entry size before a move introduces complications if it spills out of +the current directory block. + +So how do we go about moving a directory atomically? + +We rely on the improbableness of power loss. + +Power loss during a move is certainly possible, but it's actually relatively +rare. Unless a device is writing to a filesystem constantly, it's unlikely that +a power loss will occur during filesystem activity. We still need to handle +the condition, but runtime during a power loss takes a back seat to the runtime +during normal operations. + +So what littlefs does is inelegantly simple. When littlefs moves a file, it +marks the file as "moving". This is stored as a single bit in the directory +entry and doesn't take up much space. Then littlefs moves the directory, +finishing with the complete remove of the "moving" directory entry. + +``` + .--------. + |root dir|-. + | pair 0 | | +.--------| |-' +| '--------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update root directory to mark directory A as moving +v + + .----------. + |root dir |-. + | pair 0 | | +.-------| moving A!|-' +| '----------' +| .-' '-. +| v v +| .--------. .--------. +'->| dir A |->| dir B | + | pair 0 | | pair 0 | + | | | | + '--------' '--------' + +| update directory B to point to directory A +v + + .----------. + |root dir |-. + | pair 0 | | +.-------| moving A!|-' +| '----------' +| .-----' '-. +| | v +| | .--------. +| | .->| dir B | +| | | | pair 0 | +| | | | | +| | | '--------' +| | .-------' +| v v | +| .--------. | +'->| dir A |-' + | pair 0 | + | | + '--------' + +| update root to no longer contain directory A +v + .--------. + |root dir|-. + | pair 0 | | +.----| |-' +| '--------' +| | +| v +| .--------. +| .->| dir B | +| | | pair 0 | +| '--| |-. +| '--------' | +| | | +| v | +| .--------. | +'--->| dir A |-' + | pair 0 | + | | + '--------' +``` + +Now, if we run into a directory entry that has been marked as "moved", one +of two things is possible. Either the directory entry exists elsewhere in the +filesystem, or it doesn't. This is a O(n) operation, but only occurs in the +unlikely case we lost power during a move. + +And we can easily fix the "moved" directory entry. Since we're already scanning +the filesystem during the deorphan step, we can also check for moved entries. +If we find one, we either remove the "moved" marking or remove the whole entry +if it exists elsewhere in the filesystem. + +## Wear awareness + +So now that we have all of the pieces of a filesystem, we can look at a more +subtle attribute of embedded storage: The wear down of flash blocks. + +The first concern for the littlefs, is that perfectly valid blocks can suddenly +become unusable. As a nice side-effect of using a COW data-structure for files, +we can simply move on to a different block when a file write fails. All +modifications to files are performed in copies, so we will only replace the +old file when we are sure none of the new file has errors. Directories, on +the other hand, need a different strategy. + +The solution to directory corruption in the littlefs relies on the redundant +nature of the metadata pairs. If an error is detected during a write to one +of the metadata pairs, we seek out a new block to take its place. Once we find +a block without errors, we iterate through the directory tree, updating any +references to the corrupted metadata pair to point to the new metadata block. +Just like when we remove directories, we can lose power during this operation +and end up with a desynchronized metadata pair in our filesystem. And just like +when we remove directories, we leave the possibility of a desynchronized +metadata pair up to the deorphan step to clean up. + +Here's what encountering a directory error may look like with all of +the directories and directory pointers fully expanded: +``` + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'--------------------------------------------------. +|| ||||'-----------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 0 |->| rev: 1 | rev: 0 | +'-->| | |->| | |->| | | + | | | | | | | + | | | | | | | | | + '---------'---------' '---------'---------' '---------'---------' + +| update directory B +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'--------------------------------------------------. +|| ||||'-----------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 2 |->| rev: 1 | rev: 0 | +'-->| | |->| | corrupt!|->| | | + | | | | | corrupt!| | | | + | | | | | corrupt!| | | | + '---------'---------' '---------'---------' '---------'---------' + +| oh no! corruption detected +v allocate a replacement block + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 0 |--. + | | |-.| +.------| | |-|' +|.-----| | |-' +|| '---------'---------' +|| |||||'----------------------------------------------------. +|| ||||'-------------------------------------------. | +|| |||'-----------------------------. | | +|| ||'--------------------. | | | +|| |'-------. | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 6 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 0 |->| rev: 1 | rev: 2 |--->| rev: 1 | rev: 0 | +'-->| | |->| | corrupt!|--->| | | + | | | | | corrupt!| .->| | | + | | | | | corrupt!| | | | | + '---------'---------' '---------'---------' | '---------'---------' + block 9 | + .---------. | + | rev: 2 |-' + | | + | | + | | + '---------' + +| update root directory to contain block 9 +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 |--. + | | |-.| +.-----| | |-|' +|.----| | |-' +|| '---------'---------' +|| .--------'||||'----------------------------------------------. +|| | |||'-------------------------------------. | +|| | ||'-----------------------. | | +|| | |'------------. | | | +|| | | | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 9 block 7 block 8 +|| .---------.---------. .---------. .---------. .---------.---------. +|'->| rev: 1 | rev: 0 |-->| rev: 1 |-| rev: 2 |--->| rev: 1 | rev: 0 | +'-->| | |-. | | | |--->| | | + | | | | | | | | .->| | | + | | | | | | | | | | | | + '---------'---------' | '---------' '---------' | '---------'---------' + | block 6 | + | .---------. | + '------------>| rev: 2 |-' + | corrupt!| + | corrupt!| + | corrupt!| + '---------' + +| remove corrupted block from linked-list +v + + root dir + block 1 block 2 + .---------.---------. + | rev: 1 | rev: 2 |--. + | | |-.| +.-----| | |-|' +|.----| | |-' +|| '---------'---------' +|| .--------'||||'-----------------------------------------. +|| | |||'--------------------------------. | +|| | ||'--------------------. | | +|| | |'-----------. | | | +|| | | | | | | +|| v v v v v v +|| dir A dir B dir C +|| block 3 block 4 block 5 block 9 block 7 block 8 +|| .---------.---------. .---------.---------. .---------.---------. +|'->| rev: 1 | rev: 2 |->| rev: 1 | rev: 2 |->| rev: 1 | rev: 0 | +'-->| | |->| | |->| | | + | | | | | | | | | + | | | | | | | | | + '---------'---------' '---------'---------' '---------'---------' +``` + +Also one question I've been getting is, what about the root directory? +It can't move so wouldn't the filesystem die as soon as the root blocks +develop errors? And you would be correct. So instead of storing the root +in the first few blocks of the storage, the root is actually pointed to +by the superblock. The superblock contains a few bits of static data, but +outside of when the filesystem is formatted, it is only updated when the root +develops errors and needs to be moved. + +## Wear leveling + +The second concern for the littlefs is that blocks in the filesystem may wear +unevenly. In this situation, a filesystem may meet an early demise where +there are no more non-corrupted blocks that aren't in use. It's common to +have files that were written once and left unmodified, wasting the potential +erase cycles of the blocks it sits on. + +Wear leveling is a term that describes distributing block writes evenly to +avoid the early termination of a flash part. There are typically two levels +of wear leveling: +1. Dynamic wear leveling - Wear is distributed evenly across all **dynamic** + blocks. Usually this is accomplished by simply choosing the unused block + with the lowest amount of wear. Note this does not solve the problem of + static data. +2. Static wear leveling - Wear is distributed evenly across all **dynamic** + and **static** blocks. Unmodified blocks may be evicted for new block + writes. This does handle the problem of static data but may lead to + wear amplification. + +In littlefs's case, it's possible to use the revision count on metadata pairs +to approximate the wear of a metadata block. And combined with the COW nature +of files, littlefs could provide your usual implementation of dynamic wear +leveling. + +However, the littlefs does not. This is for a few reasons. Most notably, even +if the littlefs did implement dynamic wear leveling, this would still not +handle the case of write-once files, and near the end of the lifetime of a +flash device, you would likely end up with uneven wear on the blocks anyways. + +As a flash device reaches the end of its life, the metadata blocks will +naturally be the first to go since they are updated most often. In this +situation, the littlefs is designed to simply move on to another set of +metadata blocks. This travelling means that at the end of a flash device's +life, the filesystem will have worn the device down nearly as evenly as the +usual dynamic wear leveling could. More aggressive wear leveling would come +with a code-size cost for marginal benefit. + + +One important takeaway to note, if your storage stack uses highly sensitive +storage such as NAND flash, static wear leveling is the only valid solution. +In most cases you are going to be better off using a full [flash translation layer (FTL)](https://en.wikipedia.org/wiki/Flash_translation_layer). +NAND flash already has many limitations that make it poorly suited for an +embedded system: low erase cycles, very large blocks, errors that can develop +even during reads, errors that can develop during writes of neighboring blocks. +Managing sensitive storage such as NAND flash is out of scope for the littlefs. +The littlefs does have some properties that may be beneficial on top of a FTL, +such as limiting the number of writes where possible, but if you have the +storage requirements that necessitate the need of NAND flash, you should have +the RAM to match and just use an FTL or flash filesystem. + +## Summary + +So, to summarize: + +1. The littlefs is composed of directory blocks +2. Each directory is a linked-list of metadata pairs +3. These metadata pairs can be updated atomically by alternating which + metadata block is active +4. Directory blocks contain either references to other directories or files +5. Files are represented by copy-on-write CTZ skip-lists which support O(1) + append and O(n log n) reading +6. Blocks are allocated by scanning the filesystem for used blocks in a + fixed-size lookahead region that is stored in a bit-vector +7. To facilitate scanning the filesystem, all directories are part of a + linked-list that is threaded through the entire filesystem +8. If a block develops an error, the littlefs allocates a new block, and + moves the data and references of the old block to the new. +9. Any case where an atomic operation is not possible, mistakes are resolved + by a deorphan step that occurs on the first allocation after boot + +That's the little filesystem. Thanks for reading! + diff --git a/fs/littlefs/Kconfig b/fs/littlefs/Kconfig new file mode 100644 index 0000000000..ae69d7ed34 --- /dev/null +++ b/fs/littlefs/Kconfig @@ -0,0 +1,9 @@ +config FS_LITTLEFS + bool "LITTLEFS File System" + default n + select FS_READABLE + select FS_WRITABLE + depends on !DISABLE_MOUNTPOINT && MTD_BYTE_WRITE && EXPERIMENTAL + ---help--- + Build the LITTLEFS file system. https://github.com/ARMmbed/littlefs. + diff --git a/fs/littlefs/Make.defs b/fs/littlefs/Make.defs new file mode 100644 index 0000000000..3997c0bd5f --- /dev/null +++ b/fs/littlefs/Make.defs @@ -0,0 +1,55 @@ +############################################################################# +# fs/littlefs/lfs.c +# +# This file is a part of NuttX: +# +# Copyright (C) 2019 Gregory Nutt. All rights reserved. +# +# Ported by: +# +# Copyright (C) 2019 Pinecone Inc. All rights reserved. +# Author: lihaichen +# +# This port derives from ARM mbed logic which has a compatible 3-clause +# BSD license: +# +# Copyright (c) 2017, Arm Limited. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################# + +ifeq ($(CONFIG_FS_LITTLEFS),y) +# Files required for littlefs file system support + +CSRCS += lfs.c lfs_util.c lfs_vfs.c + +DEPPATH += --dep-path littlefs +VPATH += :littlefs + +endif \ No newline at end of file diff --git a/fs/littlefs/README.md b/fs/littlefs/README.md new file mode 100644 index 0000000000..cfdac1c6ea --- /dev/null +++ b/fs/littlefs/README.md @@ -0,0 +1,220 @@ +## usage + +depends on !DISABLE_MOUNTPOINT && MTD_BYTE_WRITE + +1. register_mtddriver("/dev/w25", mtd, 0755, NULL); +2. mount("/dev/w25", "/w25", "littlefs", 0, NULL); + +## need to do + +1. no format tool, mount auto format. + + +## The little filesystem + +A little fail-safe filesystem designed for embedded systems. + +``` + | | | .---._____ + .-----. | | +--|o |---| littlefs | +--| |---| | + '-----' '----------' + | | | +``` + +**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount +of memory. Recursion is avoided and dynamic memory is limited to configurable +buffers that can be provided statically. + +**Power-loss resilient** - The littlefs is designed for systems that may have +random power failures. The littlefs has strong copy-on-write guarantees and +storage on disk is always kept in a valid state. + +**Wear leveling** - Since the most common form of embedded storage is erodible +flash memories, littlefs provides a form of dynamic wear leveling for systems +that can not fit a full flash translation layer. + +## Example + +Here's a simple example that updates a file named `boot_count` every time +main runs. The program can be interrupted at any time without losing track +of how many times it has been booted and without corrupting the filesystem: + +``` c +#include "lfs.h" + +/* variables used by the filesystem */ + +lfs_t lfs; +lfs_file_t file; + +/* configuration of the filesystem is provided by this struct */ + +const struct lfs_config cfg = +{ + /* block device operations */ + + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + + /* block device configuration */ + + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .lookahead = 128, +}; + +/* entry point */ + +int main(void) +{ + /* mount the filesystem */ + + int err = lfs_mount(&lfs, &cfg); + + /* reformat if we can't mount the filesystem + * this should only happen on the first boot + */ + + if (err) + { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + + /* read current count */ + + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + + /* update boot count */ + + boot_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + + /* remember the storage is not updated until the file is closed successfully */ + + lfs_file_close(&lfs, &file); + + /* release any resources we were using */ + + lfs_unmount(&lfs); + + /* print the boot count */ + + printf("boot_count: %d\n", boot_count); +} +``` + +## Usage + +Detailed documentation (or at least as much detail as is currently available) +can be found in the comments in [lfs.h](lfs.h). + +As you may have noticed, littlefs takes in a configuration structure that +defines how the filesystem operates. The configuration struct provides the +filesystem with the block device operations and dimensions, tweakable +parameters that tradeoff memory usage for performance, and optional +static buffers if the user wants to avoid dynamic memory. + +The state of the littlefs is stored in the `lfs_t` type which is left up +to the user to allocate, allowing multiple filesystems to be in use +simultaneously. With the `lfs_t` and configuration struct, a user can +format a block device or mount the filesystem. + +Once mounted, the littlefs provides a full set of POSIX-like file and +directory functions, with the deviation that the allocation of filesystem +structures must be provided by the user. + +All POSIX operations, such as remove and rename, are atomic, even in event +of power-loss. Additionally, no file updates are actually committed to the +filesystem until sync or close is called on the file. + +## Other notes + +All littlefs have the potential to return a negative error code. The errors +can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h), +or an error returned by the user's block device operations. + +In the configuration struct, the `prog` and `erase` function provided by the +user may return a `LFS_ERR_CORRUPT` error if the implementation already can +detect corrupt blocks. However, the wear leveling does not depend on the return +code of these functions, instead all data is read back and checked for +integrity. + +If your storage caches writes, make sure that the provided `sync` function +flushes all the data to memory and ensures that the next read fetches the data +from memory, otherwise data integrity can not be guaranteed. If the `write` +function does not perform caching, and therefore each `read` or `write` call +hits the memory, the `sync` function can simply return 0. + +## Reference material + +[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how +littlefs actually works. I would encourage you to read it since the +solutions and tradeoffs at work here are quite interesting. + +[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs +with all the nitty-gritty details. Can be useful for developing tooling. + +## Testing + +The littlefs comes with a test suite designed to run on a PC using the +[emulated block device](emubd/lfs_emubd.h) found in the emubd directory. +The tests assume a Linux environment and can be started with make: + +``` bash +make test +``` + +## License + +The littlefs is provided under the [BSD-3-Clause](https://spdx.org/licenses/BSD-3-Clause.html) +license. See [LICENSE.md](LICENSE.md) for more information. Contributions to +this project are accepted under the same license. + +Individual files contain the following tag instead of the full license text. + + SPDX-License-Identifier: BSD-3-Clause + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ + +## Related projects + +[Mbed OS](https://github.com/ARMmbed/mbed-os/tree/master/features/filesystem/littlefs) - +The easiest way to get started with littlefs is to jump into [Mbed](https://os.mbed.com/), +which already has block device drivers for most forms of embedded storage. The +littlefs is available in Mbed OS as the [LittleFileSystem](https://os.mbed.com/docs/latest/reference/littlefilesystem.html) +class. + +[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse) +wrapper for littlefs. The project allows you to mount littlefs directly on a +Linux machine. Can be useful for debugging littlefs if you have an SD card +handy. + +[littlefs-js](https://github.com/geky/littlefs-js) - A javascript wrapper for +littlefs. I'm not sure why you would want this, but it is handy for demos. +You can see it in action [here](http://littlefs.geky.net/demo.html). + +[mklfs](https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src) - +A command line tool built by the [Lua RTOS](https://github.com/whitecatboard/Lua-RTOS-ESP32) +guys for making littlefs images from a host PC. Supports Windows, Mac OS, +and Linux. + +[SPIFFS](https://github.com/pellepl/spiffs) - Another excellent embedded +filesystem for NOR flash. As a more traditional logging filesystem with full +static wear-leveling, SPIFFS will likely outperform littlefs on small +memories such as the internal flash on microcontrollers. + +[Dhara](https://github.com/dlbeer/dhara) - An interesting NAND flash +translation layer designed for small MCUs. It offers static wear-leveling and +power-resilience with only a fixed O(|address|) pointer structure stored on +each block and in RAM. diff --git a/fs/littlefs/SPEC.md b/fs/littlefs/SPEC.md new file mode 100644 index 0000000000..2a1f9eca8b --- /dev/null +++ b/fs/littlefs/SPEC.md @@ -0,0 +1,370 @@ +## 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 +- 0x2e - superblock entry + +Additionally, the type is broken into two 4 bit nibbles, with the upper nibble +specifying the type's data structure used when scanning the filesystem. The +lower nibble clarifies the type further when multiple entries share the same +data structure. + +The highest bit is reserved for marking the entry as "moved". If an entry +is marked as "moved", the entry may also exist somewhere else in the +filesystem. If the entry exists elsewhere, this entry must be treated as +though it does not exist. + +**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 guarantee 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 (0x2e 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 (0x2e) +(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: 2e 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 skip-list that is being referenced. + +How files are actually stored on disk is a bit complicated. The full +explanation of CTZ skip-lists can be found in [DESIGN.md](DESIGN.md#ctz-skip-lists). + +A terribly quick summary: For every nth block where n is divisible by 2^x, +the block contains a pointer to block n-2^x. These pointers are stored in +increasing order of x in each block of the file preceding the data in the +block. + +The maximum number of pointers in a block is bounded by the maximum file size +divided by the block size. With 32 bits for file size, this results in a +minimum block size of 104 bytes. + +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 +skip-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 +``` diff --git a/fs/littlefs/lfs.c b/fs/littlefs/lfs.c new file mode 100644 index 0000000000..b93cfeed88 --- /dev/null +++ b/fs/littlefs/lfs.c @@ -0,0 +1,3367 @@ +/**************************************************************************** + * fs/littlefs/lfs.c + * + * This file is a part of NuttX: + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * + * Ported by: + * + * Copyright (C) 2019 Pinecone Inc. All rights reserved. + * Author: lihaichen + * + * This port derives from ARM mbed logic which has a compatible 3-clause + * BSD license: + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "lfs.h" +#include "lfs_util.h" + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct lfs_region +{ + lfs_off_t oldoff; + lfs_size_t oldlen; + const void *newdata; + lfs_size_t newlen; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +int lfs_traverse(FAR lfs_t *lfs, CODE int (*cb)(FAR void *, lfs_block_t), + FAR void *data); +static int lfs_pred(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], + FAR lfs_dir_t *pdir); +static int lfs_parent(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], + FAR lfs_dir_t *parent, FAR lfs_entry_t *entry); +static int lfs_moved(FAR lfs_t *lfs, FAR const void *e); +static int lfs_relocate(FAR lfs_t *lfs, FAR const lfs_block_t oldpair[2], + FAR const lfs_block_t newpair[2]); +int lfs_deorphan(FAR lfs_t *lfs); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Caching block device operations */ + +static int lfs_cache_read(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, FAR void *buffer, lfs_size_t size) +{ + FAR uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) + { + if (pcache && block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) + { + /* is already in pcache? */ + + lfs_size_t diff = + lfs_min(size, lfs->cfg->prog_size - (off - pcache->off)); + memcpy(data, &pcache->buffer[off - pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (block == rcache->block && off >= rcache->off && + off < rcache->off + lfs->cfg->read_size) + { + /* is already in rcache? */ + + lfs_size_t diff = + lfs_min(size, lfs->cfg->read_size - (off - rcache->off)); + memcpy(data, &rcache->buffer[off - rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) + { + /* bypass cache? */ + + lfs_size_t diff = size - (size % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) + { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + /* load to cache, first condition can no longer fail */ + + rcache->block = block; + rcache->off = off - (off % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, rcache->off, + rcache->buffer, lfs->cfg->read_size); + if (err) + { + return err; + } + } + + return 0; +} + +static int lfs_cache_cmp(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, FAR const void *buffer, + lfs_size_t size) +{ + FAR const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) + { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1); + if (err) + { + return err; + } + + if (c != data[i]) + { + return false; + } + } + + return true; +} + +static int lfs_cache_crc(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, lfs_size_t size, FAR uint32_t *crc) +{ + for (lfs_off_t i = 0; i < size; i++) + { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1); + if (err) + { + return err; + } + + lfs_crc(crc, &c, 1); + } + + return 0; +} + +static inline void lfs_cache_drop(FAR lfs_t *lfs, FAR lfs_cache_t *rcache) +{ + /* do not zero, cheaper if cache is readonly or only going to be + * written with identical data (during relocates) + */ + + rcache->block = 0xffffffff; +} + +static inline void lfs_cache_zero(FAR lfs_t *lfs, FAR lfs_cache_t *pcache) +{ + /* zero to avoid information leak */ + + memset(pcache->buffer, 0xff, lfs->cfg->prog_size); + pcache->block = 0xffffffff; +} + +static int lfs_cache_flush(FAR lfs_t *lfs, FAR lfs_cache_t *pcache, + FAR lfs_cache_t *rcache) +{ + if (pcache->block != 0xffffffff) + { + int err = lfs->cfg->prog(lfs->cfg, pcache->block, pcache->off, + pcache->buffer, lfs->cfg->prog_size); + if (err) + { + return err; + } + + if (rcache) + { + int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block, pcache->off, + pcache->buffer, lfs->cfg->prog_size); + if (res < 0) + { + return res; + } + + if (!res) + { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} + +static int lfs_cache_prog(FAR lfs_t *lfs, FAR lfs_cache_t *pcache, + FAR lfs_cache_t *rcache, lfs_block_t block, + lfs_off_t off, FAR const void *buffer, + lfs_size_t size) +{ + FAR const uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) + { + if (block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) + { + /* is already in pcache? */ + + lfs_size_t diff = + lfs_min(size, lfs->cfg->prog_size - (off - pcache->off)); + memcpy(&pcache->buffer[off - pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + if (off % lfs->cfg->prog_size == 0) + { + /* eagerly flush out pcache if we fill up */ + + int err = lfs_cache_flush(lfs, pcache, rcache); + if (err) + { + return err; + } + } + + continue; + } + + /* pcache must have been flushed, either by programming and + * entire block or manually flushing the pcache + */ + + LFS_ASSERT(pcache->block == 0xffffffff); + + if (off % lfs->cfg->prog_size == 0 && size >= lfs->cfg->prog_size) + { + /* bypass pcache? */ + + lfs_size_t diff = size - (size % lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); + if (err) + { + return err; + } + + if (rcache) + { + int res = + lfs_cache_cmp(lfs, rcache, NULL, block, off, data, diff); + if (res < 0) + { + return res; + } + + if (!res) + { + return LFS_ERR_CORRUPT; + } + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + /* prepare pcache, first condition can no longer fail */ + + pcache->block = block; + pcache->off = off - (off % lfs->cfg->prog_size); + } + + return 0; +} + +/* General lfs block device operations */ + +static int lfs_bd_read(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, + FAR void *buffer, lfs_size_t size) +{ + /* if we ever do more than writes to alternating pairs, + * this may need to consider pcache + */ + + return lfs_cache_read(lfs, &lfs->rcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_prog(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, + FAR const void *buffer, lfs_size_t size) +{ + return lfs_cache_prog(lfs, &lfs->pcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_cmp(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, + FAR const void *buffer, lfs_size_t size) +{ + return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_crc(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, + lfs_size_t size, FAR uint32_t *crc) +{ + return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc); +} + +static int lfs_bd_erase(FAR lfs_t *lfs, lfs_block_t block) +{ + return lfs->cfg->erase(lfs->cfg, block); +} + +static int lfs_bd_sync(FAR lfs_t *lfs) +{ + lfs_cache_drop(lfs, &lfs->rcache); + + int err = lfs_cache_flush(lfs, &lfs->pcache, NULL); + if (err) + { + return err; + } + + return lfs->cfg->sync(lfs->cfg); +} + +/* Block allocator */ + +static int lfs_alloc_lookahead(FAR void *p, lfs_block_t block) +{ + FAR lfs_t *lfs = p; + + lfs_block_t off = + ((block - lfs->free.off) + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) + { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static int lfs_alloc(FAR lfs_t *lfs, FAR lfs_block_t *block) +{ + while (true) + { + while (lfs->free.i != lfs->free.size) + { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) + { + /* found a free block */ + + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + /* eagerly find next off so an alloc ack can + * discredit old lookahead blocks + */ + + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] & + (1U << (lfs->free.i % 32)))) + { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + /* check if we have looked at all blocks since last ack */ + + if (lfs->free.ack == 0) + { + LFS_WARN("No more free space %" PRIu32, lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) % lfs->cfg->block_count; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack); + lfs->free.i = 0; + + /* find mask of free blocks from tree */ + + memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8); + int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); + if (err) + { + return err; + } + } +} + +static void lfs_alloc_ack(FAR lfs_t *lfs) +{ + lfs->free.ack = lfs->cfg->block_count; +} + +/* Endian swapping functions */ + +static void lfs_dir_fromle32(FAR struct lfs_disk_dir *d) +{ + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs_dir_tole32(FAR struct lfs_disk_dir *d) +{ + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs_entry_fromle32(FAR struct lfs_disk_entry *d) +{ + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs_entry_tole32(FAR struct lfs_disk_entry *d) +{ + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs_superblock_fromle32(FAR struct lfs_disk_superblock *d) +{ + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + +static void lfs_superblock_tole32(FAR struct lfs_disk_superblock *d) +{ + d->root[0] = lfs_tole32(d->root[0]); + d->root[1] = lfs_tole32(d->root[1]); + d->block_size = lfs_tole32(d->block_size); + d->block_count = lfs_tole32(d->block_count); + d->version = lfs_tole32(d->version); +} + +/* Metadata pair and directory operations */ + +static inline void lfs_pairswap(FAR lfs_block_t pair[2]) +{ + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pairisnull(FAR const lfs_block_t pair[2]) +{ + return pair[0] == 0xffffffff || pair[1] == 0xffffffff; +} + +static inline int lfs_paircmp(FAR const lfs_block_t paira[2], + FAR const lfs_block_t pairb[2]) +{ + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pairsync(FAR const lfs_block_t paira[2], + FAR const lfs_block_t pairb[2]) +{ + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline lfs_size_t lfs_entry_size(FAR const lfs_entry_t *entry) +{ + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs_dir_alloc(FAR lfs_t *lfs, FAR lfs_dir_t *dir) +{ + /* allocate pair of dir blocks */ + + for (int i = 0; i < 2; i++) + { + int err = lfs_alloc(lfs, &dir->pair[i]); + if (err) + { + return err; + } + } + + /* rather than clobbering one of the blocks we just pretend + * the revision may be valid + */ + + int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4); + if (err && err != LFS_ERR_CORRUPT) + { + return err; + } + + if (err != LFS_ERR_CORRUPT) + { + dir->d.rev = lfs_fromle32(dir->d.rev); + } + + /* set defaults */ + + dir->d.rev += 1; + dir->d.size = sizeof(dir->d) + 4; + dir->d.tail[0] = 0xffffffff; + dir->d.tail[1] = 0xffffffff; + dir->off = sizeof(dir->d); + + /* don't write out yet, let caller take care of that */ + + return 0; +} + +static int lfs_dir_fetch(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR const lfs_block_t pair[2]) +{ + /* copy out pair, otherwise may be aliasing dir */ + + FAR const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + /* check both blocks for the most recent revision */ + + for (int i = 0; i < 2; i++) + { + struct lfs_disk_dir test; + uint32_t crc; + int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs_dir_fromle32(&test); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) + { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test) + 4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) + { + continue; + } + + crc = 0xffffffff; + lfs_dir_tole32(&test); + lfs_crc(&crc, &test, sizeof(test)); + lfs_dir_fromle32(&test); + err = lfs_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + continue; + } + + return err; + } + + if (crc != 0) + { + continue; + } + + valid = true; + + /* setup dir in case it's valid */ + + dir->pair[0] = tpair[(i + 0) % 2]; + dir->pair[1] = tpair[(i + 1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) + { + LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32, tpair[0], + tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +static int lfs_dir_commit(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR const struct lfs_region *regions, int count) +{ + /* increment revision count */ + + dir->d.rev += 1; + + /* 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; + } + + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + + while (true) + { + if (true) + { + uint32_t crc; + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + crc = 0xffffffff; + lfs_dir_tole32(&dir->d); + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + lfs_dir_fromle32(&dir->d); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size) - 4) + { + if (i < count && regions[i].oldoff == oldoff) + { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, + regions[i].newdata, regions[i].newlen); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } + else + { + uint8_t data; + err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) + { + return err; + } + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; + } + } + + crc = lfs_tole32(crc); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + crc = lfs_fromle32(crc); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + err = lfs_bd_sync(lfs); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + /* successful commit, check checksum to make sure */ + + uint32_t ncrc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, (0x7fffffff & dir->d.size) - 4, + &ncrc); + if (err) + { + return err; + } + + if (ncrc != crc) + { + goto relocate; + } + } + + break; + relocate: + /* commit was corrupted */ + + LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]); + + /* drop caches and prepare to relocate block */ + + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + + /* can't relocate superblock, filesystem is now frozen */ + + if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) + { + LFS_WARN("Superblock %" PRIu32 " has become unwritable", oldpair[0]); + return LFS_ERR_CORRUPT; + } + + /* relocate half of pair */ + + int err = lfs_alloc(lfs, &dir->pair[0]); + if (err) + { + return err; + } + } + + if (relocated) + { + /* update references if we relocated */ + + LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32, + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + int err = lfs_relocate(lfs, oldpair, dir->pair); + if (err) + { + return err; + } + } + + /* shift over any directories that are affected */ + + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) + { + if (lfs_paircmp(d->pair, dir->pair) == 0) + { + d->pair[0] = dir->pair[0]; + d->pair[1] = dir->pair[1]; + } + } + + return 0; +} + +static int lfs_dir_update(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR lfs_entry_t *entry, FAR const void *data) +{ + int err; + + lfs_entry_tole32(&entry->d); + err = lfs_dir_commit( + lfs, dir, + (struct lfs_region[]) + { + { entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d) }, + { entry->off + sizeof(entry->d), entry->d.nlen, data, entry->d.nlen } + }, + data ? 2 : 1); + lfs_entry_fromle32(&entry->d); + return err; +} + +static int lfs_dir_append(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR lfs_entry_t *entry, FAR const void *data) +{ + /* check if we fit, if top bit is set we do not and move on */ + + while (true) + { + if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) + { + entry->off = dir->d.size - 4; + + lfs_entry_tole32(&entry->d); + int err = + lfs_dir_commit(lfs, dir, + (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen}}, + 2); + lfs_entry_fromle32(&entry->d); + return err; + } + + /* we need to allocate a new dir block */ + + if (!(0x80000000 & dir->d.size)) + { + lfs_dir_t olddir = *dir; + int err = lfs_dir_alloc(lfs, dir); + if (err) + { + return err; + } + + dir->d.tail[0] = olddir.d.tail[0]; + dir->d.tail[1] = olddir.d.tail[1]; + entry->off = dir->d.size - 4; + lfs_entry_tole32(&entry->d); + err = lfs_dir_commit(lfs, dir, + (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen}}, + 2); + lfs_entry_fromle32(&entry->d); + if (err) + { + return err; + } + + olddir.d.size |= 0x80000000; + olddir.d.tail[0] = dir->pair[0]; + olddir.d.tail[1] = dir->pair[1]; + return lfs_dir_commit(lfs, &olddir, NULL, 0); + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) + { + return err; + } + } +} + +static int lfs_dir_remove(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR lfs_entry_t *entry) +{ + /* check if we should just drop the directory block */ + + 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) + { + return res; + } + + if (pdir.d.size & 0x80000000) + { + 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, &pdir, NULL, 0); + } + } + + /* shift out the entry */ + + 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/directories 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); + } + } + } + + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) + { + if (lfs_paircmp(d->pair, dir->pair) == 0) + { + if (d->off > entry->off) + { + d->off -= lfs_entry_size(entry); + d->pos -= lfs_entry_size(entry); + } + } + } + + return 0; +} + +static int lfs_dir_next(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR lfs_entry_t *entry) +{ + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size) - 4) + { + if (!(0x80000000 & dir->d.size)) + { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) + { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs_bd_read(lfs, dir->pair[0], dir->off, &entry->d, + sizeof(entry->d)); + lfs_entry_fromle32(&entry->d); + if (err) + { + return err; + } + + entry->off = dir->off; + dir->off += lfs_entry_size(entry); + dir->pos += lfs_entry_size(entry); + return 0; +} + +static int lfs_dir_find(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR lfs_entry_t *entry, + FAR const char **path) +{ + FAR const char *pathname = *path; + size_t pathlen; + entry->d.type = LFS_TYPE_DIR; + entry->d.elen = sizeof(entry->d) - 4; + entry->d.alen = 0; + entry->d.nlen = 0; + entry->d.u.dir[0] = lfs->root[0]; + entry->d.u.dir[1] = lfs->root[1]; + + while (true) + { + FAR const char *suffix; + size_t sufflen; + int depth; + + nextname: + /* skip slashes */ + + pathname += strspn(pathname, "/"); + pathlen = strcspn(pathname, "/"); + + /* skip '.' and root '..' */ + + if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) || + (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) + { + pathname += pathlen; + goto nextname; + } + + /* skip if matched by '..' in name */ + + suffix = pathname + pathlen; + depth = 1; + while (true) + { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) + { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) + { + depth -= 1; + if (depth == 0) + { + pathname = suffix + sufflen; + goto nextname; + } + } + else + { + depth += 1; + } + + suffix += sufflen; + } + + /* found path */ + + if (pathname[0] == '\0') + { + return 0; + } + + /* update what we've found */ + + *path = pathname; + + /* continue on if we hit a directory */ + + if (entry->d.type != LFS_TYPE_DIR) + { + return LFS_ERR_NOTDIR; + } + + int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir); + if (err) + { + return err; + } + + /* find entry matching name */ + + while (true) + { + err = lfs_dir_next(lfs, dir, entry); + if (err) + { + return err; + } + + if (((0x7f & entry->d.type) != LFS_TYPE_REG && + (0x7f & entry->d.type) != LFS_TYPE_DIR) || + entry->d.nlen != pathlen) + { + continue; + } + + int res = lfs_bd_cmp(lfs, dir->pair[0], + entry->off + 4 + entry->d.elen + entry->d.alen, + pathname, pathlen); + if (res < 0) + { + return res; + } + + /* found match */ + + if (res) + { + break; + } + } + + /* check that entry has not been moved */ + + if (!lfs->moving && 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; + } + + /* to next name */ + + pathname += pathlen; + } +} + +/* File index list operations */ + +static int lfs_ctz_index(FAR lfs_t *lfs, FAR lfs_off_t *off) +{ + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2 * 4; + lfs_off_t i = size / b; + if (i == 0) + { + return 0; + } + + i = (size - 4 * (lfs_popc(i - 1) + 2)) / b; + *off = size - b * i - 4 * lfs_popc(i); + return i; +} + +static int lfs_ctz_find(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR const lfs_cache_t *pcache, lfs_block_t head, + lfs_size_t size, lfs_size_t pos, + FAR lfs_block_t *block, FAR lfs_off_t *off) +{ + if (size == 0) + { + *block = 0xffffffff; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size - 1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) + { + int err; + + lfs_size_t skip = + lfs_min(lfs_npw2(current - target + 1) - 1, lfs_ctz(current)); + + err = lfs_cache_read(lfs, rcache, pcache, head, 4 * skip, &head, 4); + head = lfs_fromle32(head); + if (err) + { + return err; + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_ctz_extend(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR lfs_cache_t *pcache, lfs_block_t head, + lfs_size_t size, FAR lfs_block_t *block, + FAR lfs_off_t *off) +{ + while (true) + { + lfs_block_t nblock; + int err; + + /* go ahead and grab a block */ + + err = lfs_alloc(lfs, &nblock); + if (err) + { + return err; + } + + LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); + + if (true) + { + lfs_off_t index; + lfs_size_t skips; + + err = lfs_bd_erase(lfs, nblock); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + + return err; + } + + if (size == 0) + { + *block = nblock; + *off = 0; + return 0; + } + + size -= 1; + index = lfs_ctz_index(lfs, &size); + size += 1; + + /* just copy out the last block if it is incomplete */ + + if (size != lfs->cfg->block_size) + { + for (lfs_off_t i = 0; i < size; i++) + { + uint8_t data; + err = lfs_cache_read(lfs, rcache, NULL, head, i, + &data, 1); + if (err) + { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, nblock, i, + &data, 1); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = size; + return 0; + } + + /* append block */ + + index += 1; + skips = lfs_ctz(index) + 1; + + for (lfs_off_t i = 0; i < skips; i++) + { + head = lfs_tole32(head); + err = lfs_cache_prog(lfs, pcache, rcache, nblock, 4 * i, + &head, 4); + head = lfs_fromle32(head); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + + return err; + } + + if (i != skips - 1) + { + err = lfs_cache_read(lfs, rcache, NULL, head, 4 * i, + &head, 4); + head = lfs_fromle32(head); + if (err) + { + return err; + } + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + } + + *block = nblock; + *off = 4 * skips; + return 0; + } + + relocate: + LFS_DEBUG("Bad block at %" PRIu32, nblock); + + /* just clear cache and try a new block */ + + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +static int lfs_ctz_traverse(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, + FAR const lfs_cache_t *pcache, lfs_block_t head, + FAR lfs_size_t size, + CODE int (*cb)(FAR void *, lfs_block_t), + FAR void *data) +{ + lfs_off_t index; + + if (size == 0) + { + return 0; + } + + index = lfs_ctz_index(lfs, &(lfs_off_t){size - 1}); + + while (true) + { + lfs_block_t heads[2]; + int count; + int err = cb(data, head); + if (err) + { + return err; + } + + if (index == 0) + { + return 0; + } + + count = 2 - (index & 1); + err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count * 4); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) + { + return err; + } + + for (int i = 0; i < count - 1; i++) + { + err = cb(data, heads[i]); + if (err) + { + return err; + } + } + + head = heads[count - 1]; + index -= count; + } +} + +static int lfs_file_relocate(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + lfs_block_t nblock; + int err; + +relocate: + LFS_DEBUG("Bad block at %" PRIu32, file->block); + + /* just relocate what exists into new block */ + + err = lfs_alloc(lfs, &nblock); + if (err) + { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + /* either read from dirty cache or disk */ + + for (lfs_off_t i = 0; i < file->off; i++) + { + uint8_t data; + err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, file->block, i, + &data, 1); + if (err) + { + return err; + } + + err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, nblock, i, + &data, 1); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + + return err; + } + } + + /* copy over new state of file */ + + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + return 0; +} + +static int lfs_file_flush(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + if (file->flags & LFS_F_READING) + { + /* just drop read cache */ + + lfs_cache_drop(lfs, &file->cache); + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) + { + lfs_off_t pos = file->pos; + + /* copy over anything after current branch */ + + lfs_file_t orig = + { + .head = file->head, + .size = file->size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->size) + { + /* copy over a byte at a time, leave it up to caching + * to make this efficient + */ + + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) + { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) + { + return res; + } + + /* keep our reference to the rcache in sync */ + + if (lfs->rcache.block != 0xffffffff) + { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + /* write out what we have */ + + while (true) + { + int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + return err; + } + + break; + relocate: + err = lfs_file_relocate(lfs, file); + if (err) + { + return err; + } + } + + /* actual file updates */ + + file->head = file->block; + file->size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +/* Filesystem operations */ + +static void lfs_deinit(FAR lfs_t *lfs) +{ + /* free allocated memory */ + + if (!lfs->cfg->read_buffer) + { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) + { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) + { + lfs_free(lfs->free.buffer); + } +} + +static int lfs_init(FAR lfs_t *lfs, FAR const struct lfs_config *cfg) +{ + lfs->cfg = cfg; + + /* setup read cache */ + + if (lfs->cfg->read_buffer) + { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } + else + { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) + { + goto cleanup; + } + } + + /* setup program cache */ + + if (lfs->cfg->prog_buffer) + { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } + else + { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) + { + goto cleanup; + } + } + + /* zero to avoid information leaks */ + + lfs_cache_zero(lfs, &lfs->pcache); + lfs_cache_drop(lfs, &lfs->rcache); + + /* setup lookahead, round down to nearest 32-bits */ + + LFS_ASSERT(lfs->cfg->lookahead % 32 == 0); + LFS_ASSERT(lfs->cfg->lookahead > 0); + if (lfs->cfg->lookahead_buffer) + { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } + else + { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead / 8); + if (!lfs->free.buffer) + { + goto cleanup; + } + } + + /* check that program and read sizes are multiples of the block size */ + + LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0); + + /* check that the block size is large enough to fit ctz pointers */ + + LFS_ASSERT(4 * lfs_npw2(0xffffffff / (lfs->cfg->block_size - 2 * 4)) <= + lfs->cfg->block_size); + + /* setup default state */ + + lfs->root[0] = 0xffffffff; + lfs->root[1] = 0xffffffff; + lfs->files = NULL; + lfs->dirs = NULL; + lfs->deorphaned = false; + lfs->moving = false; + + return 0; + +cleanup: + lfs_deinit(lfs); + return LFS_ERR_NOMEM; +} + +static int lfs_pred(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], + FAR lfs_dir_t *pdir) +{ + int err; + + if (lfs_pairisnull(lfs->root)) + { + return 0; + } + + /* iterate over all directory directory entries */ + + err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1}); + if (err) + { + return err; + } + + while (!lfs_pairisnull(pdir->d.tail)) + { + if (lfs_paircmp(pdir->d.tail, dir) == 0) + { + return true; + } + + err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); + if (err) + { + return err; + } + } + + return false; +} + +static int lfs_parent(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], + FAR lfs_dir_t *parent, FAR lfs_entry_t *entry) +{ + if (lfs_pairisnull(lfs->root)) + { + return 0; + } + + parent->d.tail[0] = 0; + parent->d.tail[1] = 1; + + /* iterate over all directory directory entries */ + + while (!lfs_pairisnull(parent->d.tail)) + { + int err = lfs_dir_fetch(lfs, parent, parent->d.tail); + if (err) + { + return err; + } + + while (true) + { + err = lfs_dir_next(lfs, parent, entry); + if (err && err != LFS_ERR_NOENT) + { + return err; + } + + if (err == LFS_ERR_NOENT) + { + break; + } + + if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && + lfs_paircmp(entry->d.u.dir, dir) == 0) + { + return true; + } + } + } + + return false; +} + +static int lfs_moved(FAR lfs_t *lfs, FAR const void *e) +{ + lfs_dir_t cwd; + lfs_entry_t entry; + int err; + + if (lfs_pairisnull(lfs->root)) + { + return 0; + } + + /* skip superblock */ + + err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) + { + return err; + } + + /* iterate over all directory directory entries */ + + while (!lfs_pairisnull(cwd.d.tail)) + { + err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) + { + return err; + } + + while (true) + { + 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(FAR lfs_t *lfs, FAR const lfs_block_t oldpair[2], + FAR const lfs_block_t newpair[2]) +{ + lfs_dir_t parent; + lfs_entry_t entry; + int res; + + /* find parent */ + + res = lfs_parent(lfs, oldpair, &parent, &entry); + if (res < 0) + { + return res; + } + + if (res) + { + int err; + + /* update disk, this creates a desync */ + + entry.d.u.dir[0] = newpair[0]; + entry.d.u.dir[1] = newpair[1]; + + err = lfs_dir_update(lfs, &parent, &entry, NULL); + if (err) + { + return err; + } + + /* update internal root */ + + if (lfs_paircmp(oldpair, lfs->root) == 0) + { + LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32, newpair[0], + newpair[1]); + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + /* clean up bad block, which should now be a desync */ + + return lfs_deorphan(lfs); + } + + /* find pred */ + + res = lfs_pred(lfs, oldpair, &parent); + if (res < 0) + { + return res; + } + + if (res) + { + /* just replace bad pair, no desync can occur */ + + parent.d.tail[0] = newpair[0]; + parent.d.tail[1] = newpair[1]; + + return lfs_dir_commit(lfs, &parent, NULL, 0); + } + + /* couldn't find dir, must be new */ + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* Top level directory operations */ + +int lfs_mkdir(FAR lfs_t *lfs, FAR const char *path) +{ + lfs_dir_t cwd; + lfs_dir_t dir; + lfs_entry_t entry; + int err; + + /* deorphan if we haven't yet, needed at most once after poweron */ + + if (!lfs->deorphaned) + { + err = lfs_deorphan(lfs); + if (err) + { + return err; + } + } + + /* fetch parent directory */ + + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) + { + return err ? err : LFS_ERR_EXIST; + } + + /* build up new directory */ + + lfs_alloc_ack(lfs); + + err = lfs_dir_alloc(lfs, &dir); + if (err) + { + return err; + } + + dir.d.tail[0] = cwd.d.tail[0]; + dir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &dir, NULL, 0); + if (err) + { + return err; + } + + entry.d.type = LFS_TYPE_DIR; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.dir[0] = dir.pair[0]; + entry.d.u.dir[1] = dir.pair[1]; + + cwd.d.tail[0] = dir.pair[0]; + cwd.d.tail[1] = dir.pair[1]; + + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) + { + return err; + } + + lfs_alloc_ack(lfs); + return 0; +} + +int lfs_dir_open(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR const char *path) +{ + lfs_entry_t entry; + int err; + + dir->pair[0] = lfs->root[0]; + dir->pair[1] = lfs->root[1]; + + err = lfs_dir_find(lfs, dir, &entry, &path); + if (err) + { + return err; + } + else if (entry.d.type != LFS_TYPE_DIR) + { + return LFS_ERR_NOTDIR; + } + + err = lfs_dir_fetch(lfs, dir, entry.d.u.dir); + if (err) + { + return err; + } + + /* setup head dir + * special offset for '.' and '..' + */ + + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + + /* add to list of directories */ + + dir->next = lfs->dirs; + lfs->dirs = dir; + + return 0; +} + +int lfs_dir_close(FAR lfs_t *lfs, FAR lfs_dir_t *dir) +{ + FAR lfs_dir_t **p; + + /* remove from list of directories */ + + for (p = &lfs->dirs; *p; p = &(*p)->next) + { + if (*p == dir) + { + *p = dir->next; + break; + } + } + + return 0; +} + +int lfs_dir_read(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR struct lfs_info *info) +{ + lfs_entry_t entry; + + memset(info, 0, sizeof(*info)); + + /* special offset for '.' and '..' */ + + if (dir->pos == sizeof(dir->d) - 2) + { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return 1; + } + else if (dir->pos == sizeof(dir->d) - 1) + { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return 1; + } + + while (true) + { + int err = lfs_dir_next(lfs, dir, &entry); + if (err) + { + return (err == LFS_ERR_NOENT) ? 0 : err; + } + + 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; + if (info->type == LFS_TYPE_REG) + { + info->size = entry.d.u.file.size; + } + + int err = lfs_bd_read(lfs, dir->pair[0], + entry.off + 4 + entry.d.elen + entry.d.alen, info->name, + entry.d.nlen); + if (err) + { + return err; + } + + return 1; +} + +int lfs_dir_seek(FAR lfs_t *lfs, FAR lfs_dir_t *dir, lfs_off_t off) +{ + int err; + + /* simply walk from head dir */ + + err = lfs_dir_rewind(lfs, dir); + if (err) + { + return err; + } + + dir->pos = off; + + while (off > (0x7fffffff & dir->d.size)) + { + off -= 0x7fffffff & dir->d.size; + if (!(0x80000000 & dir->d.size)) + { + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) + { + return err; + } + } + + dir->off = off; + return 0; +} + +lfs_soff_t lfs_dir_tell(FAR lfs_t *lfs, FAR lfs_dir_t *dir) +{ + return dir->pos; +} + +int lfs_dir_rewind(FAR lfs_t *lfs, FAR lfs_dir_t *dir) +{ + int err; + + /* reload the head dir */ + + err = lfs_dir_fetch(lfs, dir, dir->head); + if (err) + { + return err; + } + + dir->pair[0] = dir->head[0]; + dir->pair[1] = dir->head[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + +/* Top level file operations */ + +int lfs_file_opencfg(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const char *path, int flags, + FAR const struct lfs_file_config *cfg) +{ + lfs_dir_t cwd; + lfs_entry_t entry; + int err; + + /* deorphan if we haven't yet, needed at most once after poweron */ + + if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) + { + err = lfs_deorphan(lfs); + if (err) + { + return err; + } + } + + /* allocate entry for file if it doesn't exist */ + + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) + { + return err; + } + + if (err == LFS_ERR_NOENT) + { + if (!(flags & LFS_O_CREAT)) + { + return LFS_ERR_NOENT; + } + + /* create entry to remember name */ + + entry.d.type = LFS_TYPE_REG; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.file.head = 0xffffffff; + entry.d.u.file.size = 0; + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) + { + return err; + } + } + else if (entry.d.type == LFS_TYPE_DIR) + { + return LFS_ERR_ISDIR; + } + else if (flags & LFS_O_EXCL) + { + return LFS_ERR_EXIST; + } + + /* setup file struct */ + + file->cfg = cfg; + file->pair[0] = cwd.pair[0]; + file->pair[1] = cwd.pair[1]; + file->poff = entry.off; + file->head = entry.d.u.file.head; + file->size = entry.d.u.file.size; + file->flags = flags; + file->pos = 0; + + if (flags & LFS_O_TRUNC) + { + if (file->size != 0) + { + file->flags |= LFS_F_DIRTY; + } + + file->head = 0xffffffff; + file->size = 0; + } + + /* allocate buffer if needed */ + + file->cache.block = 0xffffffff; + if (file->cfg && file->cfg->buffer) + { + file->cache.buffer = file->cfg->buffer; + } + else if (lfs->cfg->file_buffer) + { + if (lfs->files) + { + /* already in use */ + + return LFS_ERR_NOMEM; + } + + file->cache.buffer = lfs->cfg->file_buffer; + } + else if ((file->flags & 3) == LFS_O_RDONLY) + { + file->cache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!file->cache.buffer) + { + return LFS_ERR_NOMEM; + } + } + else + { + file->cache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!file->cache.buffer) + { + return LFS_ERR_NOMEM; + } + } + + /* zero to avoid information leak */ + + lfs_cache_drop(lfs, &file->cache); + if ((file->flags & 3) != LFS_O_RDONLY) + { + lfs_cache_zero(lfs, &file->cache); + } + + /* add to list of files */ + + file->next = lfs->files; + lfs->files = file; + + return 0; +} + +int lfs_file_open(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const char *path, int flags) +{ + return lfs_file_opencfg(lfs, file, path, flags, NULL); +} + +int lfs_file_close(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + FAR lfs_file_t **p; + int err; + + err = lfs_file_sync(lfs, file); + + /* remove from list of files */ + + for (p = &lfs->files; *p; p = &(*p)->next) + { + if (*p == file) + { + *p = file->next; + break; + } + } + + /* clean up memory */ + + if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) + { + lfs_free(file->cache.buffer); + } + + return err; +} + +int lfs_file_sync(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + int err = lfs_file_flush(lfs, file); + if (err) + { + return err; + } + + if ((file->flags & LFS_F_DIRTY) && !(file->flags & LFS_F_ERRED) && + !lfs_pairisnull(file->pair)) + { + lfs_dir_t cwd; + lfs_entry_t entry = {.off = file->poff}; + + /* update dir entry */ + + err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) + { + return err; + } + + err = lfs_bd_read(lfs, cwd.pair[0], entry.off, &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) + { + return err; + } + + LFS_ASSERT(entry.d.type == LFS_TYPE_REG); + entry.d.u.file.head = file->head; + entry.d.u.file.size = file->size; + + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) + { + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} + +lfs_ssize_t lfs_file_read(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR void *buffer, lfs_size_t size) +{ + FAR uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_WRONLY) + { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_WRITING) + { + /* flush out any writes */ + + int err = lfs_file_flush(lfs, file); + if (err) + { + return err; + } + } + + if (file->pos >= file->size) + { + /* eof if past end */ + + return 0; + } + + size = lfs_min(size, file->size - file->pos); + nsize = size; + + while (nsize > 0) + { + /* check if we need a new block */ + + if (!(file->flags & LFS_F_READING) || file->off == lfs->cfg->block_size) + { + int err = + lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, + file->pos, &file->block, &file->off); + if (err) + { + return err; + } + + file->flags |= LFS_F_READING; + } + + /* read as much as we can in current block */ + + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + int err = lfs_cache_read(lfs, &file->cache, NULL, file->block, file->off, + data, diff); + if (err) + { + return err; + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +lfs_ssize_t lfs_file_write(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const void *buffer, lfs_size_t size) +{ + FAR const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_RDONLY) + { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_READING) + { + /* drop any reads */ + + int err = lfs_file_flush(lfs, file); + if (err) + { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->size) + { + file->pos = file->size; + } + + if (file->pos + size > LFS_FILE_MAX) + { + /* larger than file limit? */ + + return LFS_ERR_FBIG; + } + + 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) + { + lfs_size_t diff; + + /* check if we need a new block */ + + if (!(file->flags & LFS_F_WRITING) || file->off == lfs->cfg->block_size) + { + int err; + + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) + { + /* find out which block we're extending from */ + + err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, + file->size, file->pos - 1, &file->block, + &file->off); + if (err) + { + file->flags |= LFS_F_ERRED; + return err; + } + + /* mark cache as dirty since we may have read data into it */ + + lfs_cache_zero(lfs, &file->cache); + } + + /* extend file with new blocks */ + + lfs_alloc_ack(lfs); + err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, file->block, + file->pos, &file->block, &file->off); + if (err) + { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags |= LFS_F_WRITING; + } + + /* program as much as we can in current block */ + + diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) + { + int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, file->block, + file->off, data, diff); + if (err) + { + if (err == LFS_ERR_CORRUPT) + { + goto relocate; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; + relocate: + err = lfs_file_relocate(lfs, file); + if (err) + { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + return size; +} + +lfs_soff_t lfs_file_seek(FAR lfs_t *lfs, FAR lfs_file_t *file, + lfs_soff_t off, int whence) +{ + lfs_soff_t npos; + int err; + + /* write out everything beforehand, may be noop if rdonly */ + + err = lfs_file_flush(lfs, file); + if (err) + { + return err; + } + + /* find new pos */ + + npos = file->pos; + if (whence == LFS_SEEK_SET) + { + npos = off; + } + else if (whence == LFS_SEEK_CUR) + { + npos = file->pos + off; + } + else if (whence == LFS_SEEK_END) + { + npos = file->size + off; + } + + if (npos < 0 || npos > LFS_FILE_MAX) + { + /* file position out of range */ + + return LFS_ERR_INVAL; + } + + /* update pos */ + + file->pos = npos; + return npos; +} + +int lfs_file_truncate(FAR lfs_t *lfs, FAR lfs_file_t *file, lfs_off_t size) +{ + lfs_off_t oldsize; + + if ((file->flags & 3) == LFS_O_RDONLY) + { + return LFS_ERR_BADF; + } + + oldsize = lfs_file_size(lfs, file); + if (size < oldsize) + { + /* need to flush since directly changing metadata */ + + int err = lfs_file_flush(lfs, file); + if (err) + { + return err; + } + + /* lookup new head in ctz skip list */ + + err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, size, + &file->head, &(lfs_off_t){0}); + if (err) + { + return err; + } + + file->size = size; + file->flags |= LFS_F_DIRTY; + } + else if (size > oldsize) + { + int err; + + lfs_off_t pos = file->pos; + + /* flush+seek if not already at end */ + + if (file->pos != oldsize) + { + err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (err < 0) + { + return err; + } + } + + /* fill with zeros */ + + while (file->pos < size) + { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) + { + return res; + } + } + + /* restore pos */ + + err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (err < 0) + { + return err; + } + } + + return 0; +} + +lfs_soff_t lfs_file_tell(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + return file->pos; +} + +int lfs_file_rewind(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) + { + return res; + } + + return 0; +} + +lfs_soff_t lfs_file_size(FAR lfs_t *lfs, FAR lfs_file_t *file) +{ + if (file->flags & LFS_F_WRITING) + { + return lfs_max(file->pos, file->size); + } + else + { + return file->size; + } +} + +/* General fs operations */ + +int lfs_stat(FAR lfs_t *lfs, FAR const char *path, + FAR struct lfs_info *info) +{ + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) + { + return err; + } + + memset(info, 0, sizeof(*info)); + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) + { + info->size = entry.d.u.file.size; + } + + if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) + { + strcpy(info->name, "/"); + } + else + { + err = lfs_bd_read(lfs, cwd.pair[0], + entry.off + 4 + entry.d.elen + entry.d.alen, info->name, + entry.d.nlen); + if (err) + { + return err; + } + } + + return 0; +} + +int lfs_remove(FAR lfs_t *lfs, FAR const char *path) +{ + lfs_dir_t cwd; + lfs_entry_t entry; + int err; + + /* deorphan if we haven't yet, needed at most once after poweron */ + + if (!lfs->deorphaned) + { + err = lfs_deorphan(lfs); + if (err) + { + return err; + } + } + + err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) + { + return err; + } + + lfs_dir_t dir; + if (entry.d.type == LFS_TYPE_DIR) + { + /* must be empty before removal, checking size + * without masking top bit checks for any case where + * dir is not empty + */ + + err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); + if (err) + { + return err; + } + else if (dir.d.size != sizeof(dir.d) + 4) + { + return LFS_ERR_NOTEMPTY; + } + } + + /* remove the entry */ + + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) + { + return err; + } + + /* if we were a directory, find pred, replace tail */ + + if (entry.d.type == LFS_TYPE_DIR) + { + int res = lfs_pred(lfs, dir.pair, &cwd); + if (res < 0) + { + return res; + } + + LFS_ASSERT(res); /* must have pred */ + + cwd.d.tail[0] = dir.d.tail[0]; + cwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &cwd, NULL, 0); + if (err) + { + return err; + } + } + + return 0; +} + +int lfs_rename(FAR lfs_t *lfs, FAR const char *oldpath, + FAR const char *newpath) +{ + lfs_dir_t oldcwd; + lfs_dir_t newcwd; + lfs_dir_t dir; + lfs_entry_t oldentry; + lfs_entry_t preventry; + lfs_entry_t newentry; + bool prevexists; + int err; + + /* deorphan if we haven't yet, needed at most once after poweron */ + + if (!lfs->deorphaned) + { + err = lfs_deorphan(lfs); + if (err) + { + return err; + } + } + + /* find old entry */ + + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &(const char *){oldpath}); + if (err) + { + return err; + } + + /* mark as moving */ + + oldentry.d.type |= 0x80; + err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + if (err) + { + return err; + } + + /* allocate new entry */ + + err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); + if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) + { + return err; + } + + /* must have same type */ + + prevexists = (err != LFS_ERR_NOENT); + if (prevexists && preventry.d.type != (0x7f & oldentry.d.type)) + { + return LFS_ERR_ISDIR; + } + + if (prevexists && preventry.d.type == LFS_TYPE_DIR) + { + /* must be empty before removal, checking size + * without masking top bit checks for any case where + * dir is not empty + */ + + err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); + if (err) + { + return err; + } + else if (dir.d.size != sizeof(dir.d) + 4) + { + return LFS_ERR_NOTEMPTY; + } + } + + /* move to new location */ + + newentry = preventry; + newentry.d = oldentry.d; + newentry.d.type &= ~0x80; + newentry.d.nlen = strlen(newpath); + + if (prevexists) + { + err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); + if (err) + { + return err; + } + } + else + { + err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); + if (err) + { + return err; + } + } + + /* fetch old pair again in case dir block changed */ + + lfs->moving = true; + err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) + { + return err; + } + lfs->moving = false; + + /* remove old entry */ + + err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + if (err) + { + return err; + } + + /* if we were a directory, find pred, replace tail */ + + if (prevexists && preventry.d.type == LFS_TYPE_DIR) + { + int res = lfs_pred(lfs, dir.pair, &newcwd); + if (res < 0) + { + return res; + } + + LFS_ASSERT(res); /* must have pred */ + + newcwd.d.tail[0] = dir.d.tail[0]; + newcwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &newcwd, NULL, 0); + if (err) + { + return err; + } + } + + return 0; +} + +int lfs_format(FAR lfs_t *lfs, FAR const struct lfs_config *cfg) +{ + lfs_superblock_t superblock; + bool valid; + int err = 0; + + if (true) + { + lfs_dir_t superdir; + lfs_dir_t root; + + err = lfs_init(lfs, cfg); + if (err) + { + return err; + } + + /* create free lookahead */ + + memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8); + lfs->free.off = 0; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + /* create superblock dir */ + + err = lfs_dir_alloc(lfs, &superdir); + if (err) + { + goto cleanup; + } + + /* write root directory */ + + err = lfs_dir_alloc(lfs, &root); + if (err) + { + goto cleanup; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) + { + goto cleanup; + } + + lfs->root[0] = root.pair[0]; + lfs->root[1] = root.pair[1]; + + /* write superblocks */ + + superblock.off = sizeof(superdir.d); + superblock.d.type = LFS_TYPE_SUPERBLOCK; + superblock.d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4; + superblock.d.nlen = sizeof(superblock.d.magic); + superblock.d.version = LFS_DISK_VERSION; + superblock.d.block_size = lfs->cfg->block_size; + superblock.d.block_count = lfs->cfg->block_count; + superblock.d.root[0] = lfs->root[0]; + superblock.d.root[0] = lfs->root[1]; + + memcpy(superblock.d.magic, "littlefs", 8); + + superdir.d.tail[0] = root.pair[0]; + superdir.d.tail[1] = root.pair[1]; + superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; + + /* write both pairs to be safe */ + + lfs_superblock_tole32(&superblock.d); + valid = false; + for (int i = 0; i < 2; i++) + { + err = lfs_dir_commit( + lfs, &superdir, + (struct lfs_region[]){{sizeof(superdir.d), sizeof(superblock.d), + &superblock.d, sizeof(superblock.d)}}, + 1); + if (err && err != LFS_ERR_CORRUPT) + { + goto cleanup; + } + + valid = valid || !err; + } + + if (!valid) + { + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + /* sanity check that fetch works */ + + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) + { + goto cleanup; + } + + lfs_alloc_ack(lfs); + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +int lfs_mount(FAR lfs_t *lfs, FAR const struct lfs_config *cfg) +{ + int err = 0; + if (true) + { + lfs_dir_t dir; + lfs_superblock_t superblock; + uint16_t major_version; + uint16_t minor_version; + + err = lfs_init(lfs, cfg); + if (err) + { + return err; + } + + /* setup free lookahead */ + + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + /* load superblock */ + + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) + { + goto cleanup; + } + + if (!err) + { + err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), &superblock.d, + sizeof(superblock.d)); + lfs_superblock_fromle32(&superblock.d); + if (err) + { + goto cleanup; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) + { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + major_version = (0xffff & (superblock.d.version >> 16)); + minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) + { + LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + + lfs_deinit(lfs); + return err; +} + +int lfs_unmount(FAR lfs_t *lfs) +{ + lfs_deinit(lfs); + return 0; +} + +/* Littlefs specific operations */ + +int lfs_traverse(FAR lfs_t *lfs, CODE int (*cb)(void *, lfs_block_t), + FAR void *data) +{ + lfs_dir_t dir; + lfs_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + FAR lfs_file_t *f; + + if (lfs_pairisnull(lfs->root)) + { + return 0; + } + + /* iterate over metadata pairs */ + + while (true) + { + int err; + + for (int i = 0; i < 2; i++) + { + err = cb(data, cwd[i]); + if (err) + { + return err; + } + } + + err = lfs_dir_fetch(lfs, &dir, cwd); + if (err) + { + return err; + } + + /* iterate over contents */ + + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size) - 4) + { + err = lfs_bd_read(lfs, dir.pair[0], dir.off, &entry.d, + sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) + { + return err; + } + + dir.off += lfs_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) + { + err = + lfs_ctz_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, + entry.d.u.file.size, cb, data); + if (err) + { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pairisnull(cwd)) + { + break; + } + } + + /* iterate over any open files */ + + for (f = lfs->files; f; f = f->next) + { + if (f->flags & LFS_F_DIRTY) + { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->head, + f->size, cb, data); + if (err) + { + return err; + } + } + + if (f->flags & LFS_F_WRITING) + { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->block, + f->pos, cb, data); + if (err) + { + return err; + } + } + } + + return 0; +} + +int lfs_deorphan(FAR lfs_t *lfs) +{ + lfs_dir_t pdir = {.d.size = 0x80000000}; + lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1}; + lfs_size_t i; + int err; + + lfs->deorphaned = true; + + if (lfs_pairisnull(lfs->root)) + { + return 0; + } + + /* iterate over all directory directory entries */ + + for (i = 0; i < lfs->cfg->block_count; i++) + { + lfs_entry_t entry; + + if (lfs_pairisnull(cwd.d.tail)) + { + return 0; + } + + err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) + { + return err; + } + + /* check head blocks for orphans */ + + if (!(0x80000000 & pdir.d.size)) + { + lfs_dir_t parent; + int res; + + /* check if we have a parent */ + + res = lfs_parent(lfs, pdir.d.tail, &parent, &entry); + if (res < 0) + { + return res; + } + + if (!res) + { + /* we are an orphan */ + + LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32, pdir.d.tail[0], + pdir.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) + { + return err; + } + + return 0; + } + + if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) + { + /* we have desynced */ + + LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32, 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]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) + { + return err; + } + + return 0; + } + } + + /* check entries for moves */ + + while (true) + { + 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 %" PRIu32 " %" PRIu32, entry.d.u.dir[0], + entry.d.u.dir[1]); + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) + { + return err; + } + } + else + { + LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + entry.d.type &= ~0x80; + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) + { + return err; + } + } + } + } + + memcpy(&pdir, &cwd, sizeof(pdir)); + } + + /* If we reached here, we have more directory pairs than blocks in the + * filesystem... So something must be horribly wrong + */ + + return LFS_ERR_CORRUPT; +} diff --git a/fs/littlefs/lfs.h b/fs/littlefs/lfs.h new file mode 100644 index 0000000000..02166856c3 --- /dev/null +++ b/fs/littlefs/lfs.h @@ -0,0 +1,673 @@ +/**************************************************************************** + * fs/littlefs/lfs.h + * + * This file is a part of NuttX: + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * + * Ported by: + * + * Copyright (C) 2019 Pinecone Inc. All rights reserved. + * Author: lihaichen + * + * This port derives from ARM mbed logic which has a compatible 3-clause + * BSD license: + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __FS_LITTLEFS_LFS_H +#define __FS_LITTLEFS_LFS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Version info */ + +/* Software library version + * Major (top-nibble), incremented on backwards incompatible changes + * Minor (bottom-nibble), incremented on feature additions + */ + +#define LFS_VERSION 0x00010007 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +/* Version of On-disk data structures + * Major (top-nibble), incremented on backwards incompatible changes + * Minor (bottom-nibble), incremented on feature additions + */ + +#define LFS_DISK_VERSION 0x00010001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + +/* Max name size in bytes */ + +#ifndef LFS_NAME_MAX +# define LFS_NAME_MAX 255 +#endif + +/* Max file size in bytes */ + +#ifndef LFS_FILE_MAX +# define LFS_FILE_MAX 2147483647 +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +/* Possible error codes, these are negative to allow + * valid positive return values + */ + +enum lfs_error +{ + LFS_ERR_OK = 0, /* No error */ + LFS_ERR_IO = -5, /* Error during device operation */ + LFS_ERR_CORRUPT = -52, /* Corrupted */ + LFS_ERR_NOENT = -2, /* No directory entry */ + LFS_ERR_EXIST = -17, /* Entry already exists */ + LFS_ERR_NOTDIR = -20, /* Entry is not a dir */ + LFS_ERR_ISDIR = -21, /* Entry is a dir */ + LFS_ERR_NOTEMPTY = -39, /* Dir is not empty */ + LFS_ERR_BADF = -9, /* Bad file number */ + LFS_ERR_FBIG = -27, /* File too large */ + LFS_ERR_INVAL = -22, /* Invalid parameter */ + LFS_ERR_NOSPC = -28, /* No space left on device */ + LFS_ERR_NOMEM = -12, /* No more memory available */ +}; + +/* File types */ + +enum lfs_type +{ + LFS_TYPE_REG = 0x11, + LFS_TYPE_DIR = 0x22, + LFS_TYPE_SUPERBLOCK = 0x2e, +}; + +/* File open flags */ + +enum lfs_open_flags +{ + /* open flags */ + + LFS_O_RDONLY = 1, /* Open a file as read only */ + LFS_O_WRONLY = 2, /* Open a file as write only */ + LFS_O_RDWR = 3, /* Open a file as read and write */ + LFS_O_CREAT = 0x0100, /* Create a file if it does not exist */ + LFS_O_EXCL = 0x0200, /* Fail if a file already exists */ + LFS_O_TRUNC = 0x0400, /* Truncate the existing file to zero size */ + LFS_O_APPEND = 0x0800, /* Move to end of file on every write */ + + /* internally used flags */ + + LFS_F_DIRTY = 0x10000, /* File does not match storage */ + LFS_F_WRITING = 0x20000, /* File has been written since last flush */ + LFS_F_READING = 0x40000, /* File has been read since last flush */ + LFS_F_ERRED = 0x80000, /* An error occured during write */ +}; + +/* File seek flags */ + +enum lfs_whence_flags +{ + LFS_SEEK_SET = 0, /* Seek relative to an absolute position */ + LFS_SEEK_CUR = 1, /* Seek relative to the current file position */ + LFS_SEEK_END = 2, /* Seek relative to the end of the file */ +}; + +/* Configuration provided during initialization of the littlefs */ + +struct lfs_config +{ + /* Opaque user provided context that can be used to pass + * information to the block device operations + */ + + FAR void *context; + + /* Read a region in a block. Negative error codes are propagated + * to the user. + */ + + CODE int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, FAR void *buffer, lfs_size_t size); + + /* Program a region in a block. The block must have previously + * been erased. Negative error codes are propagated to the user. + * May return LFS_ERR_CORRUPT if the block should be considered bad. + */ + + CODE int (*prog)(FAR const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + /* Erase a block. A block must be erased before being programmed. + * The state of an erased block is undefined. Negative error codes + * are propagated to the user. + * May return LFS_ERR_CORRUPT if the block should be considered bad. + */ + + CODE int (*erase)(FAR const struct lfs_config *c, lfs_block_t block); + + /* Sync the state of the underlying block device. Negative error codes + * are propagated to the user. + */ + + CODE int (*sync)(FAR const struct lfs_config *c); + + /* Minimum size of a block read. This determines the size of read buffers. + * This may be larger than the physical read size to improve performance + * by caching more of the block device. + */ + + lfs_size_t read_size; + + /* Minimum size of a block program. This determines the size of program + * buffers. This may be larger than the physical program size to improve + * performance by caching more of the block device. + * Must be a multiple of the read size. + */ + + lfs_size_t prog_size; + + /* Size of an erasable block. This does not impact ram consumption and + * may be larger than the physical erase size. However, this should be + * kept small as each file currently takes up an entire block. + * Must be a multiple of the program size. + */ + + lfs_size_t block_size; + + /* Number of erasable blocks on the device. */ + + lfs_size_t block_count; + + /* Number of blocks to lookahead during block allocation. A larger + * lookahead reduces the number of passes required to allocate a block. + * The lookahead buffer requires only 1 bit per block so it can be quite + * large with little ram impact. Should be a multiple of 32. + */ + + lfs_size_t lookahead; + + /* Optional, statically allocated read buffer. Must be read sized. */ + + FAR void *read_buffer; + + /* Optional, statically allocated program buffer. Must be program sized. */ + + FAR void *prog_buffer; + + /* Optional, statically allocated lookahead buffer. Must be 1 bit per + * lookahead block. + */ + + FAR void *lookahead_buffer; + + /* Optional, statically allocated buffer for files. Must be program sized. + * If enabled, only one file may be opened at a time. + */ + + FAR void *file_buffer; +}; + +/* Optional configuration provided during lfs_file_opencfg */ + +struct lfs_file_config +{ + /* Optional, statically allocated buffer for files. Must be program sized. + * If NULL, malloc will be used by default. + */ + + FAR void *buffer; +}; + +/* File info structure */ + +struct lfs_info +{ + /* Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR */ + + uint8_t type; + + /* Size of the file, only valid for REG files */ + + lfs_size_t size; + + /* Name of the file stored as a null-terminated string */ + + char name[LFS_NAME_MAX+1]; +}; + + +/* littlefs data structures */ + +typedef struct lfs_entry +{ + lfs_off_t off; + + struct lfs_disk_entry + { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union + { + struct + { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs_entry_t; + +typedef struct lfs_cache +{ + lfs_block_t block; + lfs_off_t off; + FAR uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_file +{ + struct lfs_file *next; + lfs_block_t pair[2]; + lfs_off_t poff; + + lfs_block_t head; + lfs_size_t size; + + FAR const struct lfs_file_config *cfg; + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; +} lfs_file_t; + +typedef struct lfs_dir +{ + struct lfs_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs_disk_dir + { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs_dir_t; + +typedef struct lfs_superblock +{ + lfs_off_t off; + + struct lfs_disk_superblock + { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs_superblock_t; + +typedef struct lfs_free +{ + lfs_block_t off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + FAR uint32_t *buffer; +} lfs_free_t; + +/* The littlefs type */ + +typedef struct lfs +{ + FAR const struct lfs_config *cfg; + + lfs_block_t root[2]; + FAR lfs_file_t *files; + lfs_dir_t *dirs; + + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_free_t free; + bool deorphaned; + bool moving; +} lfs_t; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/* Filesystem functions */ + +/* Format a block device with the littlefs + * + * Requires a littlefs object and config struct. This clobbers the littlefs + * object, and does not leave the filesystem mounted. The config struct must + * be zeroed for defaults and backwards compatibility. + * + * Returns a negative error code on failure. + */ + +int lfs_format(FAR lfs_t *lfs, FAR const struct lfs_config *config); + +/* Mounts a littlefs + * + * Requires a littlefs object and config struct. Multiple filesystems + * may be mounted simultaneously with multiple littlefs objects. Both + * lfs and config must be allocated while mounted. The config struct must + * be zeroed for defaults and backwards compatibility. + * + * Returns a negative error code on failure. + */ + +int lfs_mount(FAR lfs_t *lfs, FAR const struct lfs_config *config); + +/* Unmounts a littlefs + * + * Does nothing besides releasing any allocated resources. + * Returns a negative error code on failure. + */ + +int lfs_unmount(FAR lfs_t *lfs); + +/* General operations */ + +/* Removes a file or directory + * + * If removing a directory, the directory must be empty. + * Returns a negative error code on failure. + */ + +int lfs_remove(FAR lfs_t *lfs, FAR const char *path); + +/* Rename or move a file or directory + * + * If the destination exists, it must match the source in type. + * If the destination is a directory, the directory must be empty. + * + * Returns a negative error code on failure. + */ + +int lfs_rename(FAR lfs_t *lfs, FAR const char *oldpath, FAR + const char *newpath); + +/* Find info about a file or directory + * + * Fills out the info structure, based on the specified file or directory. + * Returns a negative error code on failure. + */ + +int lfs_stat(FAR lfs_t *lfs, FAR const char *path, + FAR struct lfs_info *info); + +/* File operations */ + +/* Open a file + * + * The mode that the file is opened in is determined by the flags, which + * are values from the enum lfs_open_flags that are bitwise-ored together. + * + * Returns a negative error code on failure. + */ + +int lfs_file_open(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const char *path, int flags); + +/* Open a file with extra configuration + * + * The mode that the file is opened in is determined by the flags, which + * are values from the enum lfs_open_flags that are bitwise-ored together. + * + * The config struct provides additional config options per file as described + * above. The config struct must be allocated while the file is open, and the + * config struct must be zeroed for defaults and backwards compatibility. + * + * Returns a negative error code on failure. + */ + +int lfs_file_opencfg(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const char *path, int flags, + FAR const struct lfs_file_config *config); + +/* Close a file + * + * Any pending writes are written out to storage as though + * sync had been called and releases any allocated resources. + * + * Returns a negative error code on failure. + */ + +int lfs_file_close(FAR lfs_t *lfs, FAR lfs_file_t *file); + +/* Synchronize a file on storage + * + * Any pending writes are written out to storage. + * Returns a negative error code on failure. + */ + +int lfs_file_sync(FAR lfs_t *lfs, FAR lfs_file_t *file); + +/* Read data from file + * + * Takes a buffer and size indicating where to store the read data. + * Returns the number of bytes read, or a negative error code on failure. + */ + +lfs_ssize_t lfs_file_read(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR void *buffer, lfs_size_t size); + +/* Write data to file + * + * Takes a buffer and size indicating the data to write. The file will not + * actually be updated on the storage until either sync or close is called. + * + * Returns the number of bytes written, or a negative error code on failure. + */ + +lfs_ssize_t lfs_file_write(FAR lfs_t *lfs, FAR lfs_file_t *file, + FAR const void *buffer, lfs_size_t size); + +/* Change the position of the file + * + * The change in position is determined by the offset and whence flag. + * Returns the old position of the file, or a negative error code on failure. + */ + +lfs_soff_t lfs_file_seek(FAR lfs_t *lfs, FAR lfs_file_t *file, + lfs_soff_t off, int whence); + +/* Truncates the size of the file to the specified size + * + * Returns a negative error code on failure. + */ + +int lfs_file_truncate(FAR FAR lfs_t *lfs, FAR lfs_file_t *file, lfs_off_t size); + +/* Return the position of the file + * + * Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) + * Returns the position of the file, or a negative error code on failure. + */ + +lfs_soff_t lfs_file_tell(FAR lfs_t *lfs, FAR lfs_file_t *file); + +/* Change the position of the file to the beginning of the file + * + * Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) + * Returns a negative error code on failure. + */ + +int lfs_file_rewind(FAR lfs_t *lfs, FAR lfs_file_t *file); + +/* Return the size of the file + * + * Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) + * Returns the size of the file, or a negative error code on failure. + */ + +lfs_soff_t lfs_file_size(FAR lfs_t *lfs, FAR lfs_file_t *file); + +/* Directory operations */ + +/* Create a directory + * + * Returns a negative error code on failure. + */ + +int lfs_mkdir(FAR lfs_t *lfs, FAR const char *path); + +/* Open a directory + * + * Once open a directory can be used with read to iterate over files. + * Returns a negative error code on failure. + */ + +int lfs_dir_open(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR const char *path); + +/* Close a directory + * + * Releases any allocated resources. + * Returns a negative error code on failure. + */ + +int lfs_dir_close(FAR lfs_t *lfs, FAR lfs_dir_t *dir); + +/* Read an entry in the directory + * + * Fills out the info structure, based on the specified file or directory. + * Returns a negative error code on failure. + */ + +int lfs_dir_read(FAR lfs_t *lfs, FAR lfs_dir_t *dir, + FAR struct lfs_info *info); + +/* Change the position of the directory + * + * The new off must be a value previous returned from tell and specifies + * an absolute offset in the directory seek. + * + * Returns a negative error code on failure. + */ + +int lfs_dir_seek(FAR lfs_t *lfs, FAR lfs_dir_t *dir, lfs_off_t off); + +/* Return the position of the directory + * + * The returned offset is only meant to be consumed by seek and may not make + * sense, but does indicate the current position in the directory iteration. + * + * Returns the position of the directory, or a negative error code on failure. + */ + +lfs_soff_t lfs_dir_tell(FAR lfs_t *lfs, FAR lfs_dir_t *dir); + +/* Change the position of the directory to the beginning of the directory + * + * Returns a negative error code on failure. + */ + +int lfs_dir_rewind(FAR lfs_t *lfs, FAR lfs_dir_t *dir); + +/* Miscellaneous littlefs specific operations */ + +/* Traverse through all blocks in use by the filesystem + * + * The provided callback will be called with each block address that is + * currently in use by the filesystem. This can be used to determine which + * blocks are in use or how much of the storage is available. + * + * Returns a negative error code on failure. + */ + +int lfs_traverse(FAR lfs_t *lfs, CODE int (*cb)(FAR void *, lfs_block_t), + FAR void *data); + +/* Prunes any recoverable errors that may have occured in the filesystem + * + * Not needed to be called by user unless an operation is interrupted + * but the filesystem is still mounted. This is already called on first + * allocation. + * + * Returns a negative error code on failure. + */ + +int lfs_deorphan(FAR lfs_t *lfs); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __FS_LITTLEFS_LFS_H */ diff --git a/fs/littlefs/lfs_util.c b/fs/littlefs/lfs_util.c new file mode 100644 index 0000000000..b0dcde018d --- /dev/null +++ b/fs/littlefs/lfs_util.c @@ -0,0 +1,83 @@ +/**************************************************************************** + * fs/littlefs/lfs_util.c + * + * This file is a part of NuttX: + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * + * Ported by: + * + * Copyright (C) 2019 Pinecone Inc. All rights reserved. + * Author: lihaichen + * + * This port derives from ARM mbed logic which has a compatible 3-clause + * BSD license: + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "lfs.h" +#include "lfs_util.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* Only compile if user does not provide custom config */ + +#ifndef LFS_CONFIG + +/* Software CRC implementation with small lookup table */ + +void lfs_crc(FAR uint32_t *restrict crc, FAR const void *buffer, size_t size) +{ + static const uint32_t rtable[16] = + { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, + 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + FAR const uint8_t *data = buffer; + size_t i; + + for (i = 0; i < size; i++) + { + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf]; + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf]; + } +} + +#endif diff --git a/fs/littlefs/lfs_util.h b/fs/littlefs/lfs_util.h new file mode 100644 index 0000000000..5060b0212c --- /dev/null +++ b/fs/littlefs/lfs_util.h @@ -0,0 +1,283 @@ +/**************************************************************************** + * fs/littlefs/lfs.c + * + * This file is a part of NuttX: + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * + * Ported by: + * + * Copyright (C) 2019 Pinecone Inc. All rights reserved. + * Author: lihaichen + * + * This port derives from ARM mbed logic which has a compatible 3-clause + * BSD license: + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __FS_LITTLEFS_LFS_UTIL_H +#define __FS_LITTLEFS_LFS_UTIL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +/* Users can override lfs_util.h with their own configuration by defining + * LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). + * + * If LFS_CONFIG is used, none of the default utils will be emitted and must be + * provided by the config file. To start I would suggest copying lfs_util.h and + * modifying as needed. + */ + +#ifdef LFS_CONFIG +# define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +# define LFS_STRINGIZE2(x) #x +# include LFS_STRINGIZE(LFS_CONFIG) +#else + +/* System includes */ + +#include +#include +#include + +#define LFS_NO_DEBUG + +#ifndef LFS_NO_MALLOC +# include +#endif +#ifndef LFS_NO_ASSERT +# include +#endif +#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) +# include +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Macros, may be replaced by system specific wrappers. Arguments to these + * macros must not have side-effects as the macros can be removed for a smaller + * code footprint + */ + +/* Logging functions */ + +#ifndef LFS_NO_DEBUG +# define LFS_DEBUG(fmt, ...) \ + printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +# define LFS_DEBUG(fmt, ...) +#endif + +#ifndef LFS_NO_WARN +# define LFS_WARN(fmt, ...) \ + printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +# define LFS_WARN(fmt, ...) +#endif + +#ifndef LFS_NO_ERROR +# define LFS_ERROR(fmt, ...) \ + printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +# define LFS_ERROR(fmt, ...) +#endif + +/* Runtime assertions */ + +#ifndef LFS_NO_ASSERT +# define LFS_ASSERT(test) assert(test) +#else +# define LFS_ASSERT(test) +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/* Builtin functions, these may be replaced by more efficient + * toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more + * expensive basic C implementation for debugging purposes + */ + +/* Min/max functions for unsigned 32-bit numbers */ + +static inline uint32_t lfs_max(uint32_t a, uint32_t b) +{ + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) +{ + return (a < b) ? a : b; +} + +/* Find the next smallest power of 2 less than or equal to a */ + +static inline uint32_t lfs_npw2(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a - 1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; + a >>= s; + r |= s; + s = (a > 0xff) << 3; + a >>= s; + r |= s; + s = (a > 0xf) << 2; + a >>= s; + r |= s; + s = (a > 0x3) << 1; + a >>= s; + r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +/* Count the number of trailing binary zeros in a + * lfs_ctz(0) may be undefined + */ + +static inline uint32_t lfs_ctz(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +/* Count the number of binary ones in a */ + +static inline uint32_t lfs_popc(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +/* Find the sequence comparison of a and b, this is the distance + * between a and b ignoring overflow + */ + +static inline int lfs_scmp(uint32_t a, uint32_t b) +{ + return (int)(unsigned)(a - b); +} + +/* Convert from 32-bit little-endian to native order */ + +static inline uint32_t lfs_fromle32(uint32_t a) +{ +#if !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && \ + ((defined(BYTE_ORDER) && BYTE_ORDER == ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t *)&a)[0] << 0) | (((uint8_t *)&a)[1] << 8) | + (((uint8_t *)&a)[2] << 16) | (((uint8_t *)&a)[3] << 24); +#endif +} + +/* Convert to 32-bit little-endian from native order */ + +static inline uint32_t lfs_tole32(uint32_t a) +{ + return lfs_fromle32(a); +} + +/* Allocate memory, only used if buffers are not provided to littlefs */ + +static inline void *lfs_malloc(size_t size) +{ +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + return NULL; +#endif +} + +/* Deallocate memory, only used if buffers are not provided to littlefs */ + +static inline void lfs_free(FAR void *p) +{ +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + + /* Calculate CRC-32 with polynomial = 0x04c11db7 */ + +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif /* __FS_LITTLEFS_LFS_UTIL_H */ diff --git a/fs/littlefs/lfs_vfs.c b/fs/littlefs/lfs_vfs.c new file mode 100644 index 0000000000..ce67f30b5c --- /dev/null +++ b/fs/littlefs/lfs_vfs.c @@ -0,0 +1,906 @@ +/**************************************************************************** + * fs/littlefs/lfs.c + * + * This file is a part of NuttX: + * + * Copyright (C) 2019 Gregory Nutt. All rights reserved. + * + * Ported by: + * + * Copyright (C) 2019 Pinecone Inc. All rights reserved. + * Author: lihaichen + * + * This port derives from ARM mbed logic which has a compatible 3-clause + * BSD license: + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lfs.h" +#include "lfs_util.h" +#include +#include +#include +#include +#include + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct littefs_s +{ + struct lfs lfs; + struct lfs_config cfg; + FAR struct mtd_dev_s *mtd; + struct mtd_geometry_s geo; + sem_t fs_sem; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static void littlefs_semgive(FAR struct littefs_s *fs); +static void littlefs_semtake(FAR struct littefs_s *fs); + +static void littlefs_tostat(FAR struct stat *st, + FAR struct lfs_info *info); + +static int littlefs_open(FAR struct file *filep, + FAR const char *relpath, int oflags, + mode_t mode); +static int littlefs_close(FAR struct file *filep); +static ssize_t littlefs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t littlefs_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static off_t littlefs_seek(FAR struct file *filep, off_t offset, + int whence); + +static int littlefs_bind(FAR struct inode *mtdinode, + FAR const void *data, FAR void **handle); +static int littlefs_unbind(FAR void *handle, + FAR struct inode **mtdinode, unsigned int flags); + +static int littlefs_statfs(FAR struct inode *mountpt, + FAR struct statfs *buf); + +static int littlefs_opendir(FAR struct inode *mountpt, + FAR const char *relpath, + FAR struct fs_dirent_s *dir); +static int littlefs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); +static int littlefs_readdir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); + +static int littlefs_rewinddir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir); + +static int littlefs_fstat(FAR const struct file *filep, + FAR struct stat *buf); +static int littlefs_truncate(FAR struct file *filep, off_t length); + +static int littlefs_mkdir(FAR struct inode *mountpt, + FAR const char *relpath, mode_t mode); +static int littlefs_rmdir(FAR struct inode *mountpt, + FAR const char *relpath); + +static int littlefs_unlink(FAR struct inode *mountpt, + FAR const char *relpath); + +static int littlefs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath); +static int littlefs_stat(FAR struct inode *mountpt, + FAR const char *relpath, + FAR struct stat *buf); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct mountpt_operations littlefs_operations = +{ + littlefs_open, /* open */ + littlefs_close, /* close */ + littlefs_read, /* read */ + littlefs_write, /* write */ + littlefs_seek, /* seek */ + NULL, /* ioctl */ + + NULL, /* sync */ + NULL, /* dup */ + littlefs_fstat, /* fstat */ + littlefs_truncate, /* truncate */ + + littlefs_opendir, /* opendir */ + littlefs_closedir, /* closedir */ + littlefs_readdir, /* readdir */ + littlefs_rewinddir, /* rewinddir */ + + littlefs_bind, /* bind */ + littlefs_unbind, /* unbind */ + littlefs_statfs, /* statfs */ + + littlefs_unlink, /* unlink */ + littlefs_mkdir, /* mkdir */ + littlefs_rmdir, /* rmdir */ + littlefs_rename, /* rename */ + littlefs_stat, /* stat */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +static void littlefs_semtake(FAR struct littefs_s *fs) +{ + int ret; + do + { + ret = nxsem_wait(&fs->fs_sem); + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -EINTR); +} + +static void littlefs_semgive(FAR struct littefs_s *fs) +{ + nxsem_post(&fs->fs_sem); +} + +static int vfs_flag_to_lfs(int flags) +{ + int res = 0; + + if ((flags & 3) == O_RDONLY) + { + res |= LFS_O_RDONLY; + } + + if ((flags & 3) == O_WRONLY) + { + res |= LFS_O_WRONLY; + } + + if ((flags & 3) == O_RDWR) + { + res |= LFS_O_RDWR; + } + + if (flags & O_CREAT) + { + res |= LFS_O_CREAT; + } + + if (flags & O_EXCL) + { + res |= LFS_O_EXCL; + } + + if (flags & O_TRUNC) + { + res |= LFS_O_TRUNC; + } + + if (flags & O_APPEND) + { + res |= LFS_O_APPEND; + } + + return res; +} + +/* Conversion error code */ + +static int lfs_result_to_vfs(int result) +{ + int status = 0; + + switch (result) + { + case LFS_ERR_OK: + status = OK; + break; + + case LFS_ERR_IO: + status = -EIO; + break; /* Error during device operation */ + + case LFS_ERR_NOENT: + status = -ENOENT; + break; /* No directory entry */ + + case LFS_ERR_EXIST: + status = -EEXIST; + break; /* Entry already exists */ + + case LFS_ERR_NOTDIR: + status = -ENOTDIR; + break; /* Entry is not a dir */ + + case LFS_ERR_ISDIR: + status = -EISDIR; + break; /* Entry is a dir */ + + case LFS_ERR_NOTEMPTY: + status = -ENOTEMPTY; + break; /* Dir is not empty */ + + case LFS_ERR_BADF: + status = -EBADF; + break; /* Bad file number */ + + case LFS_ERR_INVAL: + status = -EINVAL; + break; /* Invalid parameter */ + + case LFS_ERR_NOSPC: + status = -ENOSPC; + break; /* No space left on device */ + + case LFS_ERR_NOMEM: + status = -ENOMEM; + break; /* No more memory available */ + + case LFS_ERR_CORRUPT: + status = LFS_ERR_CORRUPT; + break; /* Corrupted */ + + default: + status = -EIO; + break; + } + + return status; +} + +static int _lfs_flash_read(FAR const struct lfs_config *cfg, + lfs_block_t block, lfs_off_t off, + FAR void *buffer, lfs_size_t size) +{ + FAR struct mtd_dev_s *mtd; + + DEBUGASSERT(cfg != NULL); + DEBUGASSERT(cfg->context != NULL); + + mtd = (FAR struct mtd_dev_s *)cfg->context; + if (mtd->read(mtd, block * cfg->block_size + off, size, buffer) != size) + { + return LFS_ERR_IO; + } + + return LFS_ERR_OK; +} + +static int _lfs_flash_prog(FAR const struct lfs_config *cfg, + lfs_block_t block, lfs_off_t off, + FAR const void *buffer, lfs_size_t size) +{ + FAR struct mtd_dev_s *mtd; + + DEBUGASSERT(cfg != NULL); + DEBUGASSERT(cfg->context != NULL); + + mtd = (FAR struct mtd_dev_s *)cfg->context; + if (mtd->write(mtd, block * cfg->block_size + off, size, buffer) != size) + { + return LFS_ERR_IO; + } + + return LFS_ERR_OK; +} + +static int _lfs_flash_erase(FAR const struct lfs_config *cfg, + lfs_block_t block) +{ + FAR struct mtd_dev_s *mtd; + + DEBUGASSERT(cfg != NULL); + DEBUGASSERT(cfg->context != NULL); + + mtd = (struct mtd_dev_s *)cfg->context; + if (mtd->erase(mtd, block, 1) != 1) + { + return LFS_ERR_IO; + } + + return LFS_ERR_OK; +} + +static int _lfs_flash_sync(FAR const struct lfs_config *cfg) +{ + return LFS_ERR_OK; +} + +static int littlefs_bind(FAR struct inode *mtdinode, + FAR const void *data, FAR void **handle) +{ + FAR struct mtd_dev_s *mtd; + FAR struct littefs_s *fs; + int ret; + + finfo("mtdinode=%p data=%p handle=%p\n", mtdinode, data, handle); + DEBUGASSERT(mtdinode != NULL && handle != NULL); + + /* Extract the MTD interface reference */ + + DEBUGASSERT(INODE_IS_MTD(mtdinode) && mtdinode->u.i_mtd != NULL); + mtd = mtdinode->u.i_mtd; + fs = (FAR struct littefs_s *)kmm_zalloc(sizeof(struct littefs_s)); + if (fs == NULL) + { + ferr("ERROR: Failed to allocate volume structure\n"); + return -ENOMEM; + } + + (void)nxsem_init(&fs->fs_sem, 0, 0); + + fs->mtd = mtd; + + ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, + (unsigned long)((uintptr_t)&fs->geo)); + if (ret < 0) + { + ret = -ENODEV; + ferr("ERROR: MTD_IOCTL(MTDIOC_GEOMETRY) failed: %d\n", ret); + goto errout_with_volume; + } + + fs->cfg.context = mtd; + fs->cfg.read_size = fs->geo.blocksize; + fs->cfg.prog_size = fs->geo.blocksize; + fs->cfg.block_size = fs->geo.erasesize; + fs->cfg.block_count = fs->geo.neraseblocks; + + fs->cfg.lookahead = 32 * ((fs->cfg.block_count + 31) / 32); + if (fs->cfg.lookahead > 512) + { + fs->cfg.lookahead = 512; + } + + fs->cfg.read = _lfs_flash_read; + fs->cfg.prog = _lfs_flash_prog; + fs->cfg.erase = _lfs_flash_erase; + fs->cfg.sync = _lfs_flash_sync; + + ret = lfs_mount(&fs->lfs, &fs->cfg); + if (ret != LFS_ERR_OK) + { + finfo("mount error\n"); + ret = lfs_format(&fs->lfs, &fs->cfg); + if (ret != LFS_ERR_OK) + { + ret = -EINVAL; + ferr("ERROR: Invalid lfs format failed: %d\n", ret); + goto errout_with_volume; + } + + ret = lfs_mount(&fs->lfs, &fs->cfg); + if (ret != LFS_ERR_OK) + { + ret = -EINVAL; + ferr("ERROR: Invalid lfs mount failed: %d\n", ret); + goto errout_with_volume; + } + } + + *handle = (FAR void *)fs; + littlefs_semgive(fs); + finfo("mount ok\n"); + return OK; + +errout_with_volume: + nxsem_destroy(&fs->fs_sem); + kmm_free(fs); + return ret; +} + +static int littlefs_unbind(FAR void *handle, FAR struct inode **mtdinode, + unsigned int flags) +{ + FAR struct littefs_s *fs = (FAR struct littefs_s *)handle; + int ret = OK; + + if (!fs) + { + return -EINVAL; + } + + littlefs_semtake(fs); + ret = lfs_unmount(&fs->lfs); + nxsem_destroy(&fs->fs_sem); + kmm_free(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_opendir(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct fs_dirent_s *dir) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_dir_open(&fs->lfs, (lfs_dir_t *)&dir->u.littlefs, relpath); + littlefs_semgive(fs); + + finfo("open dir\n"); + return lfs_result_to_vfs(ret); +} + +static int littlefs_closedir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("close dir\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_dir_close(&fs->lfs, (lfs_dir_t *)&dir->u.littlefs); + littlefs_semgive(fs); + + return lfs_result_to_vfs(ret); +} + +static int littlefs_readdir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct littefs_s *fs = mountpt->i_private; + struct lfs_info info; + int ret = OK; + + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_dir_read(&fs->lfs, (lfs_dir_t *)&dir->u.littlefs, &info); + if (ret == 1) + { + memset(dir->fd_dir.d_name, 0, NAME_MAX); + strncpy(dir->fd_dir.d_name, info.name, NAME_MAX); + dir->fd_dir.d_type = 0; + switch (info.type) + { + case LFS_TYPE_DIR: + dir->fd_dir.d_type |= DTYPE_DIRECTORY; + break; + + case LFS_TYPE_REG: + dir->fd_dir.d_type |= DTYPE_FILE; + break; + } + } + else + { + goto breakout; + } + + littlefs_semgive(fs); + return OK; + +breakout: + ret = -ENOENT; + littlefs_semgive(fs); + return ret; +} + +static int littlefs_rewinddir(FAR struct inode *mountpt, + FAR struct fs_dirent_s *dir) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("littlefs_rewinddir\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_dir_rewind(&fs->lfs, (lfs_dir_t *)&dir->u.littlefs); + if (ret != LFS_ERR_OK) + { + goto breakout; + } + + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_mkdir(FAR struct inode *mountpt, + FAR const char *relpath, mode_t mode) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("littlefs_mkdir\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_mkdir(&fs->lfs, relpath); + if (ret != LFS_ERR_OK) + { + goto breakout; + } + + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_rmdir(FAR struct inode *mountpt, + FAR const char *relpath) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("littlefs_rmdir\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_remove(&fs->lfs, relpath); + if (ret != LFS_ERR_OK) + { + goto breakout; + } + + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + int ret = OK; + + finfo("littlefs_open [%s]\n", relpath); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + + littlefs_semtake(fs); + + lf = (lfs_file_t *)kmm_zalloc(sizeof(lfs_file_t)); + if (!lf) + { + ret = LFS_ERR_NOMEM; + goto breakout; + } + + ret = lfs_file_open(&fs->lfs, lf, relpath, vfs_flag_to_lfs(oflags)); + if (ret != LFS_ERR_OK) + { + ret = lfs_result_to_vfs(ret); + goto breakout; + } + + filep->f_priv = lf; + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + if (lf != NULL) + { + kmm_free(lf); + } + + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_close(FAR struct file *filep) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + int ret = OK; + + finfo("littlefs_close\n"); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + lf = filep->f_priv; + + littlefs_semtake(fs); + ret = lfs_file_close(&fs->lfs, lf); + kmm_free(lf); + littlefs_semgive(fs); + + return lfs_result_to_vfs(ret); +} + +static ssize_t littlefs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + ssize_t ret = 0; + + finfo("littlefs_read\n"); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + lf = filep->f_priv; + + littlefs_semtake(fs); + ret = lfs_file_read(&fs->lfs, lf, buffer, buflen); + littlefs_semgive(fs); + if (ret < 0) + { + ret = lfs_result_to_vfs(ret); + } + + return ret; +} + +static ssize_t littlefs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + ssize_t ret = 0; + + finfo("littlefs_write %d\n", buflen); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + lf = filep->f_priv; + + littlefs_semtake(fs); + ret = lfs_file_write(&fs->lfs, lf, buffer, buflen); + littlefs_semgive(fs); + if (ret < 0) + { + ret = lfs_result_to_vfs(ret); + } + + return ret; +} + +static off_t littlefs_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + off_t ret = 0; + + finfo("littlefs_seek\n"); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + lf = filep->f_priv; + + littlefs_semtake(fs); + ret = lfs_file_seek(&fs->lfs, lf, offset, whence); + littlefs_semgive(fs); + if (ret < 0) + { + ret = lfs_result_to_vfs(ret); + } + + return ret; +} + +static int littlefs_fstat(FAR const struct file *filep, FAR struct stat *buf) +{ + finfo("littlefs_fstat\n"); + return OK; +} + +static int littlefs_truncate(FAR struct file *filep, off_t length) +{ + FAR struct inode *inode; + FAR struct littefs_s *fs; + FAR lfs_file_t *lf = NULL; + int ret = 0; + + finfo("littlefs_truncate\n"); + DEBUGASSERT(filep != NULL && filep->f_inode != NULL && + filep->f_inode->i_private); + + inode = filep->f_inode; + fs = inode->i_private; + lf = filep->f_priv; + + littlefs_semtake(fs); + ret = lfs_file_truncate(&fs->lfs, lf, length); + littlefs_semgive(fs); + if (ret < 0) + { + ret = lfs_result_to_vfs(ret); + } + + return ret; +} + +static int littlefs_statfs_count(FAR void *p, lfs_block_t b) +{ + *(FAR lfs_size_t *)p += 1; + return 0; +} + +static int littlefs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf) +{ + FAR struct littefs_s *fs = mountpt->i_private; + lfs_size_t in_use = 0; + int ret = OK; + + finfo("littlefs_statfs\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_traverse(&fs->lfs, littlefs_statfs_count, &in_use); + if (ret != LFS_ERR_OK) + { + goto breakout; + } + + memset(buf, 0, sizeof(struct statfs)); + buf->f_type = LITTLEFS_SUPER_MAGIC; + buf->f_bsize = fs->cfg.block_size; + buf->f_blocks = fs->cfg.block_count; + buf->f_bfree = fs->cfg.block_count - in_use; + buf->f_bavail = buf->f_bfree; + + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static int littlefs_unlink(FAR struct inode *mountpt, FAR const char *relpath) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("littlefs_unlink\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_remove(&fs->lfs, relpath); + littlefs_semgive(fs); + + return lfs_result_to_vfs(ret); +} + +static int littlefs_rename(FAR struct inode *mountpt, + FAR const char *oldrelpath, + FAR const char *newrelpath) +{ + FAR struct littefs_s *fs = mountpt->i_private; + int ret = OK; + + finfo("littlefs_rename\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + littlefs_semtake(fs); + ret = lfs_rename(&fs->lfs, oldrelpath, newrelpath); + littlefs_semgive(fs); + + return lfs_result_to_vfs(ret); +} + +static int littlefs_stat(FAR struct inode *mountpt, FAR const char *relpath, + FAR struct stat *buf) +{ + FAR struct littefs_s *fs = mountpt->i_private; + struct lfs_info info; + int ret = OK; + + finfo("littlefs_stat\n"); + DEBUGASSERT(mountpt != NULL && mountpt->i_private != NULL); + + memset(buf, 0, sizeof(struct stat)); + + littlefs_semtake(fs); + ret = lfs_stat(&fs->lfs, relpath, &info); + if (ret != LFS_ERR_OK) + { + goto breakout; + } + + littlefs_tostat(buf, &info); + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); + +breakout: + littlefs_semgive(fs); + return lfs_result_to_vfs(ret); +} + +static void littlefs_tostat(struct stat *st, struct lfs_info *info) +{ + memset(st, 0, sizeof(struct stat)); + st->st_size = info->size; + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO; + switch (info->type) + { + case LFS_TYPE_DIR: + st->st_mode |= S_IFDIR; + break; + + case LFS_TYPE_REG: + st->st_mode |= S_IFREG; + break; + } +} diff --git a/fs/mount/fs_gettype.c b/fs/mount/fs_gettype.c index d7b4c2a68a..3b70b1df4a 100644 --- a/fs/mount/fs_gettype.c +++ b/fs/mount/fs_gettype.c @@ -114,6 +114,12 @@ FAR const char *fs_gettype(FAR struct statfs *statbuf) break; #endif +#ifdef CONFIG_FS_LITTLEFS + case LITTLEFS_SUPER_MAGIC: + fstype = "littlefs"; + break; +#endif + #ifdef CONFIG_NFS case NFS_SUPER_MAGIC: fstype = "nfs"; diff --git a/fs/mount/fs_mount.c b/fs/mount/fs_mount.c index edf29f25e9..07891b3ca4 100644 --- a/fs/mount/fs_mount.c +++ b/fs/mount/fs_mount.c @@ -78,7 +78,7 @@ /* These file systems require MTD drivers */ -#ifdef CONFIG_FS_SPIFFS +#if defined(CONFIG_FS_SPIFFS) || defined(CONFIG_FS_LITTLEFS) # define MDFS_SUPPORT 1 #endif @@ -140,11 +140,18 @@ static const struct fsmap_t g_bdfsmap[] = extern const struct mountpt_operations spiffs_operations; #endif +#ifdef CONFIG_FS_LITTLEFS +extern const struct mountpt_operations littlefs_operations; +#endif + static const struct fsmap_t g_mdfsmap[] = { #ifdef CONFIG_FS_SPIFFS { "spiffs", &spiffs_operations }, #endif +#ifdef CONFIG_FS_LITTLEFS + { "littlefs", &littlefs_operations }, +#endif }; #endif /* MDFS_SUPPORT */ diff --git a/include/nuttx/fs/dirent.h b/include/nuttx/fs/dirent.h index 208a6c04b7..ff5058f2ac 100644 --- a/include/nuttx/fs/dirent.h +++ b/include/nuttx/fs/dirent.h @@ -222,6 +222,29 @@ struct fs_hostfsdir_s #endif /* CONFIG_DISABLE_MOUNTPOINT */ +#ifdef CONFIG_FS_LITTLEFS +typedef uint32_t _lfs_block_t; +typedef uint32_t _lfs_size_t; +typedef uint32_t _lfs_off_t; + +typedef struct _lfs_dir +{ + struct _lfs_dir *next; + _lfs_block_t pair[2]; + _lfs_off_t off; + + _lfs_block_t head[2]; + _lfs_off_t pos; + + struct _lfs_disk_dir + { + uint32_t rev; + _lfs_size_t size; + _lfs_block_t tail[2]; + } d; +} _lfs_dir_t; +#endif + struct fs_dirent_s { /* This is the node that was opened by opendir. The type of the inode @@ -289,6 +312,9 @@ struct fs_dirent_s #ifdef CONFIG_FS_SPIFFS struct fs_spiffsdir_s spiffs; #endif +#ifdef CONFIG_FS_LITTLEFS + _lfs_dir_t littlefs; +#endif #ifdef CONFIG_FS_UNIONFS struct fs_unionfsdir_s unionfs; #endif diff --git a/include/sys/statfs.h b/include/sys/statfs.h index 8211b53ddd..775994e4c7 100644 --- a/include/sys/statfs.h +++ b/include/sys/statfs.h @@ -96,6 +96,7 @@ #define XFS_SUPER_MAGIC 0x58465342 #define _XIAFS_SUPER_MAGIC 0x012fd16d #define SPIFFS_SUPER_MAGIC 0x20090315 +#define LITTLEFS_SUPER_MAGIC 0x0a732923 /* NuttX specific file-systems */