Initial commit based on content of fs/nffs from mynewt-core
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e53a422
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+-->
+
+## Overview
+Newtron Flash File System (nffs) is a flash file system with the following
+priorities:
+    * Minimal RAM usage
+    * Reliability
+
diff --git a/design.txt b/design.txt
new file mode 100644
index 0000000..07c2e9d
--- /dev/null
+++ b/design.txt
@@ -0,0 +1,885 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+***** NFFS
+
+*** HISTORY
+Rev.    Date            Changes
+6       2015/10/12      Add directory-reading API.
+5       2015/09/28      Rename to Newtron Flash File System.
+4       2015/09/08      Fix some badly-specified CRC behavior; clarification of
+                            behavior during restore of corrupt disk.
+3       2015/08/20      More cache specifics; updated nffs_read() function
+                            type.
+2       2015/08/17      Addition of crc16; clarification of sweep phase.
+1       2015/08/13
+
+
+*** SUMMARY
+
+Newtron Flash File System (nffs) is a flash file system with the following
+priorities:
+    * Minimal RAM usage
+    * Reliability
+
+
+*** DISK STRUCTURE
+
+At the top level, an nffs disk is partitioned into areas.  An area is a region
+of disk with the following properties:
+    (1) An area can be fully erased without affecting any other areas.
+    (2) A write to one area does not restrict writes to other areas.
+
+Clarification of point (2): some flash hardware divides its memory space into
+"blocks."  Writes within a block must be sequential, but writes to one block
+have no effect on what parts of other blocks can be written.  Thus, for flash
+hardware with such a restriction, each area must comprise a discrete number of
+blocks.
+
+While not strictly necessary, it is recommended that all areas have the same
+size.
+
+On disk, each area is prefixed with the following header:
+
+/** On-disk representation of an area header. */
+struct nffs_disk_area {
+    uint32_t nda_magic[4];  /* NFFS_AREA_MAGIC{0,1,2,3} */
+    uint32_t nda_length;    /* Total size of area, in bytes. */
+    uint8_t nda_ver;        /* Current nffs version: 0 */
+    uint8_t nda_gc_seq;     /* Garbage collection count. */
+    uint8_t reserved8;
+    uint8_t nda_id;         /* 0xff if scratch area. */
+};
+
+Beyond its header, an area contains a sequence of disk objects, representing
+the contents of the file system.  There are two types of objects: inodes and
+data blocks.  An inode represents a file or directory; a data block represents
+part of a file's contents.
+
+/** On-disk representation of an inode (file or directory). */
+struct nffs_disk_inode {
+    uint32_t ndi_magic;         /* NFFS_INODE_MAGIC */
+    uint32_t ndi_id;            /* Unique object ID. */
+    uint32_t ndi_seq;           /* Sequence number; greater supersedes
+                                   lesser. */
+    uint32_t ndi_parent_id;     /* Object ID of parent directory inode. */
+    uint8_t reserved8;
+    uint8_t ndi_filename_len;   /* Length of filename, in bytes. */
+    uint16_t ndi_crc16;         /* Covers rest of header and filename. */
+    /* Followed by filename. */
+};
+
+An inode filename's length cannot exceed 256 bytes.  The filename is not
+null-terminated.  The following ASCII characters are not allowed in a
+filename:
+    * /  (slash character)
+    * \0 (NUL character)
+
+/** On-disk representation of a data block. */
+struct nffs_disk_block {
+    uint32_t ndb_magic;     /* NFFS_BLOCK_MAGIC */
+    uint32_t ndb_id;        /* Unique object ID. */
+    uint32_t ndb_seq;       /* Sequence number; greater supersedes lesser. */
+    uint32_t ndb_inode_id;  /* Object ID of owning inode. */
+    uint32_t ndb_prev_id;   /* Object ID of previous block in file;
+                               NFFS_ID_NONE if this is the first block. */
+    uint16_t ndb_data_len;  /* Length of data contents, in bytes. */
+    uint16_t ndb_crc16;     /* Covers rest of header and data. */
+    /* Followed by 'ndb_data_len' bytes of data. */
+};
+
+Each data block contains the ID of the previous data block in the file.
+Together, the set of blocks in a file form a reverse singly-linked list.
+
+The maximum number of data bytes that a block can contain is determined at
+initialization-time.  The result is the greatest number which satisfies all of
+the following restrictions:
+    o No more than 2048.
+    o At least two maximum-sized blocks can fit in the smallest area.
+
+The 2048 number was chosen somewhat arbitrarily, and may change in the future.
+
+
+*** ID SPACE
+
+All disk objects have a unique 32-bit ID.  The ID space is partitioned as
+follows:
+      * 0x00000000 - 0x0fffffff: Directory inodes.
+      * 0x10000000 - 0x7fffffff: File inodes.
+      * 0x80000000 - 0xfffffffe: Data blocks.
+      * 0xffffffff             : Reserved (NFFS_ID_NONE)
+
+
+*** SCRATCH AREA
+
+A valid nffs file system must contain a single "scratch area."  The scratch
+area does not contain any objects of its own, and is only used during garbage
+collection.  The scratch area must have a size greater than or equal to each
+of the other areas in flash.
+
+
+*** RAM REPRESENTATION
+
+The file system comprises a set of objects of the following two types:
+    1) inode
+    2) data block
+
+Every object in the file system is stored in a 256-entry hash table.  An
+object's hash key is derived from its 32-bit ID.  Each list in the hash table
+is sorted by time of use; most-recently-used is at the front of the list. All
+objects are represented by the following structure:
+
+/**
+ * What gets stored in the hash table.  Each entry represents a data block or
+ * an inode.
+ */
+struct nffs_hash_entry {
+    SLIST_ENTRY(nffs_hash_entry) nhe_next;
+    uint32_t nhe_id;        /* 0 - 0x7fffffff if inode; else if block. */
+    uint32_t nhe_flash_loc; /* Upper-byte = area idx; rest = area offset. */
+};
+
+For each data block, the above structure is all that is stored in RAM.  To
+acquire more information about a data block, the block header must be read
+from flash.
+
+Inodes require a fuller RAM representation to capture the structure of the
+file system.  There are two types of inodes: files and directories.  Each
+inode hash entry is actually an instance of the following structure:
+
+/** Each inode hash entry is actually one of these. */
+struct nffs_inode_entry {
+    struct nffs_hash_entry nie_hash_entry;
+    SLIST_ENTRY(nffs_inode_entry) nie_sibling_next;
+    union {
+        struct nffs_inode_list nie_child_list;           /* If directory */
+        struct nffs_hash_entry *nie_last_block_entry;    /* If file */
+    };
+    uint8_t nie_refcnt;
+};
+
+A directory inode contains a list of its child files and directories
+(fie_child_list).  These entries are sorted alphabetically using the ASCII
+character set.
+
+A file inode contains a pointer to the last data block in the file
+(nie_last_block_entry).  For most file operations, the reversed block list must
+be walked backwards.  This introduces a number of speed inefficiencies:
+    * All data blocks must be read to determine the length of the file.
+    * Data blocks often need to be processed sequentially.  The reversed
+      nature of the block list transforms this from linear time to an O(n^2)
+      operation.
+
+Furthermore, obtaining information about any constituent data block requires a
+separate flash read.
+
+
+*** INODE CACHE AND DATA BLOCK CACHE
+The speed issues are addressed by a pair of caches.  Cached inodes entries
+contain the file length and a much more convenient doubly-linked list of
+cached data blocks.  The benefit of using caches is that the size of the
+caches need not be proportional to the size of the file system.  In other
+words, caches can address speed efficiency concerns without negatively
+impacting the file system's scalability.
+
+nffs requires both caches during normal operation, so it is not possible to
+disable them.  However, the cache sizes are configurable, and both caches can
+be configured with a size of one if RAM usage must be minimized.
+
+The following data structures are used in the inode and data block caches.
+
+/** Full data block representation; not stored permanently in RAM. */
+struct nffs_block {
+    struct nffs_hash_entry *nb_hash_entry;   /* Points to real block entry. */
+    uint32_t nb_seq;                         /* Sequence number; greater
+                                                supersedes lesser. */
+    struct nffs_inode_entry *nb_inode_entry; /* Owning inode. */
+    struct nffs_hash_entry *nb_prev;         /* Previous block in file. */
+    uint16_t nb_data_len;                    /* # of data bytes in block. */
+    uint16_t reserved16;
+};
+
+/** Represents a single cached data block. */
+struct nffs_cache_block {
+    TAILQ_ENTRY(nffs_cache_block) ncb_link; /* Next / prev cached block. */
+    struct nffs_block ncb_block;            /* Full data block. */
+    uint32_t ncb_file_offset;               /* File offset of this block. */
+};
+
+/** Full inode representation; not stored permanently in RAM. */
+struct nffs_inode {
+    struct nffs_inode_entry *ni_inode_entry; /* Points to real inode entry. */
+    uint32_t ni_seq;                         /* Sequence number; greater
+                                                supersedes lesser. */
+    struct nffs_inode_entry *ni_parent;      /* Points to parent directory. */
+    uint8_t ni_filename_len;                 /* # chars in filename. */
+    uint8_t ni_filename[NFFS_SHORT_FILENAME_LEN]; /* First 3 bytes. */
+};
+
+/** Doubly-linked tail queue of cached blocks; contained in cached inodes. */
+TAILQ_HEAD(nffs_block_cache_list, nffs_block_cache_entry);
+
+/** Represents a single cached file inode. */
+struct nffs_cache_inode {
+    TAILQ_ENTRY(nffs_cache_inode) nci_link;        /* Sorted; LRU at tail. */
+    struct nffs_inode nci_inode;                   /* Full inode. */
+    struct nffs_cache_block_list nci_block_list;   /* List of cached blocks. */
+    uint32_t nci_file_size;                        /* Total file size. */
+};
+
+Only file inodes are cached; directory inodes are never cached.
+
+Within a cached inode, all cached data blocks are contiguous.  E.g., if the
+start and end of a file are cached, then the middle must also be cached.  A
+data block is only cached if its owning file is also cached.
+
+Internally, cached inodes are stored in a singly-linked list, ordered by time
+of use.  The most-recently-used entry is the first element in the list.  If a
+new inode needs to be cached, but the inode cache is full, the
+least-recently-used entry is freed to make room for the new one.  The
+following operations cause an inode to be cached:
+    * Querying a file's length.
+    * Seeking within a file.
+    * Reading from a file.
+    * Writing to a file.
+
+The following operations cause a data block to be cached:
+    * Reading from the block.
+    * Writing to the block.
+
+If one of the above operations is applied to a data block that is not currently
+cached, nffs uses the following procedure to cache the necessary block:
+    1. If none of the owning inode's blocks are currently cached, allocate a
+       cached block entry corresponding to the requested block and insert it
+       into the inode's list.
+    2. Else if the requested file offset is less than that of the first cached
+       block, bridge the gap between the inode's sequence of cached blocks and
+       the block that now needs to be cached.  This is accomplished by caching
+       each block in the gap, finishing with the requested block.
+    3. Else (the requested offset is beyond the end of the cache),
+        a. If the requested offset belongs to the block that immediately
+           follows the end of the cache, cache the block and append it to the
+           list.
+        b. Else, clear the cache, and populate it with the single entry
+           corresponding to the requested block.
+
+If the system is unable to allocate a cached block entry at any point during
+the above procedure, the system frees up other blocks currently in the cache.
+This is accomplished as follows:
+    1. Iterate the inode cache in reverse (i.e., start with the
+       least-recently-used entry).  For each entry:
+        a. If the entry's cached block list is empty, advance to the next
+           entry.
+        b. Else, free all the cached blocks in the entry's list.
+
+Because the system imposes a minimum block cache size of one, the above
+procedure will always reclaim at least one cache block entry.  The above
+procedure may result in the freeing of the block list that belongs to the very
+inode being operated on.  This is OK, as the final block to get cached is
+always the block being requested.
+
+
+*** CONFIGURATION
+The file system is configured by populating fields in a global structure.
+Each field in the structure corresponds to a setting.  All configuration must
+be done prior to calling nffs_init().  The configuration structure is defined
+as follows:
+
+struct nffs_config {
+    /** Maximum number of inodes; default=1024. */
+    uint32_t nc_num_inodes;
+
+    /** Maximum number of data blocks; default=4096. */
+    uint32_t nc_num_blocks;
+
+    /** Maximum number of open files; default=4. */
+    uint32_t nc_num_files;
+
+    /** Inode cache size; default=4. */
+    uint32_t nc_num_cache_inodes;
+
+    /** Data block cache size; default=64. */
+    uint32_t nc_num_cache_blocks;
+};
+
+extern struct nffs_config nffs_config;
+
+Any fields that are set to 0 (or not set at all) inherit the corresponding
+default value.  This means that it is impossible to configure any setting with
+a value of zero.
+
+
+*** INITIALIZATION
+
+There are two means of initializing an nffs file system:
+    (1) Restore an existing file system via detection.
+    (2) Create a new file system via formatting.
+
+Both methods require the user to describe how the flash memory is divided into
+areas.  This is accomplished with an array of struct nffs_area_desc, defined as
+follows:
+
+struct nffs_area_desc {
+    uint32_t nad_offset;    /* Flash offset of start of area. */
+    uint32_t nad_length;    /* Size of area, in bytes. */
+};
+
+An array of area descriptors is terminated by an entry with a fad_length field
+of 0.
+
+One common initialization sequence is the following:
+
+    (1) Detect an nffs file system anywhere in flash.
+    (2) If no file system detected, format a new file system in a specific
+        region of flash.
+
+
+*** DETECTION
+
+The file system detection process consists of scanning a specified set of
+flash regions for valid nffs areas, and then populating the RAM representation
+of the file system with the detected objects.  Detection is initiated with the
+following function:
+
+/**
+ * Searches for a valid nffs file system among the specified areas.  This
+ * function succeeds if a file system is detected among any subset of the
+ * supplied areas.  If the area set does not contain a valid file system,
+ * a new one can be created via a separate call to nffs_format().
+ *
+ * @param area_descs        The area set to search.  This array must be
+ *                              terminated with a 0-length area.
+ *
+ * @return                  0 on success;
+ *                          NFFS_ECORRUPT if no valid file system was detected;
+ *                          other nonzero on error.
+ */
+int nffs_detect(const struct nffs_area_desc *area_descs);
+
+As indicated, not every area descriptor needs to reference a valid nffs area.
+Detection is successful as long as a complete file system is detected
+somewhere in the specified regions of flash.  If an application is unsure
+where a file system might be located, it can initiate detection across the
+entire flash region.
+
+A detected file system is valid if:
+    (1) At least one non-scratch area is present.
+    (2) At least one scratch area is present (only the first gets used if
+        there is more than one).
+    (3) The root directory inode is present.
+
+During detection, each indicated region of flash is checked for a valid area
+header.  The contents of each valid non-scratch area are then restored into
+the nffs RAM representation.  The following procedure is applied to each object
+in the area:
+
+    (1) Verify the object's integrity via a crc16 check.  If invalid, the
+        object is discarded and the procedure restarts on the next object in
+        the area.
+
+    (2) Convert the disk object into its corresponding RAM representation and
+        insert it into the hash table.  If the object is an inode, its
+        reference count is initialized to 1, indicating ownership by its
+        parent directory.
+
+    (3) If an object with the same ID is already present, then one supersedes
+        the other.  Accept the object with the greater sequence number and
+        discard the other.
+
+    (4) If the object references a nonexistant inode (parent directory in the
+        case of an inode; owning file in the case of a data block), insert a
+        temporary "dummy" inode into the hash table so that inter-object links
+        can be maintained until the absent inode is eventually restored.  Dummy
+        inodes are identified by a reference count of 0.
+
+    (5) If a delete record for an inode is encountered, the inode's parent
+        pointer is set to null to indicate that it should be removed from RAM.
+
+If nffs encounters an object that cannot be identified (i.e., its magic number
+is not valid), it scans the remainder of the flash area for the next valid
+magic number.  Upon encountering a valid object, nffs resumes the procedure
+described above.
+
+After all areas have been restored, a sweep is performed across the entire RAM
+representation so that invalid inodes can be deleted from memory.
+
+For each directory inode:
+    * If its reference count is 0 (i.e., it is a dummy), migrate its children
+      to the /lost+found directory, and delete it from the RAM representation.
+      This should only happen in the case of file system corruption.
+    * If its parent reference is null (i.e., it was deleted), delete it and all
+      its children from the RAM representation.
+
+For each file inode:
+    * If its reference count is 0 (i.e., it is a dummy), delete it from the RAM
+      representation.  This should only happen in the case of file system
+      corruption.  (We should try to migrate the file to the lost+found
+      directory in this case, as mentioned in the todo section).
+
+When an object is deleted during this sweep, it is only deleted from the RAM
+representation; nothing is written to disk.
+
+When objects are migrated to the lost+found directory, their parent inode
+reference is permanently updated on the disk.
+
+In addition, a single scratch area is identified during the detection process.
+The first area whose 'fda_id' value is set to 0xff is designated as the file
+system scratch area.  If no valid scratch area is found, the cause could be
+that the system was restarted while a garbage collection cycle was in progress.
+Such a condition is identified by the presence of two areas with the same ID.
+In such a case, the shorter of the two areas is erased and designated as the
+scratch area.
+
+
+*** FORMATTING
+
+A new file system is created via formatting.  Formatting is achieved via the
+following function:
+
+/**
+ * Erases all the specified areas and initializes them with a clean nffs
+ * file system.
+ *
+ * @param area_descs        The set of areas to format.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int nffs_format(const struct nffs_area_desc *area_descs);
+
+On success, an area header is written to each of the specified locations.  The
+largest area in the set is designated as the initial scratch area.
+
+
+*** FLASH WRITES
+
+The nffs implementation always writes in a strictly sequential fashion within an
+area.  For each area, the system keeps track of the current offset.  Whenever
+an object gets written to an area, it gets written to that area's current
+offset, and the offset is increased by the object's disk size.
+
+When a write needs to be performed, the nffs implementation selects the
+appropriate destination area by iterating though each area until one with
+sufficient free space is encountered.
+
+There is no write buffering.  Each call to a write function results in a write
+operation being sent to the flash hardware.
+
+
+*** NEW OBJECTS
+
+Whenever a new object is written to disk, it is assigned the following
+properties:
+    * ID: A unique value is selected from the 32-bit ID space, as appropriate
+      for the object's type.
+    * Sequence number: 0
+
+When a new file or directory is created, a corresponding inode is written to
+flash.  Likewise, a new data block also results in the writing of a
+corresponding disk object.
+
+
+*** MOVING / RENAMING FILES AND DIRECTORIES
+
+When a file or directory is moved or renamed, its corresponding inode is
+rewritten to flash with the following properties:
+    * ID: Unchanged
+    * Sequence number: Previous value plus one.
+    * Parent inode: As specified by the move / rename operation.
+    * Filename: As specified by the move / rename operation.
+
+Because the inode's ID is unchanged, all dependent objects remain valid.
+
+
+*** UNLINKING FILES AND DIRECTORIES
+
+When a file or directory is unlinked from its parent directory, a deletion
+record for the unlinked inode gets written to flash.  The deletion record is an
+inode with the following properties:
+    * ID: Unchanged
+    * Sequence number: Previous value plus one.
+    * Parent inode ID: NFFS_ID_NONE
+
+When an inode is unlinked, no deletion records need to be written for the
+inode's dependent objects (constituent data blocks or child inodes).  During
+the next file system detection, it is recognized that the objects belong to
+a deleted inode, so they are not restored into the RAM representation.
+
+If a file has an open handle at the time it gets unlinked, application code
+can continued to use the file handle to read and write data.  All files retain
+a reference count, and a file isn't deleted from the RAM representation until
+its reference code drops to 0.  Any attempt to open an unlinked file fails,
+even if the file is referenced by other file handles.
+
+
+*** WRITING TO A FILE
+
+The following procedure is used whenever the application code writes to a file.
+First, if the write operation specifies too much data to fit into a single
+block, the operation is split into several separate write operations.  Then,
+for each write operation:
+
+    (1) Determine which existing blocks the write operation overlaps
+        (n = number of overwritten blocks).
+
+    (2) If n = 0, this is an append operation.  Write a data block with the
+        following properties:
+            * ID: New unique value.
+            * Sequence number: 0.
+
+    (3) Else (n > 1), this write overlaps existing data.
+            (a) For each block in [1, 2, ... n-1], write a new block
+                containing the updated contents.  Each new block supersedes the
+                block it overwrites.  That is, each block has the following
+                properties:
+                    * ID: Unchanged
+                    * Sequence number: Previous value plus one.
+
+            (b) Write the nth block.  The nth block includes all appended data,
+                if any.  As with the other blocks, its ID is unchanged and its
+                sequence number is incremented.
+
+Appended data can only be written to the end of the file.  That is, "holes" are
+not supported.
+
+
+*** GARBAGE COLLECTION
+
+When the file system is too full to accomodate a write operation, the system
+must perform garbage collection to make room.  The garbage collection
+procedure is described below:
+
+    (1) The non-scratch area with the lowest garbage collection sequence
+        number is selected as the "source area."  If there are other areas
+        with the same sequence number, the one with the smallest flash offset
+        is selected. 
+
+    (2) The source area's ID is written to the scratch area's header,
+        transforming it into a non-scratch ID.  This former scratch area is now
+        known as the "destination area."
+
+    (3) The RAM representation is exhaustively searched for collectible
+        objects.  The following procedure is applied to each inode in the
+        system:
+
+        (a) If the inode is resident in the source area, copy the inode record
+            to the destination area.
+
+        (b) If the inode is a file inode, walk the inode's list of data blocks,
+            starting with the last block in the file.  Each block that is
+            resident in the source area is copied to the destination area.  If
+            there is a run of two or more blocks that are resident in the
+            source area, they are consolidated and copied to the destination
+            area as a single new block (subject to the maximum block size
+            restriction).
+
+    (4) The source area is reformatted as a scratch sector (i.e., is is fully
+        erased, and its header is rewritten with an ID of 0xff).  The area's
+        garbage collection sequence number is incremented prior to rewriting
+        the header.  This area is now the new scratch sector.
+
+
+*** MISC
+
+    * RAM usage:
+        o 24 bytes per inode
+        o 12 bytes per data block
+        o 36 bytes per inode cache entry
+        o 32 bytes per data block cache entry
+    * Maximum filename size: 256 characters (no null terminator required)
+    * Disallowed filename characters: '/' and '\0'
+
+
+*** FUTURE ENHANCEMENTS
+
+    * API function to traverse a directory.
+    * Migrate corrupt files to the /lost+found directory during restore, rather
+      than discarding them from RAM.
+    * Error correction.
+    * Encryption.
+    * Compression.
+
+
+*** API
+
+struct nffs_file;
+
+/**
+ * Opens a file at the specified path.  The result of opening a nonexistent
+ * file depends on the access flags specified.  All intermediate directories
+ * must already exist.
+ *
+ * The mode strings passed to fopen() map to nffs_open()'s access flags as
+ * follows:
+ *   "r"  -  NFFS_ACCESS_READ
+ *   "r+" -  NFFS_ACCESS_READ | NFFS_ACCESS_WRITE
+ *   "w"  -  NFFS_ACCESS_WRITE | NFFS_ACCESS_TRUNCATE
+ *   "w+" -  NFFS_ACCESS_READ | NFFS_ACCESS_WRITE | NFFS_ACCESS_TRUNCATE
+ *   "a"  -  NFFS_ACCESS_WRITE | NFFS_ACCESS_APPEND
+ *   "a+" -  NFFS_ACCESS_READ | NFFS_ACCESS_WRITE | NFFS_ACCESS_APPEND
+ *
+ * @param out_file          On success, a pointer to the newly-created file
+ *                              handle gets written here.
+ * @param path              The path of the file to open.
+ * @param access_flags      Flags controlling file access; see above table.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_open(const char *path, uint8_t access_flags,
+              struct nffs_file **out_file);
+
+
+/**
+ * Closes the specified file and invalidates the file handle.  If the file has
+ * already been unlinked, and this is the last open handle to the file, this
+ * operation causes the file to be deleted from disk.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_close(struct nffs_file *file);
+
+
+/**
+ * Positions a file's read and write pointer at the specified offset.  The
+ * offset is expressed as the number of bytes from the start of the file (i.e.,
+ * seeking to offset 0 places the pointer at the first byte in the file).
+ *
+ * @param file              The file to reposition.
+ * @param offset            The 0-based file offset to seek to.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_seek(struct nffs_file *file, uint32_t offset);
+
+
+/**
+ * Retrieves the current read and write position of the specified open file.
+ *
+ * @param file              The file to query.
+ *
+ * @return                  The file offset, in bytes.
+ */
+uint32_t nffs_getpos(const struct nffs_file *file);
+
+
+/**
+ * Retrieves the current length of the specified open file.
+ *
+ * @param file              The file to query.
+ * @param out_len           On success, the number of bytes in the file gets
+ *                              written here.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_file_len(const struct nffs_file *file, uint32_t *out_len)
+
+
+/**
+ * Reads data from the specified file.  If more data is requested than remains
+ * in the file, all available data is retrieved.  Note: this type of short read
+ * results in a success return code.
+ *
+ * @param file              The file to read from.
+ * @param len               The number of bytes to attempt to read.
+ * @param out_data          The destination buffer to read into.
+ * @param out_len           On success, the number of bytes actually read gets
+ *                              written here.  Pass null if you don't care.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_read(struct nffs_file *file, uint32_t len, void *out_data,
+              uint32_t *out_len)
+
+
+/**
+ * Writes the supplied data to the current offset of the specified file handle.
+ *
+ * @param file              The file to write to.
+ * @param data              The data to write.
+ * @param len               The number of bytes to write.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_write(struct nffs_file *file, const void *data, int len);
+
+
+/**
+ * Unlinks the file or directory at the specified path.  If the path refers to
+ * a directory, all the directory's descendants are recursively unlinked.  Any
+ * open file handles refering to an unlinked file remain valid, and can be
+ * read from and written to.
+ *
+ * @path                    The path of the file or directory to unlink.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int nffs_unlink(const char *path);
+
+
+/**
+ * Performs a rename and / or move of the specified source path to the
+ * specified destination.  The source path can refer to either a file or a
+ * directory.  All intermediate directories in the destination path must
+ * already exist.  If the source path refers to a file, the destination path
+ * must contain a full filename path, rather than just the new parent
+ * directory.  If an object already exists at the specified destination path,
+ * this function causes it to be unlinked prior to the rename (i.e., the
+ * destination gets clobbered).
+ *
+ * @param from              The source path.
+ * @param to                The destination path.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int nffs_rename(const char *from, const char *to);
+
+
+/**
+ * Creates the directory represented by the specified path.  All intermediate
+ * directories must already exist.  The specified path must start with a '/'
+ * character.
+ *
+ * @param path                  The directory to create.
+ *
+ * @return                      0 on success;
+ *                              nonzero on failure.
+ */
+int nffs_mkdir(const char *path);
+
+
+/**
+ * Erases all the specified areas and initializes them with a clean nffs
+ * file system.
+ *
+ * @param area_descs        The set of areas to format.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int nffs_format(const struct nffs_area_desc *area_descs);
+
+
+/**
+ * Opens the directory at the specified path.  The directory's contents can be
+ * read with subsequent calls to nffs_readdir().  When you are done with the
+ * directory handle, close it with nffs_closedir().
+ *
+ * Unlinking files from the directory while it is open may result in
+ * unpredictable behavior.  New files can be created inside the directory.
+ *
+ * @param path                  The directory to open.
+ * @param out_dir               On success, points to the directory handle.
+ *
+ * @return                      0 on success;
+ *                              NFFS_ENOENT if the specified directory does not
+ *                                  exist;
+ *                              other nonzero on error.
+ */
+int nffs_opendir(const char *path, struct nffs_dir **out_dir);
+
+
+/**
+ * Reads the next entry in an open directory.
+ *
+ * @param dir                   The directory handle to read from.
+ * @param out_dirent            On success, points to the next child entry in
+ *                                  the specified directory.
+ *
+ * @return                      0 on success;
+ *                              NFFS_ENOENT if there are no more entries in the
+ *                                  parent directory;
+ *                              other nonzero on error.
+ */
+int nffs_readdir(struct nffs_dir *dir, struct nffs_dirent **out_dirent);
+
+
+/**
+ * Closes the specified directory handle.
+ *
+ * @param dir                   The directory to close.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int nffs_closedir(struct nffs_dir *dir);
+
+
+/**
+ * Retrieves the filename of the specified directory entry.  The retrieved
+ * filename is always null-terminated.  To ensure enough space to hold the full
+ * filename plus a null-termintor, a destination buffer of size
+ * (NFFS_FILENAME_MAX_LEN + 1) should be used.
+ *
+ * @param dirent                The directory entry to query.
+ * @param max_len               The size of the "out_name" character buffer.
+ * @param out_name              On success, the entry's filename is written
+ *                                  here; always null-terminated.
+ * @param out_name_len          On success, contains the actual length of the
+ *                                  filename, NOT including the
+ *                                  null-terminator.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int nffs_dirent_name(struct nffs_dirent *dirent, size_t max_len,
+                     char *out_name, uint8_t *out_name_len);
+
+
+/**
+ * Tells you whether the specified directory entry is a sub-directory or a
+ * regular file.
+ *
+ * @param dirent                The directory entry to query.
+ *
+ * @return                      1: The entry is a directory;
+ *                              0: The entry is a regular file.
+ */
+int nffs_dirent_is_dir(const struct nffs_dirent *dirent);
+
+/**
+ * Searches for a valid nffs file system among the specified areas.  This
+ * function succeeds if a file system is detected among any subset of the
+ * supplied areas.  If the area set does not contain a valid file system,
+ * a new one can be created via a call to nffs_format().
+ *
+ * @param area_descs        The area set to search.  This array must be
+ *                              terminated with a 0-length area.
+ *
+ * @return                  0 on success;
+ *                          NFFS_ECORRUPT if no valid file system was detected;
+ *                          other nonzero on error.
+ */
+int nffs_detect(const struct nffs_area_desc *area_descs);
+
+
+/**
+ * Indicates whether a valid filesystem has been initialized, either via
+ * detection or formatting.
+ *
+ * @return                  1 if a file system is present; 0 otherwise.
+ */
+int nffs_ready(void);
+
+
+/**
+ * Initializes the nffs memory and data structures.  This must be called before
+ * any nffs operations are attempted.
+ *
+ * @return                  0 on success; nonzero on error.
+ */
+int nffs_init(void);
diff --git a/include/nffs/nffs.h b/include/nffs/nffs.h
new file mode 100644
index 0000000..35baeb5
--- /dev/null
+++ b/include/nffs/nffs.h
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NFFS_
+#define H_NFFS_
+
+#include <stddef.h>
+#include <inttypes.h>
+#include "fs/fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NFFS_FILENAME_MAX_LEN   256  /* Does not require null terminator. */
+#define NFFS_MAX_AREAS          256
+
+struct nffs_config {
+    /** Maximum number of inodes; default=1024. */
+    uint32_t nc_num_inodes;
+
+    /** Maximum number of data blocks; default=4096. */
+    uint32_t nc_num_blocks;
+
+    /** Maximum number of open files; default=4. */
+    uint32_t nc_num_files;
+
+    /** Maximum number of open directories; default=4. */
+    uint32_t nc_num_dirs;
+
+    /** Inode cache size; default=4. */
+    uint32_t nc_num_cache_inodes;
+
+    /** Data block cache size; default=64. */
+    uint32_t nc_num_cache_blocks;
+};
+
+extern struct nffs_config nffs_config;
+
+struct nffs_area_desc {
+    uint32_t nad_offset;    /* Flash offset of start of area. */
+    uint32_t nad_length;    /* Size of area, in bytes. */
+    uint8_t nad_flash_id;   /* Logical flash id */
+};
+
+int nffs_init(void);
+int nffs_detect(const struct nffs_area_desc *area_descs);
+int nffs_format(const struct nffs_area_desc *area_descs);
+
+int nffs_misc_desc_from_flash_area(int idx, int *cnt, struct nffs_area_desc *nad);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/nffs/nffs_test.h b/include/nffs/nffs_test.h
new file mode 100644
index 0000000..bac660a
--- /dev/null
+++ b/include/nffs/nffs_test.h
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NFFS_TEST_
+#define H_NFFS_TEST_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int nffs_test_all(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/pkg.yml b/pkg.yml
new file mode 100644
index 0000000..276f456
--- /dev/null
+++ b/pkg.yml
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+pkg.name: fs/nffs
+pkg.description: Newtron Flash File System.
+pkg.author: "Apache Mynewt <dev@mynewt.apache.org>"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+    - file
+    - filesystem
+    - ffs
+
+pkg.deps:
+    - fs/fs
+    - util/crc
+    - hw/hal
+    - kernel/os
+    - test/testutil
+    - sys/flash_map
+pkg.req_apis:
+    - log
+    - stats
+
+pkg.init:
+    nffs_pkg_init: 200
diff --git a/src/nffs.c b/src/nffs.c
new file mode 100644
index 0000000..ace9174
--- /dev/null
+++ b/src/nffs.c
@@ -0,0 +1,809 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "sysinit/sysinit.h"
+#include "sysflash/sysflash.h"
+#include "bsp/bsp.h"
+#include "hal/hal_flash.h"
+#include "flash_map/flash_map.h"
+#include "os/os_mempool.h"
+#include "os/os_mutex.h"
+#include "os/os_malloc.h"
+#include "stats/stats.h"
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+#include "fs/fs_if.h"
+#include "disk/disk.h"
+
+struct nffs_area *nffs_areas;
+uint8_t nffs_num_areas;
+uint8_t nffs_scratch_area_idx;
+uint16_t nffs_block_max_data_sz;
+struct nffs_area_desc *nffs_current_area_descs;
+
+struct os_mempool nffs_file_pool;
+struct os_mempool nffs_dir_pool;
+struct os_mempool nffs_inode_entry_pool;
+struct os_mempool nffs_block_entry_pool;
+struct os_mempool nffs_cache_inode_pool;
+struct os_mempool nffs_cache_block_pool;
+
+void *nffs_file_mem;
+void *nffs_inode_mem;
+void *nffs_block_entry_mem;
+void *nffs_cache_inode_mem;
+void *nffs_cache_block_mem;
+void *nffs_dir_mem;
+
+struct nffs_inode_entry *nffs_root_dir;
+struct nffs_inode_entry *nffs_lost_found_dir;
+
+static struct os_mutex nffs_mutex;
+
+struct log nffs_log;
+
+static int nffs_open(const char *path, uint8_t access_flags,
+  struct fs_file **out_file);
+static int nffs_close(struct fs_file *fs_file);
+static int nffs_read(struct fs_file *fs_file, uint32_t len, void *out_data,
+  uint32_t *out_len);
+static int nffs_write(struct fs_file *fs_file, const void *data, int len);
+static int nffs_seek(struct fs_file *fs_file, uint32_t offset);
+static uint32_t nffs_getpos(const struct fs_file *fs_file);
+static int nffs_file_len(const struct fs_file *fs_file, uint32_t *out_len);
+static int nffs_unlink(const char *path);
+static int nffs_rename(const char *from, const char *to);
+static int nffs_mkdir(const char *path);
+static int nffs_opendir(const char *path, struct fs_dir **out_fs_dir);
+static int nffs_readdir(struct fs_dir *dir, struct fs_dirent **out_dirent);
+static int nffs_closedir(struct fs_dir *dir);
+static int nffs_dirent_name(const struct fs_dirent *fs_dirent, size_t max_len,
+  char *out_name, uint8_t *out_name_len);
+static int nffs_dirent_is_dir(const struct fs_dirent *fs_dirent);
+
+struct fs_ops nffs_ops = {
+    .f_open = nffs_open,
+    .f_close = nffs_close,
+    .f_read = nffs_read,
+    .f_write = nffs_write,
+
+    .f_seek = nffs_seek,
+    .f_getpos = nffs_getpos,
+    .f_filelen = nffs_file_len,
+
+    .f_unlink = nffs_unlink,
+    .f_rename = nffs_rename,
+    .f_mkdir = nffs_mkdir,
+
+    .f_opendir = nffs_opendir,
+    .f_readdir = nffs_readdir,
+    .f_closedir = nffs_closedir,
+
+    .f_dirent_name = nffs_dirent_name,
+    .f_dirent_is_dir = nffs_dirent_is_dir,
+
+    .f_name = "nffs"
+};
+
+STATS_SECT_DECL(nffs_stats) nffs_stats;
+STATS_NAME_START(nffs_stats)
+    STATS_NAME(nffs_stats, nffs_hashcnt_ins)
+    STATS_NAME(nffs_stats, nffs_hashcnt_rm)
+    STATS_NAME(nffs_stats, nffs_object_count)
+    STATS_NAME(nffs_stats, nffs_iocnt_read)
+    STATS_NAME(nffs_stats, nffs_iocnt_write)
+    STATS_NAME(nffs_stats, nffs_gccnt)
+    STATS_NAME(nffs_stats, nffs_readcnt_data)
+    STATS_NAME(nffs_stats, nffs_readcnt_block)
+    STATS_NAME(nffs_stats, nffs_readcnt_crc)
+    STATS_NAME(nffs_stats, nffs_readcnt_copy)
+    STATS_NAME(nffs_stats, nffs_readcnt_format)
+    STATS_NAME(nffs_stats, nffs_readcnt_gccollate)
+    STATS_NAME(nffs_stats, nffs_readcnt_inode)
+    STATS_NAME(nffs_stats, nffs_readcnt_inodeent)
+    STATS_NAME(nffs_stats, nffs_readcnt_rename)
+    STATS_NAME(nffs_stats, nffs_readcnt_update)
+    STATS_NAME(nffs_stats, nffs_readcnt_filename)
+    STATS_NAME(nffs_stats, nffs_readcnt_object)
+    STATS_NAME(nffs_stats, nffs_readcnt_detect)
+STATS_NAME_END(nffs_stats)
+
+static void
+nffs_lock(void)
+{
+    int rc;
+
+    rc = os_mutex_pend(&nffs_mutex, 0xffffffff);
+    assert(rc == 0 || rc == OS_NOT_STARTED);
+}
+
+static void
+nffs_unlock(void)
+{
+    int rc;
+
+    rc = os_mutex_release(&nffs_mutex);
+    assert(rc == 0 || rc == OS_NOT_STARTED);
+}
+
+static int
+nffs_stats_init(void)
+{
+    int rc;
+
+    rc = stats_init_and_reg(
+                    STATS_HDR(nffs_stats),
+                    STATS_SIZE_INIT_PARMS(nffs_stats, STATS_SIZE_32),
+                    STATS_NAME_INIT_PARMS(nffs_stats),
+                    "nffs_stats");
+    if (rc) {
+        if (rc < 0) {
+            /* multiple initializations are okay */
+            rc = 0;
+        } else {
+            rc = FS_EOS;
+        }
+    }
+    return rc;
+}
+
+/**
+ * Opens a file at the specified path.  The result of opening a nonexistent
+ * file depends on the access flags specified.  All intermediate directories
+ * must already exist.
+ *
+ * The mode strings passed to fopen() map to nffs_open()'s access flags as
+ * follows:
+ *   "r"  -  FS_ACCESS_READ
+ *   "r+" -  FS_ACCESS_READ | FS_ACCESS_WRITE
+ *   "w"  -  FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE
+ *   "w+" -  FS_ACCESS_READ | FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE
+ *   "a"  -  FS_ACCESS_WRITE | FS_ACCESS_APPEND
+ *   "a+" -  FS_ACCESS_READ | FS_ACCESS_WRITE | FS_ACCESS_APPEND
+ *
+ * @param path              The path of the file to open.
+ * @param access_flags      Flags controlling file access; see above table.
+ * @param out_file          On success, a pointer to the newly-created file
+ *                              handle gets written here.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_open(const char *path, uint8_t access_flags, struct fs_file **out_fs_file)
+{
+    int rc;
+    struct nffs_file *out_file;
+    char *filepath = NULL;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    filepath = disk_filepath_from_path(path);
+
+    rc = nffs_file_open(&out_file, filepath, access_flags);
+    if (rc != 0) {
+        goto done;
+    }
+    *out_fs_file = (struct fs_file *)out_file;
+done:
+    if (filepath) {
+        free(filepath);
+    }
+    nffs_unlock();
+    if (rc != 0) {
+        *out_fs_file = NULL;
+    }
+    return rc;
+}
+
+/**
+ * Closes the specified file and invalidates the file handle.  If the file has
+ * already been unlinked, and this is the last open handle to the file, this
+ * operation causes the file to be deleted from disk.
+ *
+ * @param file              The file handle to close.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_close(struct fs_file *fs_file)
+{
+    int rc;
+    struct nffs_file *file = (struct nffs_file *)fs_file;
+
+    if (file == NULL) {
+        return 0;
+    }
+
+    nffs_lock();
+    rc = nffs_file_close(file);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Positions a file's read and write pointer at the specified offset.  The
+ * offset is expressed as the number of bytes from the start of the file (i.e.,
+ * seeking to offset 0 places the pointer at the first byte in the file).
+ *
+ * @param file              The file to reposition.
+ * @param offset            The 0-based file offset to seek to.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_seek(struct fs_file *fs_file, uint32_t offset)
+{
+    int rc;
+    struct nffs_file *file = (struct nffs_file *)fs_file;
+
+    nffs_lock();
+    rc = nffs_file_seek(file, offset);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Retrieves the current read and write position of the specified open file.
+ *
+ * @param file              The file to query.
+ *
+ * @return                  The file offset, in bytes.
+ */
+static uint32_t
+nffs_getpos(const struct fs_file *fs_file)
+{
+    uint32_t offset;
+    const struct nffs_file *file = (const struct nffs_file *)fs_file;
+
+    nffs_lock();
+    offset = file->nf_offset;
+    nffs_unlock();
+
+    return offset;
+}
+
+/**
+ * Retrieves the current length of the specified open file.
+ *
+ * @param file              The file to query.
+ * @param out_len           On success, the number of bytes in the file gets
+ *                              written here.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_file_len(const struct fs_file *fs_file, uint32_t *out_len)
+{
+    int rc;
+    const struct nffs_file *file = (const struct nffs_file *)fs_file;
+
+    nffs_lock();
+    rc = nffs_inode_data_len(file->nf_inode_entry, out_len);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Reads data from the specified file.  If more data is requested than remains
+ * in the file, all available data is retrieved and a success code is returned.
+ *
+ * @param file              The file to read from.
+ * @param len               The number of bytes to attempt to read.
+ * @param out_data          The destination buffer to read into.
+ * @param out_len           On success, the number of bytes actually read gets
+ *                              written here.  Pass null if you don't care.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_read(struct fs_file *fs_file, uint32_t len, void *out_data,
+          uint32_t *out_len)
+{
+    int rc;
+    struct nffs_file *file = (struct nffs_file *)fs_file;
+
+    nffs_lock();
+    rc = nffs_file_read(file, len, out_data, out_len);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Writes the supplied data to the current offset of the specified file handle.
+ *
+ * @param file              The file to write to.
+ * @param data              The data to write.
+ * @param len               The number of bytes to write.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_write(struct fs_file *fs_file, const void *data, int len)
+{
+    int rc;
+    struct nffs_file *file = (struct nffs_file *)fs_file;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    rc = nffs_write_to_file(file, data, len);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rc = 0;
+
+done:
+    nffs_unlock();
+    return rc;
+}
+
+/**
+ * Unlinks the file or directory at the specified path.  If the path refers to
+ * a directory, all the directory's descendants are recursively unlinked.  Any
+ * open file handles refering to an unlinked file remain valid, and can be
+ * read from and written to.
+ *
+ * @path                    The path of the file or directory to unlink.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+nffs_unlink(const char *path)
+{
+    int rc;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    rc = nffs_path_unlink(path);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rc = 0;
+
+done:
+    nffs_unlock();
+    return rc;
+}
+
+/**
+ * Performs a rename and / or move of the specified source path to the
+ * specified destination.  The source path can refer to either a file or a
+ * directory.  All intermediate directories in the destination path must
+ * already exist.  If the source path refers to a file, the destination path
+ * must contain a full filename path, rather than just the new parent
+ * directory.  If an object already exists at the specified destination path,
+ * this function causes it to be unlinked prior to the rename (i.e., the
+ * destination gets clobbered).
+ *
+ * @param from              The source path.
+ * @param to                The destination path.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+static int
+nffs_rename(const char *from, const char *to)
+{
+    int rc;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    rc = nffs_path_rename(from, to);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rc = 0;
+
+done:
+    nffs_unlock();
+    return rc;
+}
+
+/**
+ * Creates the directory represented by the specified path.  All intermediate
+ * directories must already exist.  The specified path must start with a '/'
+ * character.
+ *
+ * @param path                  The name of the directory to create.
+ *
+ * @return                      0 on success;
+ *                              nonzero on failure.
+ */
+static int
+nffs_mkdir(const char *path)
+{
+    int rc;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    rc = nffs_path_new_dir(path, NULL);
+    if (rc != 0) {
+        goto done;
+    }
+
+done:
+    nffs_unlock();
+    return rc;
+}
+
+/**
+ * Opens the directory at the specified path.  The directory's contents can be
+ * read with subsequent calls to nffs_readdir().  When you are done with the
+ * directory handle, close it with nffs_closedir().
+ *
+ * Unlinking files from the directory while it is open may result in
+ * unpredictable behavior.  New files can be created inside the directory.
+ *
+ * @param path                  The name of the directory to open.
+ * @param out_dir               On success, points to the directory handle.
+ *
+ * @return                      0 on success;
+ *                              FS_ENOENT if the specified directory does not
+ *                                  exist;
+ *                              other nonzero on error.
+ */
+static int
+nffs_opendir(const char *path, struct fs_dir **out_fs_dir)
+{
+    int rc;
+    struct nffs_dir **out_dir = (struct nffs_dir **)out_fs_dir;
+    char *filepath = NULL;
+
+    nffs_lock();
+
+    if (!nffs_misc_ready()) {
+        rc = FS_EUNINIT;
+        goto done;
+    }
+
+    filepath = disk_filepath_from_path(path);
+
+    rc = nffs_dir_open(filepath, out_dir);
+
+done:
+    if (filepath) {
+        free(filepath);
+    }
+    nffs_unlock();
+    if (rc != 0) {
+        *out_dir = NULL;
+    }
+    return rc;
+}
+
+/**
+ * Reads the next entry in an open directory.
+ *
+ * @param dir                   The directory handle to read from.
+ * @param out_dirent            On success, points to the next child entry in
+ *                                  the specified directory.
+ *
+ * @return                      0 on success;
+ *                              FS_ENOENT if there are no more entries in the
+ *                                  parent directory;
+ *                              other nonzero on error.
+ */
+static int
+nffs_readdir(struct fs_dir *fs_dir, struct fs_dirent **out_fs_dirent)
+{
+    int rc;
+    struct nffs_dir *dir = (struct nffs_dir *)fs_dir;
+    struct nffs_dirent **out_dirent = (struct nffs_dirent **)out_fs_dirent;
+
+    nffs_lock();
+    rc = nffs_dir_read(dir, out_dirent);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Closes the specified directory handle.
+ *
+ * @param dir                   The name of the directory to close.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_closedir(struct fs_dir *fs_dir)
+{
+    int rc;
+    struct nffs_dir *dir = (struct nffs_dir *)fs_dir;
+
+    nffs_lock();
+    rc = nffs_dir_close(dir);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Retrieves the filename of the specified directory entry.  The retrieved
+ * filename is always null-terminated.  To ensure enough space to hold the full
+ * filename plus a null-termintor, a destination buffer of size
+ * (NFFS_FILENAME_MAX_LEN + 1) should be used.
+ *
+ * @param dirent                The directory entry to query.
+ * @param max_len               The size of the "out_name" character buffer.
+ * @param out_name              On success, the entry's filename is written
+ *                                  here; always null-terminated.
+ * @param out_name_len          On success, contains the actual length of the
+ *                                  filename, NOT including the
+ *                                  null-terminator.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_dirent_name(const struct fs_dirent *fs_dirent, size_t max_len,
+                 char *out_name, uint8_t *out_name_len)
+{
+    int rc;
+    struct nffs_dirent *dirent = (struct nffs_dirent *)fs_dirent;
+
+    nffs_lock();
+
+    assert(dirent != NULL && dirent->nde_inode_entry != NULL);
+    rc = nffs_inode_read_filename(dirent->nde_inode_entry, max_len, out_name,
+                                  out_name_len);
+
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Tells you whether the specified directory entry is a sub-directory or a
+ * regular file.
+ *
+ * @param dirent                The directory entry to query.
+ *
+ * @return                      1: The entry is a directory;
+ *                              0: The entry is a regular file.
+ */
+static int
+nffs_dirent_is_dir(const struct fs_dirent *fs_dirent)
+{
+    uint32_t id;
+    const struct nffs_dirent *dirent = (const struct nffs_dirent *)fs_dirent;
+
+    nffs_lock();
+
+    assert(dirent != NULL && dirent->nde_inode_entry != NULL);
+    id = dirent->nde_inode_entry->nie_hash_entry.nhe_id;
+
+    nffs_unlock();
+
+    return nffs_hash_id_is_dir(id);
+}
+
+/**
+ * Erases all the specified areas and initializes them with a clean nffs
+ * file system.
+ *
+ * @param area_descs        The set of areas to format.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int
+nffs_format(const struct nffs_area_desc *area_descs)
+{
+    int rc;
+
+    nffs_lock();
+    rc = nffs_format_full(area_descs);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Searches for a valid nffs file system among the specified areas.  This
+ * function succeeds if a file system is detected among any subset of the
+ * supplied areas.  If the area set does not contain a valid file system,
+ * a new one can be created via a separate call to nffs_format().
+ *
+ * @param area_descs        The area set to search.  This array must be
+ *                              terminated with a 0-length area.
+ *
+ * @return                  0 on success;
+ *                          FS_ECORRUPT if no valid file system was detected;
+ *                          other nonzero on error.
+ */
+int
+nffs_detect(const struct nffs_area_desc *area_descs)
+{
+    int rc;
+
+    nffs_lock();
+    rc = nffs_restore_full(area_descs);
+    nffs_unlock();
+
+    return rc;
+}
+
+/**
+ * Initializes internal nffs memory and data structures.  This must be called
+ * before any nffs operations are attempted.
+ *
+ * @return                  0 on success; nonzero on error.
+ */
+int
+nffs_init(void)
+{
+    int rc;
+
+    nffs_config_init();
+
+    nffs_cache_clear();
+
+    rc = nffs_stats_init();
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mutex_init(&nffs_mutex);
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    free(nffs_file_mem);
+    nffs_file_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_files, sizeof (struct nffs_file)));
+    if (nffs_file_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    free(nffs_inode_mem);
+    nffs_inode_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_inodes,
+                        sizeof (struct nffs_inode_entry)));
+    if (nffs_inode_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    free(nffs_block_entry_mem);
+    nffs_block_entry_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_blocks,
+                         sizeof (struct nffs_hash_entry)));
+    if (nffs_block_entry_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    free(nffs_cache_inode_mem);
+    nffs_cache_inode_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_cache_inodes,
+                         sizeof (struct nffs_cache_inode)));
+    if (nffs_cache_inode_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    free(nffs_cache_block_mem);
+    nffs_cache_block_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_cache_blocks,
+                         sizeof (struct nffs_cache_block)));
+    if (nffs_cache_block_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    free(nffs_dir_mem);
+    nffs_dir_mem = malloc(
+        OS_MEMPOOL_BYTES(nffs_config.nc_num_dirs,
+                         sizeof (struct nffs_dir)));
+    if (nffs_dir_mem == NULL) {
+        return FS_ENOMEM;
+    }
+
+    rc = nffs_misc_reset();
+    if (rc != 0) {
+        return rc;
+    }
+
+    fs_register(&nffs_ops);
+    return 0;
+}
+
+void
+nffs_pkg_init(void)
+{
+    struct nffs_area_desc descs[MYNEWT_VAL(NFFS_NUM_AREAS) + 1];
+    int cnt;
+    int rc;
+
+    /* Ensure this function only gets called by sysinit. */
+    SYSINIT_ASSERT_ACTIVE();
+
+    /* Initialize nffs's internal state. */
+    rc = nffs_init();
+    SYSINIT_PANIC_ASSERT(rc == 0);
+
+    /* Convert the set of flash blocks we intend to use for nffs into an array
+     * of nffs area descriptors.
+     */
+    cnt = MYNEWT_VAL(NFFS_NUM_AREAS);
+    rc = nffs_misc_desc_from_flash_area(
+        MYNEWT_VAL(NFFS_FLASH_AREA), &cnt, descs);
+    SYSINIT_PANIC_ASSERT(rc == 0);
+
+    /* Attempt to restore an existing nffs file system from flash. */
+    rc = nffs_detect(descs);
+    switch (rc) {
+    case 0:
+        break;
+
+    case FS_ECORRUPT:
+        /* No valid nffs instance detected; act based on configued detection
+         * failure policy.
+         */
+        switch (MYNEWT_VAL(NFFS_DETECT_FAIL)) {
+        case NFFS_DETECT_FAIL_IGNORE:
+            break;
+
+        case NFFS_DETECT_FAIL_FORMAT:
+            rc = nffs_format(descs);
+            SYSINIT_PANIC_ASSERT(rc == 0);
+            break;
+
+        default:
+            SYSINIT_PANIC();
+            break;
+        }
+        break;
+
+    default:
+        SYSINIT_PANIC();
+        break;
+    }
+}
diff --git a/src/nffs_area.c b/src/nffs_area.c
new file mode 100644
index 0000000..87b0dbf
--- /dev/null
+++ b/src/nffs_area.c
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+
+static void
+nffs_area_set_magic(struct nffs_disk_area *disk_area)
+{
+    disk_area->nda_magic[0] = NFFS_AREA_MAGIC0;
+    disk_area->nda_magic[1] = NFFS_AREA_MAGIC1;
+    disk_area->nda_magic[2] = NFFS_AREA_MAGIC2;
+    disk_area->nda_magic[3] = NFFS_AREA_MAGIC3;
+}
+
+int
+nffs_area_magic_is_set(const struct nffs_disk_area *disk_area)
+{
+    return disk_area->nda_magic[0] == NFFS_AREA_MAGIC0 &&
+           disk_area->nda_magic[1] == NFFS_AREA_MAGIC1 &&
+           disk_area->nda_magic[2] == NFFS_AREA_MAGIC2 &&
+           disk_area->nda_magic[3] == NFFS_AREA_MAGIC3;
+}
+
+int
+nffs_area_is_scratch(const struct nffs_disk_area *disk_area)
+{
+    return nffs_area_magic_is_set(disk_area) &&
+           disk_area->nda_id == NFFS_AREA_ID_NONE;
+}
+
+int
+nffs_area_is_current_version(const struct nffs_disk_area *disk_area)
+{
+    return disk_area->nda_ver == NFFS_AREA_VER;
+}
+
+void
+nffs_area_to_disk(const struct nffs_area *area,
+                  struct nffs_disk_area *out_disk_area)
+{
+    memset(out_disk_area, 0, sizeof *out_disk_area);
+    nffs_area_set_magic(out_disk_area);
+    out_disk_area->nda_length = area->na_length;
+    out_disk_area->nda_ver = NFFS_AREA_VER;
+    out_disk_area->nda_gc_seq = area->na_gc_seq;
+    out_disk_area->nda_id = area->na_id;
+}
+
+uint32_t
+nffs_area_free_space(const struct nffs_area *area)
+{
+    return area->na_length - area->na_cur;
+}
+
+/**
+ * Finds a corrupt scratch area.  An area is indentified as a corrupt scratch
+ * area if it and another area share the same ID.  Among two areas with the
+ * same ID, the one with fewer bytes written is the corrupt scratch area.
+ *
+ * @param out_good_idx          On success, the index of the good area (longer
+ *                                  of the two areas) gets written here.
+ * @param out_bad_idx           On success, the index of the corrupt scratch
+ *                                  area gets written here.
+ *
+ * @return                      0 if a corrupt scratch area was identified;
+ *                              FS_ENOENT if one was not found.
+ */
+int
+nffs_area_find_corrupt_scratch(uint16_t *out_good_idx, uint16_t *out_bad_idx)
+{
+    const struct nffs_area *iarea;
+    const struct nffs_area *jarea;
+    int i;
+    int j;
+
+    for (i = 0; i < nffs_num_areas; i++) {
+        iarea = nffs_areas + i;
+        for (j = i + 1; j < nffs_num_areas; j++) {
+            jarea = nffs_areas + j;
+
+            if (jarea->na_id == iarea->na_id) {
+                /* Found a duplicate.  The shorter of the two areas should be
+                 * used as scratch.
+                 */
+                if (iarea->na_cur < jarea->na_cur) {
+                    *out_good_idx = j;
+                    *out_bad_idx = i;
+                } else {
+                    *out_good_idx = i;
+                    *out_bad_idx = j;
+                }
+
+                return 0;
+            }
+        }
+    }
+
+    return FS_ENOENT;
+}
diff --git a/src/nffs_block.c b/src/nffs_block.c
new file mode 100644
index 0000000..f7d5847
--- /dev/null
+++ b/src/nffs_block.c
@@ -0,0 +1,456 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+#include "testutil/testutil.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+struct nffs_hash_entry *
+nffs_block_entry_alloc(void)
+{
+    struct nffs_hash_entry *entry;
+
+    entry = os_memblock_get(&nffs_block_entry_pool);
+    if (entry != NULL) {
+        memset(entry, 0, sizeof *entry);
+    }
+
+    return entry;
+}
+
+void
+nffs_block_entry_free(struct nffs_hash_entry *block_entry)
+{
+    assert(nffs_hash_id_is_block(block_entry->nhe_id));
+    os_memblock_put(&nffs_block_entry_pool, block_entry);
+}
+
+/**
+ * Allocates a block entry.  If allocation fails due to memory exhaustion,
+ * garbage collection is performed and the allocation is retried.  This
+ * process is repeated until allocation is successful or all areas have been
+ * garbage collected.
+ *
+ * @param out_block_entry           On success, the address of the allocated
+ *                                      block gets written here.
+ *
+ * @return                          0 on successful allocation;
+ *                                  FS_ENOMEM on memory exhaustion;
+ *                                  other nonzero on garbage collection error.
+ */
+int
+nffs_block_entry_reserve(struct nffs_hash_entry **out_block_entry)
+{
+    int rc;
+
+    do {
+        *out_block_entry = nffs_block_entry_alloc();
+    } while (nffs_misc_gc_if_oom(*out_block_entry, &rc));
+
+    return rc;
+}
+
+/**
+ * Reads a data block header from flash.
+ *
+ * @param area_idx              The index of the area to read from.
+ * @param area_offset           The offset within the area to read from.
+ * @param out_disk_block        On success, the block header is writteh here.
+ *
+ * @return                      0 on success;
+ *                              FS_EOFFSET on an attempt to read an invalid
+ *                                  address range;
+ *                              FS_EHW on flash error;
+ *                              FS_EUNEXP if the specified disk location does
+ *                                  not contain a block.
+ */
+int
+nffs_block_read_disk(uint8_t area_idx, uint32_t area_offset,
+                     struct nffs_disk_block *out_disk_block)
+{
+    int rc;
+
+    STATS_INC(nffs_stats, nffs_readcnt_block);
+    rc = nffs_flash_read(area_idx, area_offset, out_disk_block,
+                         sizeof *out_disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+    if (!nffs_hash_id_is_block(out_disk_block->ndb_id)) {
+        return FS_EUNEXP;
+    }
+
+    return 0;
+}
+
+/**
+ * Writes the specified data block to a suitable location in flash.
+ *
+ * @param disk_block            Points to the disk block to write.
+ * @param data                  The contents of the data block.
+ * @param out_area_idx          On success, contains the index of the area
+ *                                  written to.
+ * @param out_area_offset       On success, contains the offset within the area
+ *                                  written to.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_block_write_disk(const struct nffs_disk_block *disk_block,
+                      const void *data,
+                      uint8_t *out_area_idx, uint32_t *out_area_offset)
+{
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    rc = nffs_misc_reserve_space(sizeof *disk_block + disk_block->ndb_data_len,
+                                &area_idx, &area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_flash_write(area_idx, area_offset, disk_block,
+                         sizeof *disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (disk_block->ndb_data_len > 0) {
+        rc = nffs_flash_write(area_idx, area_offset + sizeof *disk_block,
+                             data, disk_block->ndb_data_len);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    *out_area_idx = area_idx;
+    *out_area_offset = area_offset;
+
+    ASSERT_IF_TEST(nffs_crc_disk_block_validate(disk_block, area_idx,
+                                               area_offset) == 0);
+
+    return 0;
+}
+
+static void
+nffs_block_from_disk_no_ptrs(struct nffs_block *out_block,
+                             const struct nffs_disk_block *disk_block)
+{
+    out_block->nb_seq = disk_block->ndb_seq;
+    out_block->nb_inode_entry = NULL;
+    out_block->nb_prev = NULL;
+    out_block->nb_data_len = disk_block->ndb_data_len;
+}
+
+/**
+ * Constructs a block representation from a disk record.  If the disk block
+ * references other objects (inode or previous data block), the resulting block
+ * object is populated with pointers to the referenced objects.  If the any
+ * referenced objects are not present in the NFFS RAM representation, this
+ * indicates file system corruption.  In this case, the resulting block is
+ * populated with all valid references, and an FS_ECORRUPT code is returned.
+ *
+ * @param out_block             The resulting block is written here (regardless
+ *                                  of this function's return code).
+ * @param disk_block            The source disk record to convert.
+ *
+ * @return                      0 if the block was successfully constructed;
+ *                              FS_ECORRUPT if one or more pointers could not
+ *                                  be filled in due to file system corruption.
+ */
+static int
+nffs_block_from_disk(struct nffs_block *out_block,
+                     const struct nffs_disk_block *disk_block)
+{
+    int rc;
+
+    rc = 0;
+
+    nffs_block_from_disk_no_ptrs(out_block, disk_block);
+
+    out_block->nb_inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
+    if (out_block->nb_inode_entry == NULL) {
+        rc = FS_ECORRUPT;
+    }
+
+    if (disk_block->ndb_prev_id != NFFS_ID_NONE) {
+        out_block->nb_prev = nffs_hash_find_block(disk_block->ndb_prev_id);
+        if (out_block->nb_prev == NULL) {
+            rc = FS_ECORRUPT;
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Constructs a disk-representation of the specified data block.
+ *
+ * @param block                 The source block to convert.
+ * @param out_disk_block        The disk block to write to.
+ */
+void
+nffs_block_to_disk(const struct nffs_block *block,
+                   struct nffs_disk_block *out_disk_block)
+{
+    assert(block->nb_inode_entry != NULL);
+
+    memset(out_disk_block, 0, sizeof *out_disk_block);
+    out_disk_block->ndb_id = block->nb_hash_entry->nhe_id;
+    out_disk_block->ndb_seq = block->nb_seq;
+    out_disk_block->ndb_inode_id =
+        block->nb_inode_entry->nie_hash_entry.nhe_id;
+    if (block->nb_prev == NULL) {
+        out_disk_block->ndb_prev_id = NFFS_ID_NONE;
+    } else {
+        out_disk_block->ndb_prev_id = block->nb_prev->nhe_id;
+    }
+    out_disk_block->ndb_data_len = block->nb_data_len;
+}
+
+/**
+ * Deletes the specified block entry from the nffs RAM representation.
+ *
+ * @param block_entry           The block entry to delete.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_block_delete_from_ram(struct nffs_hash_entry *block_entry)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_block block;
+    int rc;
+
+    if (nffs_hash_entry_is_dummy(block_entry)) {
+        /*
+         * it's very limited to what we can do here as the block doesn't have
+         * any way to get to the inode via hash entry. Just delete the
+         * block and return FS_ECORRUPT
+         */
+        nffs_hash_remove(block_entry);
+        nffs_block_entry_free(block_entry);
+        return FS_ECORRUPT;
+    }
+
+    rc = nffs_block_from_hash_entry(&block, block_entry);
+    if (rc == 0 || rc == FS_ECORRUPT) {
+        /* If file system corruption was detected, the resulting block is still
+         * valid and can be removed from RAM.
+         * Note that FS_CORRUPT can occur because the owning inode was not
+         * found in the hash table - this can occur during the sweep where
+         * the inodes were deleted ahead of the blocks.
+         */
+        inode_entry = block.nb_inode_entry;
+        if (inode_entry != NULL &&
+            inode_entry->nie_last_block_entry == block_entry) {
+
+            inode_entry->nie_last_block_entry = block.nb_prev;
+        }
+
+        nffs_hash_remove(block_entry);
+        nffs_block_entry_free(block_entry);
+    }
+
+    return rc;
+}
+
+/**
+ * Determines if a particular block can be found in RAM by following a chain of
+ * previous block pointers, starting with the specified hash entry.
+ *
+ * @param start                 The block entry at which to start the search.
+ * @param sought_id             The ID of the block to search for.
+ *
+ * @return                      0 if the sought after ID was found;
+ *                              FS_ENOENT if the ID was not found;
+ *                              Other FS code on error.
+ */
+int
+nffs_block_find_predecessor(struct nffs_hash_entry *start, uint32_t sought_id)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_disk_block disk_block;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    entry = start;
+    while (entry != NULL && entry->nhe_id != sought_id) {
+        nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset);
+        rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
+        if (rc != 0) {
+            return rc;
+        }
+
+        if (disk_block.ndb_prev_id == NFFS_ID_NONE) {
+            entry = NULL;
+        } else {
+            entry = nffs_hash_find(disk_block.ndb_prev_id);
+        }
+    }
+
+    if (entry == NULL) {
+        rc = FS_ENOENT;
+    } else {
+        rc = 0;
+    }
+
+    return rc;
+}
+
+/**
+ * Constructs a full data block representation from the specified minimal
+ * block entry.  However, the resultant block's pointers are set to null,
+ * rather than populated via hash table lookups.  This behavior is useful when
+ * the RAM representation has not been fully constructed yet.
+ *
+ * @param out_block             On success, this gets populated with the data
+ *                                  block information.
+ * @param block_entry           The source block entry to convert.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_block_from_hash_entry_no_ptrs(struct nffs_block *out_block,
+                                   struct nffs_hash_entry *block_entry)
+{
+    struct nffs_disk_block disk_block;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    assert(nffs_hash_id_is_block(block_entry->nhe_id));
+
+    memset(out_block, 0, sizeof *out_block);
+
+    if (nffs_hash_entry_is_dummy(block_entry)) {
+        /*
+         * We can't read this from disk so we'll be missing filling in anything
+         * not already in inode_entry (e.g., prev_id).
+         */
+        out_block->nb_hash_entry = block_entry;
+        return FS_ENOENT; /* let caller know it's a partial inode_entry */
+    }
+
+    nffs_flash_loc_expand(block_entry->nhe_flash_loc, &area_idx, &area_offset);
+    rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+
+    out_block->nb_hash_entry = block_entry;
+    nffs_block_from_disk_no_ptrs(out_block, &disk_block);
+
+    return 0;
+}
+
+/**
+ * Constructs a block representation from a minimal block hash entry.  If the
+ * hash entry references other objects (inode or previous data block), the
+ * resulting block object is populated with pointers to the referenced objects.
+ * If the any referenced objects are not present in the NFFS RAM
+ * representation, this indicates file system corruption.  In this case, the
+ * resulting block is populated with all valid references, and an FS_ECORRUPT
+ * code is returned.
+ *
+ * @param out_block             On success, this gets populated with the data
+ *                                  block information.
+ * @param block_entry           The source block entry to convert.
+ *
+ * @return                      0 on success;
+ *                              FS_ECORRUPT if one or more pointers could not
+ *                                  be filled in due to file system corruption;
+ *                              FS_EOFFSET on an attempt to read an invalid
+ *                                  address range;
+ *                              FS_EHW on flash error;
+ *                              FS_EUNEXP if the specified disk location does
+ *                                  not contain a block.
+ */
+int
+nffs_block_from_hash_entry(struct nffs_block *out_block,
+                           struct nffs_hash_entry *block_entry)
+{
+    struct nffs_disk_block disk_block;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    assert(nffs_hash_id_is_block(block_entry->nhe_id));
+
+    if (nffs_block_is_dummy(block_entry)) {
+        out_block->nb_hash_entry = block_entry;
+        out_block->nb_inode_entry = NULL;
+        out_block->nb_prev = NULL;
+        /*
+         * Dummy block added when inode was read in before real block
+         * (see nffs_restore_inode()). Return success (because there's 
+         * too many places that ned to check for this,
+         * but it's the responsibility fo the upstream code to check
+         * whether this is still a dummy entry.  XXX
+         */
+        return 0;
+        /*return FS_ENOENT;*/
+    }
+    nffs_flash_loc_expand(block_entry->nhe_flash_loc, &area_idx, &area_offset);
+    rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+
+    out_block->nb_hash_entry = block_entry;
+    rc = nffs_block_from_disk(out_block, &disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+int
+nffs_block_read_data(const struct nffs_block *block, uint16_t offset,
+                     uint16_t length, void *dst)
+{
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    nffs_flash_loc_expand(block->nb_hash_entry->nhe_flash_loc,
+                         &area_idx, &area_offset);
+    area_offset += sizeof (struct nffs_disk_block);
+    area_offset += offset;
+
+    STATS_INC(nffs_stats, nffs_readcnt_data);
+    rc = nffs_flash_read(area_idx, area_offset, dst, length);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+int
+nffs_block_is_dummy(struct nffs_hash_entry *entry)
+{
+    return (entry->nhe_flash_loc == NFFS_FLASH_LOC_NONE);
+}
diff --git a/src/nffs_cache.c b/src/nffs_cache.c
new file mode 100644
index 0000000..b50750f
--- /dev/null
+++ b/src/nffs_cache.c
@@ -0,0 +1,511 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+TAILQ_HEAD(nffs_cache_inode_list, nffs_cache_inode);
+static struct nffs_cache_inode_list nffs_cache_inode_list =
+    TAILQ_HEAD_INITIALIZER(nffs_cache_inode_list);
+
+static void nffs_cache_reclaim_blocks(void);
+
+static struct nffs_cache_block *
+nffs_cache_block_alloc(void)
+{
+    struct nffs_cache_block *entry;
+
+    entry = os_memblock_get(&nffs_cache_block_pool);
+    if (entry != NULL) {
+        memset(entry, 0, sizeof *entry);
+    }
+
+    return entry;
+}
+
+static void
+nffs_cache_block_free(struct nffs_cache_block *entry)
+{
+    if (entry != NULL) {
+        os_memblock_put(&nffs_cache_block_pool, entry);
+    }
+}
+
+static struct nffs_cache_block *
+nffs_cache_block_acquire(void)
+{
+    struct nffs_cache_block *cache_block;
+
+    cache_block = nffs_cache_block_alloc();
+    if (cache_block == NULL) {
+        nffs_cache_reclaim_blocks();
+        cache_block = nffs_cache_block_alloc();
+    }
+
+    assert(cache_block != NULL);
+
+    return cache_block;
+}
+
+static int
+nffs_cache_block_populate(struct nffs_cache_block *cache_block,
+                          struct nffs_hash_entry *block_entry,
+                          uint32_t end_offset)
+{
+    int rc;
+
+    rc = nffs_block_from_hash_entry(&cache_block->ncb_block, block_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    cache_block->ncb_file_offset = end_offset -
+                                   cache_block->ncb_block.nb_data_len;
+
+    return 0;
+}
+
+static struct nffs_cache_inode *
+nffs_cache_inode_alloc(void)
+{
+    struct nffs_cache_inode *entry;
+
+    entry = os_memblock_get(&nffs_cache_inode_pool);
+    if (entry != NULL) {
+        memset(entry, 0, sizeof *entry);
+        TAILQ_INIT(&entry->nci_block_list);
+    }
+
+    return entry;
+}
+
+static void
+nffs_cache_inode_free_blocks(struct nffs_cache_inode *cache_inode)
+{
+    struct nffs_cache_block *cache_block;
+
+    while ((cache_block = TAILQ_FIRST(&cache_inode->nci_block_list)) != NULL) {
+        TAILQ_REMOVE(&cache_inode->nci_block_list, cache_block, ncb_link);
+        nffs_cache_block_free(cache_block);
+    }
+}
+
+static void
+nffs_cache_inode_free(struct nffs_cache_inode *entry)
+{
+    if (entry != NULL) {
+        nffs_cache_inode_free_blocks(entry);
+        os_memblock_put(&nffs_cache_inode_pool, entry);
+    }
+}
+
+static struct nffs_cache_inode *
+nffs_cache_inode_acquire(void)
+{
+    struct nffs_cache_inode *entry;
+
+    entry = nffs_cache_inode_alloc();
+    if (entry == NULL) {
+        entry = TAILQ_LAST(&nffs_cache_inode_list, nffs_cache_inode_list);
+        assert(entry != NULL);
+
+        TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link);
+        nffs_cache_inode_free(entry);
+
+        entry = nffs_cache_inode_alloc();
+    }
+
+    assert(entry != NULL);
+
+    return entry;
+}
+
+static int
+nffs_cache_inode_populate(struct nffs_cache_inode *cache_inode,
+                         struct nffs_inode_entry *inode_entry)
+{
+    int rc;
+
+    memset(cache_inode, 0, sizeof *cache_inode);
+
+    rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_inode_calc_data_length(cache_inode->nci_inode.ni_inode_entry,
+                                     &cache_inode->nci_file_size);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Retrieves the block entry corresponding to the last cached block in the
+ * specified inode's list.  If the inode has no cached blocks, this function
+ * returns null.
+ */
+static struct nffs_hash_entry *
+nffs_cache_inode_last_entry(struct nffs_cache_inode *cache_inode)
+{
+    struct nffs_cache_block *cache_block;
+
+    if (TAILQ_EMPTY(&cache_inode->nci_block_list)) {
+        return NULL;
+    }
+
+    cache_block = TAILQ_LAST(&cache_inode->nci_block_list,
+                             nffs_cache_block_list);
+    return cache_block->ncb_block.nb_hash_entry;
+}
+
+static struct nffs_cache_inode *
+nffs_cache_inode_find(const struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_cache_inode *cur;
+
+    TAILQ_FOREACH(cur, &nffs_cache_inode_list, nci_link) {
+        if (cur->nci_inode.ni_inode_entry == inode_entry) {
+            return cur;
+        }
+    }
+
+    return NULL;
+}
+
+void
+nffs_cache_inode_range(const struct nffs_cache_inode *cache_inode,
+                      uint32_t *out_start, uint32_t *out_end)
+{
+    struct nffs_cache_block *cache_block;
+
+    cache_block = TAILQ_FIRST(&cache_inode->nci_block_list);
+    if (cache_block == NULL) {
+        *out_start = 0;
+        *out_end = 0;
+        return;
+    }
+
+    *out_start = cache_block->ncb_file_offset;
+
+    cache_block = TAILQ_LAST(&cache_inode->nci_block_list,
+                             nffs_cache_block_list);
+    *out_end = cache_block->ncb_file_offset +
+               cache_block->ncb_block.nb_data_len;
+}
+
+static void
+nffs_cache_reclaim_blocks(void)
+{
+    struct nffs_cache_inode *cache_inode;
+
+    TAILQ_FOREACH_REVERSE(cache_inode, &nffs_cache_inode_list,
+                          nffs_cache_inode_list, nci_link) {
+        if (!TAILQ_EMPTY(&cache_inode->nci_block_list)) {
+            nffs_cache_inode_free_blocks(cache_inode);
+            return;
+        }
+    }
+
+    assert(0);
+}
+
+void
+nffs_cache_inode_delete(const struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_cache_inode *entry;
+
+    entry = nffs_cache_inode_find(inode_entry);
+    if (entry == NULL) {
+        return;
+    }
+
+    TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link);
+    nffs_cache_inode_free(entry);
+}
+
+int
+nffs_cache_inode_ensure(struct nffs_cache_inode **out_cache_inode,
+                        struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_cache_inode *cache_inode;
+    int rc;
+
+    cache_inode = nffs_cache_inode_find(inode_entry);
+    if (cache_inode != NULL) {
+        rc = 0;
+        goto done;
+    }
+
+    cache_inode = nffs_cache_inode_acquire();
+    rc = nffs_cache_inode_populate(cache_inode, inode_entry);
+    if (rc != 0) {
+        goto done;
+    }
+
+    TAILQ_INSERT_HEAD(&nffs_cache_inode_list, cache_inode, nci_link);
+
+    rc = 0;
+
+done:
+    if (rc == 0) {
+        *out_cache_inode = cache_inode;
+    } else {
+        nffs_cache_inode_free(cache_inode);
+        *out_cache_inode = NULL;
+    }
+    return rc;
+}
+
+/**
+ * Recaches all cached inodes.  All cached blocks are deleted from the cache
+ * during this operation.  This function should be called after garbage
+ * collection occurs to ensure the cache is consistent.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_cache_inode_refresh(void)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_inode_entry *inode_entry;
+    int rc;
+
+    TAILQ_FOREACH(cache_inode, &nffs_cache_inode_list, nci_link) {
+        /* Clear entire block list. */
+        nffs_cache_inode_free_blocks(cache_inode);
+
+        inode_entry = cache_inode->nci_inode.ni_inode_entry;
+        rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry);
+        if (rc != 0) {
+            return rc;
+        }
+
+        /* File size remains valid. */
+    }
+
+    return 0;
+}
+
+static void
+nffs_cache_log_block(struct nffs_cache_inode *cache_inode,
+                     struct nffs_cache_block *cache_block)
+{
+    NFFS_LOG(DEBUG, "id=%u inode=%u flash_off=0x%08x "
+                    "file_off=%u len=%d (entry=%p)\n",
+             (unsigned int)cache_block->ncb_block.nb_hash_entry->nhe_id,
+             (unsigned int)cache_inode->nci_inode.ni_inode_entry->nie_hash_entry.nhe_id,
+             (unsigned int)cache_block->ncb_block.nb_hash_entry->nhe_flash_loc,
+             (unsigned int)cache_block->ncb_file_offset,
+             cache_block->ncb_block.nb_data_len,
+             cache_block->ncb_block.nb_hash_entry);
+}
+
+static void
+nffs_cache_log_insert_block(struct nffs_cache_inode *cache_inode,
+                            struct nffs_cache_block *cache_block,
+                            int tail)
+{
+    NFFS_LOG(DEBUG, "caching block (%s): ", tail ? "tail" : "head");
+    nffs_cache_log_block(cache_inode, cache_block);
+}
+
+void
+nffs_cache_insert_block(struct nffs_cache_inode *cache_inode,
+                        struct nffs_cache_block *cache_block,
+                        int tail)
+{
+    if (tail) {
+        TAILQ_INSERT_TAIL(&cache_inode->nci_block_list, cache_block, ncb_link);
+    } else {
+        TAILQ_INSERT_HEAD(&cache_inode->nci_block_list, cache_block, ncb_link);
+    }
+
+    nffs_cache_log_insert_block(cache_inode, cache_block, tail);
+}
+
+/**
+ * Finds the data block containing the specified offset within a file inode.
+ * If the block is not yet cached, it gets cached as a result of this
+ * operation.  This function modifies the inode's cached block list according
+ * to the following procedure:
+ *
+ *  1. If none of the owning inode's blocks are currently cached, allocate a
+ *     cached block entry and insert it into the inode's list.
+ *  2. Else if the requested file offset is less than that of the first cached
+ *     block, bridge the gap between the inode's sequence of cached blocks and
+ *     the block that now needs to be cached.  This is accomplished by caching
+ *     each block in the gap, finishing with the requested block.
+ *  3. Else (the requested offset is beyond the end of the cache),
+ *      a. If the requested offset belongs to the block that immediately
+ *         follows the end of the cache, cache the block and append it to the
+ *         list.
+ *      b. Else, clear the cache, and populate it with the single entry
+ *         corresponding to the requested block.
+ *
+ * @param cache_inode           The cached file inode to seek within.
+ * @param seek_offset           The file offset to seek to.
+ * @param out_cache_block       On success, the requested cached block gets
+ *                                  written here; pass null if you don't need
+ *                                  this.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_cache_seek(struct nffs_cache_inode *cache_inode, uint32_t seek_offset,
+                struct nffs_cache_block **out_cache_block)
+{
+    struct nffs_cache_block *cache_block;
+    struct nffs_hash_entry *last_cached_entry;
+    struct nffs_hash_entry *block_entry;
+    struct nffs_hash_entry *pred_entry;
+    struct nffs_block block;
+    uint32_t cache_start;
+    uint32_t cache_end;
+    uint32_t block_start;
+    uint32_t block_end;
+    int rc;
+
+    /* Empty files have no blocks that can be cached. */
+    if (cache_inode->nci_file_size == 0) {
+        return FS_ENOENT;
+    }
+
+    nffs_cache_inode_range(cache_inode, &cache_start, &cache_end);
+    if (cache_end != 0 && seek_offset < cache_start) {
+        /* Seeking prior to cache.  Iterate backwards from cache start. */
+        cache_block = TAILQ_FIRST(&cache_inode->nci_block_list);
+        block_entry = cache_block->ncb_block.nb_prev;
+        block_end = cache_block->ncb_file_offset;
+        cache_block = NULL;
+    } else if (seek_offset < cache_end) {
+        /* Seeking within cache.  Iterate backwards from cache end. */
+        cache_block = TAILQ_LAST(&cache_inode->nci_block_list,
+                                 nffs_cache_block_list);
+        block_entry = cache_block->ncb_block.nb_hash_entry;
+        block_end = cache_end;
+    } else {
+        /* Seeking beyond end of cache.  Iterate backwards from file end.  If
+         * sought-after block is adjacent to cache end, its cache entry will
+         * get appended to the current cache.  Otherwise, the current cache
+         * will be freed and replaced with the single requested block.
+         */
+        cache_block = NULL;
+        block_entry =
+            cache_inode->nci_inode.ni_inode_entry->nie_last_block_entry;
+        block_end = cache_inode->nci_file_size;
+    }
+
+    /* Scan backwards until we find the block containing the seek offest. */
+    while (1) {
+        if (block_end <= cache_start) {
+            /* We are looking before the start of the cache.  Allocate a new
+             * cache block and prepend it to the cache.
+             */
+            assert(cache_block == NULL);
+            cache_block = nffs_cache_block_acquire();
+            rc = nffs_cache_block_populate(cache_block, block_entry,
+                                           block_end);
+            if (rc != 0) {
+                return rc;
+            }
+
+            nffs_cache_insert_block(cache_inode, cache_block, 0);
+        }
+
+        /* Calculate the file offset of the start of this block.  This is used
+         * to determine if this block contains the sought-after offset.
+         */
+        if (cache_block != NULL) {
+            /* Current block is cached. */
+            block_start = cache_block->ncb_file_offset;
+            pred_entry = cache_block->ncb_block.nb_prev;
+        } else {
+            /* We are looking beyond the end of the cache.  Read the data block
+             * from flash.
+             */
+            rc = nffs_block_from_hash_entry(&block, block_entry);
+            if (rc != 0) {
+                return rc;
+            }
+
+            block_start = block_end - block.nb_data_len;
+            pred_entry = block.nb_prev;
+        }
+
+        if (block_start <= seek_offset) {
+            /* This block contains the requested address; iteration is
+             * complete.
+             */
+           if (cache_block == NULL) {
+                /* The block isn't cached, so it must come after the cache end.
+                 * Append it to the cache if it directly follows.  Otherwise,
+                 * erase the current cache and populate it with this single
+                 * block.
+                 */
+                cache_block = nffs_cache_block_acquire();
+                cache_block->ncb_block = block;
+                cache_block->ncb_file_offset = block_start;
+
+                last_cached_entry = nffs_cache_inode_last_entry(cache_inode);
+                if (last_cached_entry != NULL &&
+                    last_cached_entry == pred_entry) {
+
+                    nffs_cache_insert_block(cache_inode, cache_block, 1);
+                } else {
+                    nffs_cache_inode_free_blocks(cache_inode);
+                    nffs_cache_insert_block(cache_inode, cache_block, 0);
+                }
+            }
+
+            if (out_cache_block != NULL) {
+                *out_cache_block = cache_block;
+            }
+            break;
+        }
+
+        /* Prepare for next iteration. */
+        if (cache_block != NULL) {
+            cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list,
+                                     ncb_link);
+        }
+        block_entry = pred_entry;
+        block_end = block_start;
+    }
+
+    return 0;
+}
+
+/**
+ * Frees all cached inodes and blocks.
+ */
+void
+nffs_cache_clear(void)
+{
+    struct nffs_cache_inode *entry;
+
+    while ((entry = TAILQ_FIRST(&nffs_cache_inode_list)) != NULL) {
+        TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link);
+        nffs_cache_inode_free(entry);
+    }
+}
diff --git a/src/nffs_config.c b/src/nffs_config.c
new file mode 100644
index 0000000..6ff6177
--- /dev/null
+++ b/src/nffs_config.c
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs/nffs.h"
+
+struct nffs_config nffs_config;
+
+const struct nffs_config nffs_config_dflt = {
+    .nc_num_inodes = 100,
+    .nc_num_blocks = 100,
+    .nc_num_files = 4,
+    .nc_num_cache_inodes = 4,
+    .nc_num_cache_blocks = 64,
+    .nc_num_dirs = 4,
+};
+
+void
+nffs_config_init(void)
+{
+    if (nffs_config.nc_num_inodes == 0) {
+        nffs_config.nc_num_inodes = nffs_config_dflt.nc_num_inodes;
+    }
+    if (nffs_config.nc_num_blocks == 0) {
+        nffs_config.nc_num_blocks = nffs_config_dflt.nc_num_blocks;
+    }
+    if (nffs_config.nc_num_files == 0) {
+        nffs_config.nc_num_files = nffs_config_dflt.nc_num_files;
+    }
+    if (nffs_config.nc_num_cache_inodes == 0) {
+        nffs_config.nc_num_cache_inodes = nffs_config_dflt.nc_num_cache_inodes;
+    }
+    if (nffs_config.nc_num_cache_blocks == 0) {
+        nffs_config.nc_num_cache_blocks = nffs_config_dflt.nc_num_cache_blocks;
+    }
+    if (nffs_config.nc_num_dirs == 0) {
+        nffs_config.nc_num_dirs = nffs_config_dflt.nc_num_dirs;
+    }
+}
diff --git a/src/nffs_crc.c b/src/nffs_crc.c
new file mode 100644
index 0000000..12a0a3a
--- /dev/null
+++ b/src/nffs_crc.c
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_priv.h"
+
+int
+nffs_crc_flash(uint16_t initial_crc, uint8_t area_idx, uint32_t area_offset,
+               uint32_t len, uint16_t *out_crc)
+{
+    uint32_t chunk_len;
+    uint16_t crc;
+    int rc;
+
+    crc = initial_crc;
+
+    /* Copy data in chunks small enough to fit in the flash buffer. */
+    while (len > 0) {
+        if (len > sizeof nffs_flash_buf) {
+            chunk_len = sizeof nffs_flash_buf;
+        } else {
+            chunk_len = len;
+        }
+
+        STATS_INC(nffs_stats, nffs_readcnt_crc);
+        rc = nffs_flash_read(area_idx, area_offset, nffs_flash_buf, chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        crc = crc16_ccitt(crc, nffs_flash_buf, chunk_len);
+
+        area_offset += chunk_len;
+        len -= chunk_len;
+    }
+
+    *out_crc = crc;
+    return 0;
+}
+
+uint16_t
+nffs_crc_disk_block_hdr(const struct nffs_disk_block *disk_block)
+{
+    uint16_t crc;
+
+    crc = crc16_ccitt(0, disk_block, NFFS_DISK_BLOCK_OFFSET_CRC);
+
+    return crc;
+}
+
+static int
+nffs_crc_disk_block(const struct nffs_disk_block *disk_block,
+                    uint8_t area_idx, uint32_t area_offset,
+                    uint16_t *out_crc)
+{
+    uint16_t crc;
+    int rc;
+
+    crc = nffs_crc_disk_block_hdr(disk_block);
+
+    rc = nffs_crc_flash(crc, area_idx, area_offset + sizeof *disk_block,
+                        disk_block->ndb_data_len, &crc);
+    if (rc != 0) {
+        return rc;
+    }
+
+    *out_crc = crc;
+    return 0;
+}
+
+int
+nffs_crc_disk_block_validate(const struct nffs_disk_block *disk_block,
+                             uint8_t area_idx, uint32_t area_offset)
+{
+    uint16_t crc;
+    int rc;
+
+    rc = nffs_crc_disk_block(disk_block, area_idx, area_offset, &crc);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (crc != disk_block->ndb_crc16) {
+        return FS_ECORRUPT;
+    }
+
+    return 0;
+}
+
+void
+nffs_crc_disk_block_fill(struct nffs_disk_block *disk_block, const void *data)
+{
+    uint16_t crc16;
+
+    crc16 = nffs_crc_disk_block_hdr(disk_block);
+    crc16 = crc16_ccitt(crc16, data, disk_block->ndb_data_len);
+
+    disk_block->ndb_crc16 = crc16;
+}
+
+static uint16_t
+nffs_crc_disk_inode_hdr(const struct nffs_disk_inode *disk_inode)
+{
+    uint16_t crc;
+
+    crc = crc16_ccitt(0, disk_inode, NFFS_DISK_INODE_OFFSET_CRC);
+
+    return crc;
+}
+
+static int
+nffs_crc_disk_inode(const struct nffs_disk_inode *disk_inode,
+                    uint8_t area_idx, uint32_t area_offset,
+                    uint16_t *out_crc)
+{
+    uint16_t crc;
+    int rc;
+
+    crc = nffs_crc_disk_inode_hdr(disk_inode);
+
+    rc = nffs_crc_flash(crc, area_idx, area_offset + sizeof *disk_inode,
+                        disk_inode->ndi_filename_len, &crc);
+    if (rc != 0) {
+        return rc;
+    }
+
+    *out_crc = crc;
+    return 0;
+}
+
+int
+nffs_crc_disk_inode_validate(const struct nffs_disk_inode *disk_inode,
+                             uint8_t area_idx, uint32_t area_offset)
+{
+    uint16_t crc;
+    int rc;
+
+    rc = nffs_crc_disk_inode(disk_inode, area_idx, area_offset, &crc);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (crc != disk_inode->ndi_crc16) {
+        return FS_ECORRUPT;
+    }
+
+    return 0;
+}
+
+void
+nffs_crc_disk_inode_fill(struct nffs_disk_inode *disk_inode,
+                         const char *filename)
+{
+    uint16_t crc16;
+
+    crc16 = nffs_crc_disk_inode_hdr(disk_inode);
+    crc16 = crc16_ccitt(crc16, filename, disk_inode->ndi_filename_len);
+
+    disk_inode->ndi_crc16 = crc16;
+}
diff --git a/src/nffs_dir.c b/src/nffs_dir.c
new file mode 100644
index 0000000..8cdefa9
--- /dev/null
+++ b/src/nffs_dir.c
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+#include "fs/fs_if.h"
+
+struct fs_ops nffs_ops;
+
+static struct nffs_dir *
+nffs_dir_alloc(void)
+{
+    struct nffs_dir *dir;
+
+    dir = os_memblock_get(&nffs_dir_pool);
+    if (dir != NULL) {
+        memset(dir, 0, sizeof *dir);
+    }
+
+    return dir;
+}
+
+static int
+nffs_dir_free(struct nffs_dir *dir)
+{
+    int rc;
+
+    if (dir != NULL) {
+        rc = os_memblock_put(&nffs_dir_pool, dir);
+        if (rc != 0) {
+            return FS_EOS;
+        }
+    }
+
+    return 0;
+}
+
+int
+nffs_dir_open(const char *path, struct nffs_dir **out_dir)
+{
+    struct nffs_inode_entry *parent_inode_entry;
+    struct nffs_dir *dir;
+    int rc;
+
+    rc = nffs_path_find_inode_entry(path, &parent_inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (!nffs_hash_id_is_dir(parent_inode_entry->nie_hash_entry.nhe_id)) {
+        return FS_EINVAL;
+    }
+
+    dir = nffs_dir_alloc();
+    if (dir == NULL) {
+        return FS_ENOMEM;
+    }
+
+    dir->nd_parent_inode_entry = parent_inode_entry;
+    nffs_inode_inc_refcnt(dir->nd_parent_inode_entry);
+    memset(&dir->nd_dirent, 0, sizeof dir->nd_dirent);
+    dir->fops = &nffs_ops;
+
+    *out_dir = dir;
+
+    return 0;
+}
+
+int
+nffs_dir_read(struct nffs_dir *dir, struct nffs_dirent **out_dirent)
+{
+    struct nffs_inode_entry *child;
+    int rc;
+
+    if (dir->nd_dirent.nde_inode_entry == NULL) {
+        child = SLIST_FIRST(&dir->nd_parent_inode_entry->nie_child_list);
+    } else {
+        child = SLIST_NEXT(dir->nd_dirent.nde_inode_entry, nie_sibling_next);
+        rc = nffs_inode_dec_refcnt(dir->nd_dirent.nde_inode_entry);
+        if (rc != 0) {
+            /* XXX: Need to clean up anything? */
+            return rc;
+        }
+    }
+    dir->nd_dirent.nde_inode_entry = child;
+
+    if (child == NULL) {
+        *out_dirent = NULL;
+        return FS_ENOENT;
+    }
+
+    nffs_inode_inc_refcnt(child);
+    dir->nd_dirent.fops = &nffs_ops;
+    *out_dirent = &dir->nd_dirent;
+
+    return 0;
+}
+
+int
+nffs_dir_close(struct nffs_dir *dir)
+{
+    int rc;
+
+    if (dir == NULL) {
+        return 0;
+    }
+
+    if (dir->nd_dirent.nde_inode_entry != NULL) {
+        rc = nffs_inode_dec_refcnt(dir->nd_dirent.nde_inode_entry);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    rc = nffs_inode_dec_refcnt(dir->nd_parent_inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_dir_free(dir);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
diff --git a/src/nffs_file.c b/src/nffs_file.c
new file mode 100644
index 0000000..bf1866f
--- /dev/null
+++ b/src/nffs_file.c
@@ -0,0 +1,355 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+#include "fs/fs_if.h"
+
+struct fs_ops nffs_ops;
+
+static struct nffs_file *
+nffs_file_alloc(void)
+{
+    struct nffs_file *file;
+
+    file = os_memblock_get(&nffs_file_pool);
+    if (file != NULL) {
+        memset(file, 0, sizeof *file);
+    }
+
+    return file;
+}
+
+static int
+nffs_file_free(struct nffs_file *file)
+{
+    int rc;
+
+    if (file != NULL) {
+        rc = os_memblock_put(&nffs_file_pool, file);
+        if (rc != 0) {
+            return FS_EOS;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Creates a new empty file and writes it to the file system.  If a file with
+ * the specified path already exists, the behavior is undefined.
+ *
+ * @param parent                The parent directory to insert the new file in.
+ * @param filename              The name of the file to create.
+ * @param filename_len          The length of the filename, in characters.
+ * @param is_dir                1 if this is a directory; 0 if it is a normal
+ *                                  file.
+ * @param out_inode_entry       On success, this points to the inode
+ *                                  corresponding to the new file.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_file_new(struct nffs_inode_entry *parent, const char *filename,
+              uint8_t filename_len, int is_dir,
+              struct nffs_inode_entry **out_inode_entry)
+{
+    struct nffs_disk_inode disk_inode;
+    struct nffs_inode_entry *inode_entry;
+    uint32_t offset;
+    uint8_t area_idx;
+    int rc;
+
+    rc = nffs_inode_entry_reserve(&inode_entry);
+    if (rc != 0) {
+        goto err;
+    }
+
+    rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len,
+                                 &area_idx, &offset);
+    if (rc != 0) {
+        goto err;
+    }
+
+    memset(&disk_inode, 0, sizeof disk_inode);
+    if (is_dir) {
+        disk_inode.ndi_id = nffs_hash_next_dir_id++;
+    } else {
+        disk_inode.ndi_id = nffs_hash_next_file_id++;
+    }
+    disk_inode.ndi_seq = 0;
+    disk_inode.ndi_lastblock_id = NFFS_ID_NONE;
+    if (parent == NULL) {
+        disk_inode.ndi_parent_id = NFFS_ID_NONE;
+    } else {
+        disk_inode.ndi_parent_id = parent->nie_hash_entry.nhe_id;
+    }
+    disk_inode.ndi_filename_len = filename_len;
+    disk_inode.ndi_flags = 0;
+    nffs_crc_disk_inode_fill(&disk_inode, filename);
+
+    rc = nffs_inode_write_disk(&disk_inode, filename, area_idx, offset);
+    if (rc != 0) {
+        goto err;
+    }
+
+    inode_entry->nie_hash_entry.nhe_id = disk_inode.ndi_id;
+    inode_entry->nie_hash_entry.nhe_flash_loc =
+        nffs_flash_loc(area_idx, offset);
+    inode_entry->nie_refcnt = 1;
+    inode_entry->nie_last_block_entry = NULL;
+
+    if (parent != NULL) {
+        rc = nffs_inode_add_child(parent, inode_entry);
+        if (rc != 0) {
+            goto err;
+        }
+    } else {
+        assert(disk_inode.ndi_id == NFFS_ID_ROOT_DIR);
+        nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_INTREE);
+    }
+
+    nffs_hash_insert(&inode_entry->nie_hash_entry);
+    *out_inode_entry = inode_entry;
+
+    return 0;
+
+err:
+    nffs_inode_entry_free(inode_entry);
+    return rc;
+}
+
+/**
+ * Performs a file open operation.
+ *
+ * @param out_file          On success, a pointer to the newly-created file
+ *                              handle gets written here.
+ * @param path              The path of the file to open.
+ * @param access_flags      Flags controlling file access; see nffs_open() for
+ *                              details.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_file_open(struct nffs_file **out_file, const char *path,
+               uint8_t access_flags)
+{
+    struct nffs_path_parser parser;
+    struct nffs_inode_entry *parent;
+    struct nffs_inode_entry *inode;
+    struct nffs_file *file;
+    int rc;
+
+    file = NULL;
+
+    /* Reject invalid access flag combinations. */
+    if (!(access_flags & (FS_ACCESS_READ | FS_ACCESS_WRITE))) {
+        rc = FS_EINVAL;
+        goto err;
+    }
+    if (access_flags & (FS_ACCESS_APPEND | FS_ACCESS_TRUNCATE) &&
+        !(access_flags & FS_ACCESS_WRITE)) {
+
+        rc = FS_EINVAL;
+        goto err;
+    }
+    if (access_flags & FS_ACCESS_APPEND &&
+        access_flags & FS_ACCESS_TRUNCATE) {
+
+        rc = FS_EINVAL;
+        goto err;
+    }
+
+    file = nffs_file_alloc();
+    if (file == NULL) {
+        rc = FS_ENOMEM;
+        goto err;
+    }
+
+    nffs_path_parser_new(&parser, path);
+    rc = nffs_path_find(&parser, &inode, &parent);
+    if (rc == FS_ENOENT && parser.npp_token_type == NFFS_PATH_TOKEN_LEAF) {
+        /* The path is valid, but the file does not exist.  This is an error
+         * for read-only opens.
+         */
+        if (!(access_flags & FS_ACCESS_WRITE)) {
+            goto err;
+        }
+
+        /* Make sure the parent directory exists. */
+        if (parent == NULL) {
+            goto err;
+        }
+
+        /* Create a new file at the specified path. */
+        rc = nffs_file_new(parent, parser.npp_token, parser.npp_token_len, 0,
+                           &file->nf_inode_entry);
+        if (rc != 0) {
+            goto err;
+        }
+    } else if (rc == 0) {
+        /* The file already exists. */
+
+        /* Reject an attempt to open a directory. */
+        if (nffs_hash_id_is_dir(inode->nie_hash_entry.nhe_id)) {
+            rc = FS_EINVAL;
+            goto err;
+        }
+
+        if (access_flags & FS_ACCESS_TRUNCATE) {
+            /* The user is truncating the file.  Unlink the old file and create
+             * a new one in its place.
+             */
+            nffs_path_unlink(path);
+            rc = nffs_file_new(parent, parser.npp_token, parser.npp_token_len,
+                               0, &file->nf_inode_entry);
+            if (rc != 0) {
+                goto err;
+            }
+        } else {
+            /* The user is not truncating the file.  Point the file handle to
+             * the existing inode.
+             */
+            file->nf_inode_entry = inode;
+        }
+    } else {
+        /* Invalid path. */
+        goto err;
+    }
+
+    if (access_flags & FS_ACCESS_APPEND) {
+        rc = nffs_inode_data_len(file->nf_inode_entry, &file->nf_offset);
+        if (rc != 0) {
+            goto err;
+        }
+    } else {
+        file->nf_offset = 0;
+    }
+    nffs_inode_inc_refcnt(file->nf_inode_entry);
+    file->nf_access_flags = access_flags;
+    file->fops = &nffs_ops;
+
+    *out_file = file;
+
+    return 0;
+
+err:
+    nffs_file_free(file);
+    return rc;
+}
+
+/**
+ * Positions a file's read and write pointer at the specified offset.  The
+ * offset is expressed as the number of bytes from the start of the file (i.e.,
+ * seeking to 0 places the pointer at the first byte in the file).
+ *
+ * @param file              The file to reposition.
+ * @param offset            The offset from the start of the file to seek to.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_file_seek(struct nffs_file *file, uint32_t offset)
+{ 
+    uint32_t len;
+    int rc;
+
+    rc = nffs_inode_data_len(file->nf_inode_entry, &len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (offset > len) {
+        return FS_EOFFSET;
+    }
+
+    file->nf_offset = offset;
+    return 0;
+}
+
+/**
+ * Reads data from the specified file.  If more data is requested than remains
+ * in the file, all available data is retrieved.  Note: this type of short read
+ * results in a success return code.
+ *
+ * @param file              The file to read from.
+ * @param len               The number of bytes to attempt to read.
+ * @param out_data          The destination buffer to read into.
+ * @param out_len           On success, the number of bytes actually read gets
+ *                              written here.  Pass null if you don't care.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_file_read(struct nffs_file *file, uint32_t len, void *out_data,
+               uint32_t *out_len)
+{
+    uint32_t bytes_read;
+    int rc;
+
+    if (!nffs_misc_ready()) {
+        return FS_EUNINIT;
+    }
+
+    if (!(file->nf_access_flags & FS_ACCESS_READ)) {
+        return FS_EACCESS;
+    }
+
+    rc = nffs_inode_read(file->nf_inode_entry, file->nf_offset, len, out_data,
+                        &bytes_read);
+    if (rc != 0) {
+        return rc;
+    }
+
+    file->nf_offset += bytes_read;
+    if (out_len != NULL) {
+        *out_len = bytes_read;
+    }
+
+    return 0;
+}
+
+/**
+ * Closes the specified file and invalidates the file handle.  If the file has
+ * already been unlinked, and this is the last open handle to the file, this
+ * operation causes the file to be deleted.
+ *
+ * @param file              The file handle to close.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_file_close(struct nffs_file *file)
+{
+    int rc;
+
+    rc = nffs_inode_dec_refcnt(file->nf_inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_file_free(file);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
diff --git a/src/nffs_flash.c b/src/nffs_flash.c
new file mode 100644
index 0000000..cc83837
--- /dev/null
+++ b/src/nffs_flash.c
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include "hal/hal_flash.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+/** A buffer used for flash reads; shared across all of nffs. */
+uint8_t nffs_flash_buf[NFFS_FLASH_BUF_SZ];
+
+/**
+ * Reads a chunk of data from flash.
+ *
+ * @param area_idx              The index of the area to read from.
+ * @param area_offset           The offset within the area to read from.
+ * @param data                  On success, the flash contents are written
+ *                                  here.
+ * @param len                   The number of bytes to read.
+ *
+ * @return                      0 on success;
+ *                              FS_EOFFSET on an attempt to read an invalid
+ *                                  address range;
+ *                              FS_EHW on flash error.
+ */
+int
+nffs_flash_read(uint8_t area_idx, uint32_t area_offset, void *data,
+                uint32_t len)
+{
+    const struct nffs_area *area;
+    int rc;
+
+    assert(area_idx < nffs_num_areas);
+
+    area = nffs_areas + area_idx;
+
+    if (area_offset + len > area->na_length) {
+        return FS_EOFFSET;
+    }
+
+    STATS_INC(nffs_stats, nffs_iocnt_read);
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + area_offset, data,
+                        len);
+    if (rc != 0) {
+        return FS_EHW;
+    }
+
+    return 0;
+}
+
+/**
+ * Writes a chunk of data to flash.
+ *
+ * @param area_idx              The index of the area to write to.
+ * @param area_offset           The offset within the area to write to.
+ * @param data                  The data to write to flash.
+ * @param len                   The number of bytes to write.
+ *
+ * @return                      0 on success;
+ *                              FS_EOFFSET on an attempt to write to an
+ *                                  invalid address range, or on an attempt to
+ *                                  perform a non-strictly-sequential write;
+ *                              FS_EFLASH_ERROR on flash error.
+ */
+int
+nffs_flash_write(uint8_t area_idx, uint32_t area_offset, const void *data,
+                 uint32_t len)
+{
+    struct nffs_area *area;
+    int rc;
+
+    assert(area_idx < nffs_num_areas);
+    area = nffs_areas + area_idx;
+
+    if (area_offset + len > area->na_length) {
+        return FS_EOFFSET;
+    }
+
+    if (area_offset < area->na_cur) {
+        return FS_EOFFSET;
+    }
+
+    STATS_INC(nffs_stats, nffs_iocnt_write);
+    rc = hal_flash_write(area->na_flash_id, area->na_offset + area_offset,
+                         data, len);
+    if (rc != 0) {
+        return FS_EHW;
+    }
+
+    area->na_cur = area_offset + len;
+
+    return 0;
+}
+
+/**
+ * Copies a chunk of data from one region of flash to another.
+ *
+ * @param area_idx_from         The index of the area to copy from.
+ * @param area_offset_from      The offset within the area to copy from.
+ * @param area_idx_to           The index of the area to copy to.
+ * @param area_offset_to        The offset within the area to copy to.
+ * @param len                   The number of bytes to copy.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_flash_copy(uint8_t area_idx_from, uint32_t area_offset_from,
+                uint8_t area_idx_to, uint32_t area_offset_to,
+                uint32_t len)
+{
+    uint32_t chunk_len;
+    int rc;
+
+    /* Copy data in chunks small enough to fit in the flash buffer. */
+    while (len > 0) {
+        if (len > sizeof nffs_flash_buf) {
+            chunk_len = sizeof nffs_flash_buf;
+        } else {
+            chunk_len = len;
+        }
+
+        STATS_INC(nffs_stats, nffs_readcnt_copy);
+        rc = nffs_flash_read(area_idx_from, area_offset_from, nffs_flash_buf,
+                             chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = nffs_flash_write(area_idx_to, area_offset_to, nffs_flash_buf,
+                              chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        area_offset_from += chunk_len;
+        area_offset_to += chunk_len;
+        len -= chunk_len;
+    }
+
+    return 0;
+}
+
+/**
+ * Compresses a flash-area-index,flash-area-offset pair into a 32-bit flash
+ * location.
+ */
+uint32_t
+nffs_flash_loc(uint8_t area_idx, uint32_t area_offset)
+{
+    assert(area_offset <= 0x00ffffff);
+    return area_idx << 24 | area_offset;
+}
+
+/**
+ * Expands a compressed 32-bit flash location into a
+ * flash-area-index,flash-area-offset pair.
+ */
+void
+nffs_flash_loc_expand(uint32_t flash_loc, uint8_t *out_area_idx,
+                     uint32_t *out_area_offset)
+{
+    *out_area_idx = flash_loc >> 24;
+    *out_area_offset = flash_loc & 0x00ffffff;
+}
diff --git a/src/nffs_format.c b/src/nffs_format.c
new file mode 100644
index 0000000..d300209
--- /dev/null
+++ b/src/nffs_format.c
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "hal/hal_flash.h"
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+
+/**
+ * Turns a scratch area into a non-scratch area.  If the specified area is not
+ * actually a scratch area, this function falls back to a slower full format
+ * operation.
+ */
+int
+nffs_format_from_scratch_area(uint8_t area_idx, uint8_t area_id)
+{
+    struct nffs_disk_area disk_area;
+    int rc;
+
+    assert(area_idx < nffs_num_areas);
+    STATS_INC(nffs_stats, nffs_readcnt_format);
+    rc = nffs_flash_read(area_idx, 0, &disk_area, sizeof disk_area);
+    if (rc != 0) {
+        return rc;
+    }
+
+    nffs_areas[area_idx].na_id = area_id;
+    if (!nffs_area_is_scratch(&disk_area)) {
+        rc = nffs_format_area(area_idx, 0);
+        if (rc != 0) {
+            return rc;
+        }
+    } else {
+        disk_area.nda_id = area_id;
+        rc = nffs_flash_write(area_idx, NFFS_AREA_OFFSET_ID,
+                              &disk_area.nda_id, sizeof disk_area.nda_id);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Formats a single scratch area.
+ */
+int
+nffs_format_area(uint8_t area_idx, int is_scratch)
+{
+    struct nffs_disk_area disk_area;
+    struct nffs_area *area;
+    uint32_t write_len;
+    int rc;
+
+    area = nffs_areas + area_idx;
+
+    rc = hal_flash_erase(area->na_flash_id, area->na_offset, area->na_length);
+    if (rc != 0) {
+        return FS_EHW;
+    }
+    area->na_cur = 0;
+
+    nffs_area_to_disk(area, &disk_area);
+
+    if (is_scratch) {
+        nffs_areas[area_idx].na_id = NFFS_AREA_ID_NONE;
+        write_len = sizeof disk_area - sizeof disk_area.nda_id;
+    } else {
+        write_len = sizeof disk_area;
+    }
+
+    rc = nffs_flash_write(area_idx, 0, &disk_area.nda_magic, write_len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Erases all the specified areas and initializes them with a clean nffs
+ * file system.
+ *
+ * @param area_descs        The set of areas to format.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int
+nffs_format_full(const struct nffs_area_desc *area_descs)
+{
+    int rc;
+    int i;
+
+    /* Start from a clean state. */
+    nffs_misc_reset();
+
+    /* Select largest area to be the initial scratch area. */
+    nffs_scratch_area_idx = 0;
+    for (i = 1; area_descs[i].nad_length != 0; i++) {
+        if (i >= NFFS_MAX_AREAS) {
+            rc = FS_EINVAL;
+            goto err;
+        }
+
+        if (area_descs[i].nad_length >
+            area_descs[nffs_scratch_area_idx].nad_length) {
+
+            nffs_scratch_area_idx = i;
+        }
+    }
+
+    rc = nffs_misc_set_num_areas(i);
+    if (rc != 0) {
+        goto err;
+    }
+
+    for (i = 0; i < nffs_num_areas; i++) {
+        nffs_areas[i].na_offset = area_descs[i].nad_offset;
+        nffs_areas[i].na_length = area_descs[i].nad_length;
+        nffs_areas[i].na_flash_id = area_descs[i].nad_flash_id;
+        nffs_areas[i].na_cur = 0;
+        nffs_areas[i].na_gc_seq = 0;
+
+        if (i == nffs_scratch_area_idx) {
+            nffs_areas[i].na_id = NFFS_AREA_ID_NONE;
+        } else {
+            nffs_areas[i].na_id = i;
+        }
+
+        rc = nffs_format_area(i, i == nffs_scratch_area_idx);
+        if (rc != 0) {
+            goto err;
+        }
+    }
+
+    rc = nffs_misc_validate_scratch();
+    if (rc != 0) {
+        goto err;
+    }
+
+    /* Create root directory. */
+    rc = nffs_file_new(NULL, "", 0, 1, &nffs_root_dir);
+    if (rc != 0) {
+        goto err;
+    }
+
+    /* Create "lost+found" directory. */
+    rc = nffs_misc_create_lost_found_dir();
+    if (rc != 0) {
+        goto err;
+    }
+
+    rc = nffs_misc_validate_root_dir();
+    if (rc != 0) {
+        goto err;
+    }
+
+    rc = nffs_misc_set_max_block_data_len(0);
+    if (rc != 0) {
+        goto err;
+    }
+
+    nffs_current_area_descs = (struct nffs_area_desc*) area_descs;
+
+    return 0;
+
+err:
+    nffs_misc_reset();
+    return rc;
+}
diff --git a/src/nffs_gc.c b/src/nffs_gc.c
new file mode 100644
index 0000000..8af1d2b
--- /dev/null
+++ b/src/nffs_gc.c
@@ -0,0 +1,574 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "os/os_malloc.h"
+#include "testutil/testutil.h"
+#include "nffs_priv.h"
+#include "nffs/nffs.h"
+
+/**
+ * Keeps track of the number of garbage collections performed.  The exact
+ * number is not important, but it is useful to compare against an older copy
+ * to determine if garbage collection occurred.
+ */
+unsigned int nffs_gc_count;
+
+static int
+nffs_gc_copy_object(struct nffs_hash_entry *entry, uint16_t object_size,
+                    uint8_t to_area_idx)
+{
+    uint32_t from_area_offset;
+    uint32_t to_area_offset;
+    uint8_t from_area_idx;
+    int rc;
+
+    nffs_flash_loc_expand(entry->nhe_flash_loc,
+                          &from_area_idx, &from_area_offset);
+    to_area_offset = nffs_areas[to_area_idx].na_cur;
+
+    rc = nffs_flash_copy(from_area_idx, from_area_offset, to_area_idx,
+                         to_area_offset, object_size);
+    if (rc != 0) {
+        return rc;
+    }
+
+    entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset);
+
+    return 0;
+}
+
+static int
+nffs_gc_copy_inode(struct nffs_inode_entry *inode_entry, uint8_t to_area_idx)
+{
+    struct nffs_inode inode;
+    uint16_t copy_len;
+    int rc;
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+    copy_len = sizeof (struct nffs_disk_inode) + inode.ni_filename_len;
+
+    rc = nffs_gc_copy_object(&inode_entry->nie_hash_entry, copy_len,
+                             to_area_idx);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Selects the most appropriate area for garbage collection.
+ *
+ * @return                  The ID of the area to garbage collect.
+ */
+static uint16_t
+nffs_gc_select_area(void)
+{
+    const struct nffs_area *area;
+    uint8_t best_area_idx;
+    int8_t diff;
+    int i;
+
+    best_area_idx = 0;
+    for (i = 1; i < nffs_num_areas; i++) {
+        if (i == nffs_scratch_area_idx) {
+            continue;
+        }
+
+        area = nffs_areas + i;
+        if (area->na_length > nffs_areas[best_area_idx].na_length) {
+            best_area_idx = i;
+        } else if (best_area_idx == nffs_scratch_area_idx) {
+            best_area_idx = i;
+        } else {
+            diff = nffs_areas[i].na_gc_seq -
+                   nffs_areas[best_area_idx].na_gc_seq;
+            if (diff < 0) {
+                best_area_idx = i;
+            }
+        }
+    }
+
+    assert(best_area_idx != nffs_scratch_area_idx);
+
+    return best_area_idx;
+}
+
+static int
+nffs_gc_block_chain_copy(struct nffs_hash_entry *last_entry, uint32_t data_len,
+                         uint8_t to_area_idx)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_block block;
+    uint32_t data_bytes_copied;
+    uint16_t copy_len;
+    int rc;
+
+    data_bytes_copied = 0;
+    entry = last_entry;
+
+    while (data_bytes_copied < data_len) {
+        assert(entry != NULL);
+
+        rc = nffs_block_from_hash_entry(&block, entry);
+        if (rc != 0) {
+            return rc;
+        }
+
+        copy_len = sizeof (struct nffs_disk_block) + block.nb_data_len;
+        rc = nffs_gc_copy_object(entry, copy_len, to_area_idx);
+        if (rc != 0) {
+            return rc;
+        }
+        data_bytes_copied += block.nb_data_len;
+
+        entry = block.nb_prev;
+    }
+
+    return 0;
+}
+
+/**
+ * Moves a chain of blocks from one area to another.  This function attempts to
+ * collate the blocks into a single new block in the destination area.
+ *
+ * @param last_entry            The last block entry in the chain.
+ * @param data_len              The total length of data to collate.
+ * @param to_area_idx           The index of the area to copy to.
+ * @param inout_next            This parameter is only necessary if you are
+ *                                  calling this function during an iteration
+ *                                  of the entire hash table; pass null
+ *                                  otherwise.
+ *                              On input, this points to the next hash entry
+ *                                  you plan on processing.
+ *                              On output, this points to the next hash entry
+ *                                  that should be processed.
+ *
+ * @return                      0 on success;
+ *                              FS_ENOMEM if there is insufficient heap;
+ *                              other nonzero on failure.
+ */
+static int
+nffs_gc_block_chain_collate(struct nffs_hash_entry *last_entry,
+                            uint32_t data_len, uint8_t to_area_idx,
+                            struct nffs_hash_entry **inout_next)
+{
+    struct nffs_disk_block disk_block;
+    struct nffs_hash_entry *entry;
+    struct nffs_area *to_area;
+    struct nffs_block last_block;
+    struct nffs_block block;
+    uint32_t to_area_offset;
+    uint32_t from_area_offset;
+    uint32_t data_offset;
+    uint8_t *data;
+    uint8_t from_area_idx;
+    int rc;
+
+    memset(&last_block, 0, sizeof last_block);
+
+    data = malloc(data_len);
+    if (data == NULL) {
+        rc = FS_ENOMEM;
+        goto done;
+    }
+
+    to_area = nffs_areas + to_area_idx;
+
+    entry = last_entry;
+    data_offset = data_len;
+    while (data_offset > 0) {
+        rc = nffs_block_from_hash_entry(&block, entry);
+        if (rc != 0) {
+            goto done;
+        }
+        data_offset -= block.nb_data_len;
+
+        nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc,
+                              &from_area_idx, &from_area_offset);
+        from_area_offset += sizeof disk_block;
+        STATS_INC(nffs_stats, nffs_readcnt_gccollate);
+        rc = nffs_flash_read(from_area_idx, from_area_offset,
+                             data + data_offset, block.nb_data_len);
+        if (rc != 0) {
+            goto done;
+        }
+
+        if (entry != last_entry) {
+            if (inout_next != NULL && *inout_next == entry) {
+                *inout_next = SLIST_NEXT(entry, nhe_next);
+            }
+            nffs_block_delete_from_ram(entry);
+        } else {
+            last_block = block;
+        }
+        entry = block.nb_prev;
+    }
+
+    /* we had better have found the last block */
+    assert(last_block.nb_hash_entry);
+
+    /* The resulting block should inherit its ID from its last constituent
+     * block (this is the ID referenced by the parent inode and subsequent data
+     * block).  The previous ID gets inherited from the first constituent
+     * block.
+     */
+    memset(&disk_block, 0, sizeof disk_block);
+    disk_block.ndb_id = last_block.nb_hash_entry->nhe_id;
+    disk_block.ndb_seq = last_block.nb_seq + 1;
+    disk_block.ndb_inode_id = last_block.nb_inode_entry->nie_hash_entry.nhe_id;
+    if (entry == NULL) {
+        disk_block.ndb_prev_id = NFFS_ID_NONE;
+    } else {
+        disk_block.ndb_prev_id = entry->nhe_id;
+    }
+    disk_block.ndb_data_len = data_len;
+    nffs_crc_disk_block_fill(&disk_block, data);
+
+    to_area_offset = to_area->na_cur;
+    rc = nffs_flash_write(to_area_idx, to_area_offset,
+                          &disk_block, sizeof disk_block);
+    if (rc != 0) {
+        goto done;
+    }
+
+    rc = nffs_flash_write(to_area_idx, to_area_offset + sizeof disk_block,
+                          data, data_len);
+    if (rc != 0) {
+        goto done;
+    }
+
+    last_entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset);
+
+    rc = 0;
+
+    ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, to_area_idx,
+                                                to_area_offset) == 0);
+
+done:
+    free(data);
+    return rc;
+}
+
+/**
+ * Moves a chain of blocks from one area to another.  This function attempts to
+ * collate the blocks into a single new block in the destination area.  If
+ * there is insufficient heap memory do to this, the function falls back to
+ * copying each block separately.
+ *
+ * @param last_entry            The last block entry in the chain.
+ * @param multiple_blocks       0=single block; 1=more than one block.
+ * @param data_len              The total length of data to collate.
+ * @param to_area_idx           The index of the area to copy to.
+ * @param inout_next            This parameter is only necessary if you are
+ *                                  calling this function during an iteration
+ *                                  of the entire hash table; pass null
+ *                                  otherwise.
+ *                              On input, this points to the next hash entry
+ *                                  you plan on processing.
+ *                              On output, this points to the next hash entry
+ *                                  that should be processed.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_gc_block_chain(struct nffs_hash_entry *last_entry, int multiple_blocks,
+                    uint32_t data_len, uint8_t to_area_idx,
+                    struct nffs_hash_entry **inout_next)
+{
+    int rc;
+
+    if (!multiple_blocks) {
+        /* If there is only one block, collation has the same effect as a
+         * simple copy.  Just perform the more efficient copy.
+         */
+        rc = nffs_gc_block_chain_copy(last_entry, data_len, to_area_idx);
+    } else {
+        rc = nffs_gc_block_chain_collate(last_entry, data_len, to_area_idx,
+                                         inout_next);
+        if (rc == FS_ENOMEM) {
+            /* Insufficient heap for collation; just copy each block one by
+             * one.
+             */
+            rc = nffs_gc_block_chain_copy(last_entry, data_len, to_area_idx);
+        }
+    }
+
+    return rc;
+}
+
+static int
+nffs_gc_inode_blocks(struct nffs_inode_entry *inode_entry,
+                     uint8_t from_area_idx, uint8_t to_area_idx,
+                     struct nffs_hash_entry **inout_next)
+{
+    struct nffs_hash_entry *last_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_block block;
+    uint32_t prospective_data_len;
+    uint32_t area_offset;
+    uint32_t data_len;
+    uint8_t area_idx;
+    int multiple_blocks;
+    int rc;
+
+    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+
+    data_len = 0;
+    last_entry = NULL;
+    multiple_blocks = 0;
+    entry = inode_entry->nie_last_block_entry;
+    while (entry != NULL) {
+        rc = nffs_block_from_hash_entry(&block, entry);
+        if (rc != 0) {
+            return rc;
+        }
+
+        nffs_flash_loc_expand(entry->nhe_flash_loc, &area_idx, &area_offset);
+        if (area_idx == from_area_idx) {
+            if (last_entry == NULL) {
+                last_entry = entry;
+            }
+
+            prospective_data_len = data_len + block.nb_data_len;
+            if (prospective_data_len <= nffs_block_max_data_sz) {
+                data_len = prospective_data_len;
+                if (last_entry != entry) {
+                    multiple_blocks = 1;
+                }
+            } else {
+                rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len,
+                                         to_area_idx, inout_next);
+                if (rc != 0) {
+                    return rc;
+                }
+                last_entry = entry;
+                data_len = block.nb_data_len;
+                multiple_blocks = 0;
+            }
+        } else {
+            if (last_entry != NULL) {
+                rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len,
+                                         to_area_idx, inout_next);
+                if (rc != 0) {
+                    return rc;
+                }
+
+                last_entry = NULL;
+                data_len = 0;
+                multiple_blocks = 0;
+            }
+        }
+
+        entry = block.nb_prev;
+    }
+
+    if (last_entry != NULL) {
+        rc = nffs_gc_block_chain(last_entry, multiple_blocks, data_len,
+                                 to_area_idx, inout_next);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Triggers a garbage collection cycle.  This is implemented as follows:
+ *
+ *  (1) The non-scratch area with the lowest garbage collection sequence
+ *      number is selected as the "source area."  If there are other areas
+ *      with the same sequence number, the first one encountered is selected.
+ *
+ *  (2) The source area's ID is written to the scratch area's header,
+ *      transforming it into a non-scratch ID.  The former scratch area is now
+ *      known as the "destination area."
+ *
+ *  (3) The RAM representation is exhaustively searched for objects which are
+ *      resident in the source area.  The copy is accomplished as follows:
+ *
+ *      For each inode:
+ *          (a) If the inode is resident in the source area, copy the inode
+ *              record to the destination area.
+ *
+ *          (b) Walk the inode's list of data blocks, starting with the last
+ *              block in the file.  Each block that is resident in the source
+ *              area is copied to the destination area.  If there is a run of
+ *              two or more blocks that are resident in the source area, they
+ *              are consolidated and copied to the destination area as a single
+ *              new block.
+ *
+ *  (4) The source area is reformatted as a scratch sector (i.e., its header
+ *      indicates an ID of 0xffff).  The area's garbage collection sequence
+ *      number is incremented prior to rewriting the header.  This area is now
+ *      the new scratch sector.
+ *
+ * NOTE:
+ *     Garbage collection invalidates all cached data blocks.  Whenever this
+ *     function is called, all existing nffs_cache_block pointers are rendered
+ *     invalid.  If you maintain any such pointers, you need to reset them
+ *     after calling this function.  Cached inodes are not invalidated by
+ *     garbage collection.
+ *
+ *     If a parent function potentially calls this function, the caller of the
+ *     parent function needs to explicitly check if garbage collection
+ *     occurred.  This is done by inspecting the nffs_gc_count variable before
+ *     and after calling the function.
+ *
+ * @param out_area_idx      On success, the ID of the cleaned up area gets
+ *                              written here.  Pass null if you do not need
+ *                              this information.
+ *
+ * @return                  0 on success; nonzero on error.
+ */
+int
+nffs_gc(uint8_t *out_area_idx)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *next;
+    struct nffs_area *from_area;
+    struct nffs_area *to_area;
+    struct nffs_inode_entry *inode_entry;
+    uint32_t area_offset;
+    uint8_t from_area_idx;
+    uint8_t area_idx;
+    int rc;
+    int i;
+
+    from_area_idx = nffs_gc_select_area();
+    from_area = nffs_areas + from_area_idx;
+    to_area = nffs_areas + nffs_scratch_area_idx;
+
+    rc = nffs_format_from_scratch_area(nffs_scratch_area_idx,
+                                       from_area->na_id);
+    if (rc != 0) {
+        return rc;
+    }
+
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        entry = SLIST_FIRST(nffs_hash + i);
+        while (entry != NULL) {
+            next = SLIST_NEXT(entry, nhe_next);
+
+            if (nffs_hash_id_is_inode(entry->nhe_id)) {
+                /* The inode gets copied if it is in the source area. */
+                nffs_flash_loc_expand(entry->nhe_flash_loc,
+                                      &area_idx, &area_offset);
+                inode_entry = (struct nffs_inode_entry *)entry;
+                if (area_idx == from_area_idx) {
+                    rc = nffs_gc_copy_inode(inode_entry,
+                                            nffs_scratch_area_idx);
+                    if (rc != 0) {
+                        return rc;
+                    }
+                }
+
+                /* If the inode is a file, all constituent data blocks that are
+                 * resident in the source area get copied.
+                 */
+                if (nffs_hash_id_is_file(entry->nhe_id)) {
+                    rc = nffs_gc_inode_blocks(inode_entry, from_area_idx,
+                                              nffs_scratch_area_idx, &next);
+                    if (rc != 0) {
+                        return rc;
+                    }
+                }
+            }
+
+            entry = next;
+        }
+    }
+
+    /* The amount of written data should never increase as a result of a gc
+     * cycle.
+     */
+    assert(to_area->na_cur <= from_area->na_cur);
+
+    /* Turn the source area into the new scratch area. */
+    from_area->na_gc_seq++;
+    rc = nffs_format_area(from_area_idx, 1);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (out_area_idx != NULL) {
+        *out_area_idx = nffs_scratch_area_idx;
+    }
+
+    nffs_scratch_area_idx = from_area_idx;
+
+    /* Garbage collection renders the cache invalid:
+     *     o All cached blocks are now invalid; drop them.
+     *     o Flash locations of inodes may have changed; the cached inodes need
+     *       updated to reflect this.
+     */
+    rc = nffs_cache_inode_refresh();
+    if (rc != 0) {
+        return rc;
+    }
+
+    /* Increment the garbage collection counter so that client code knows to
+     * reset its pointers to cached objects.
+     */
+    nffs_gc_count++;
+    STATS_INC(nffs_stats, nffs_gccnt);
+
+    return 0;
+}
+
+/**
+ * Repeatedly performs garbage collection cycles until there is enough free
+ * space to accommodate an object of the specified size.  If there still isn't
+ * enough free space after every area has been garbage collected, this function
+ * fails.
+ *
+ * @param space                 The number of bytes of free space required.
+ * @param out_area_idx          On success, the index of the area which can
+ *                                  accommodate the necessary data.
+ *
+ * @return                      0 on success;
+ *                              FS_EFULL if the necessary space could not be
+ *                                  freed.
+ *                              nonzero on other failure.
+ */
+int
+nffs_gc_until(uint32_t space, uint8_t *out_area_idx)
+{
+    int rc;
+    int i;
+
+    for (i = 0; i < nffs_num_areas; i++) {
+        rc = nffs_gc(out_area_idx);
+        if (rc != 0) {
+            return rc;
+        }
+
+        if (nffs_area_free_space(nffs_areas + *out_area_idx) >= space) {
+            return 0;
+        }
+    }
+
+    return FS_EFULL;
+}
diff --git a/src/nffs_hash.c b/src/nffs_hash.c
new file mode 100644
index 0000000..00d5fa2
--- /dev/null
+++ b/src/nffs_hash.c
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+struct nffs_hash_list *nffs_hash;
+
+uint32_t nffs_hash_next_dir_id;
+uint32_t nffs_hash_next_file_id;
+uint32_t nffs_hash_next_block_id;
+
+int
+nffs_hash_id_is_dir(uint32_t id)
+{
+    return id >= NFFS_ID_DIR_MIN && id < NFFS_ID_DIR_MAX;
+}
+
+int
+nffs_hash_id_is_file(uint32_t id)
+{
+    return id >= NFFS_ID_FILE_MIN && id < NFFS_ID_FILE_MAX;
+}
+
+int
+nffs_hash_id_is_inode(uint32_t id)
+{
+    return nffs_hash_id_is_dir(id) || nffs_hash_id_is_file(id);
+}
+
+int
+nffs_hash_id_is_block(uint32_t id)
+{
+    return id >= NFFS_ID_BLOCK_MIN && id < NFFS_ID_BLOCK_MAX;
+}
+
+static int
+nffs_hash_fn(uint32_t id)
+{
+    return id % NFFS_HASH_SIZE;
+}
+
+static struct nffs_hash_entry *
+nffs_hash_find_reorder(uint32_t id)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *prev;
+    struct nffs_hash_list *list;
+    int idx;
+
+    idx = nffs_hash_fn(id);
+    list = nffs_hash + idx;
+
+    prev = NULL;
+    SLIST_FOREACH(entry, list, nhe_next) {
+        if (entry->nhe_id == id) {
+            /* Put entry at the front of the list. */
+            if (prev != NULL) {
+                SLIST_NEXT(prev, nhe_next) = SLIST_NEXT(entry, nhe_next);
+                SLIST_INSERT_HEAD(list, entry, nhe_next);
+            }
+            return entry;
+        }
+
+        prev = entry;
+    }
+
+    return NULL;
+}
+
+struct nffs_hash_entry *
+nffs_hash_find(uint32_t id)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_list *list;
+    int idx;
+
+    idx = nffs_hash_fn(id);
+    list = nffs_hash + idx;
+
+    SLIST_FOREACH(entry, list, nhe_next) {
+        if (entry->nhe_id == id) {
+            return entry;
+        }
+    }
+
+    return NULL;
+}
+
+struct nffs_inode_entry *
+nffs_hash_find_inode(uint32_t id)
+{
+    struct nffs_hash_entry *entry;
+
+    assert(nffs_hash_id_is_inode(id));
+
+    entry = nffs_hash_find_reorder(id);
+    return (struct nffs_inode_entry *)entry;
+}
+
+struct nffs_hash_entry *
+nffs_hash_find_block(uint32_t id)
+{
+    struct nffs_hash_entry *entry;
+
+    assert(nffs_hash_id_is_block(id));
+
+    entry = nffs_hash_find_reorder(id);
+    return entry;
+}
+
+int
+nffs_hash_entry_is_dummy(struct nffs_hash_entry *he)
+{
+        return(he->nhe_flash_loc == NFFS_FLASH_LOC_NONE);
+}
+
+int
+nffs_hash_id_is_dummy(uint32_t id)
+{
+    struct nffs_hash_entry *he = nffs_hash_find(id);
+    if (he != NULL) {
+        return(he->nhe_flash_loc == NFFS_FLASH_LOC_NONE);
+    }
+    return 0;
+}
+
+void
+nffs_hash_insert(struct nffs_hash_entry *entry)
+{
+    struct nffs_hash_list *list;
+    struct nffs_inode_entry *nie;
+    int idx;
+
+    assert(nffs_hash_find(entry->nhe_id) == NULL);
+    idx = nffs_hash_fn(entry->nhe_id);
+    list = nffs_hash + idx;
+
+    SLIST_INSERT_HEAD(list, entry, nhe_next);
+    STATS_INC(nffs_stats, nffs_hashcnt_ins);
+
+    if (nffs_hash_id_is_inode(entry->nhe_id)) {
+        nie = nffs_hash_find_inode(entry->nhe_id);
+        assert(nie);
+        nffs_inode_setflags(nie, NFFS_INODE_FLAG_INHASH);
+    } else {
+        assert(nffs_hash_find(entry->nhe_id));
+    }
+}
+
+void
+nffs_hash_remove(struct nffs_hash_entry *entry)
+{
+    struct nffs_hash_list *list;
+    struct nffs_inode_entry *nie = NULL;
+    int idx;
+
+    if (nffs_hash_id_is_inode(entry->nhe_id)) {
+        nie = nffs_hash_find_inode(entry->nhe_id);
+        assert(nie);
+        assert(nffs_inode_getflags(nie, NFFS_INODE_FLAG_INHASH));
+    } else {
+        assert(nffs_hash_find(entry->nhe_id));
+    }
+
+    idx = nffs_hash_fn(entry->nhe_id);
+    list = nffs_hash + idx;
+
+    SLIST_REMOVE(list, entry, nffs_hash_entry, nhe_next);
+    STATS_INC(nffs_stats, nffs_hashcnt_rm);
+
+    if (nffs_hash_id_is_inode(entry->nhe_id) && nie) {
+        nffs_inode_unsetflags(nie, NFFS_INODE_FLAG_INHASH);
+    }
+    assert(nffs_hash_find(entry->nhe_id) == NULL);
+}
+
+int
+nffs_hash_init(void)
+{
+    int i;
+
+    free(nffs_hash);
+
+    nffs_hash = malloc(NFFS_HASH_SIZE * sizeof *nffs_hash);
+    if (nffs_hash == NULL) {
+        return FS_ENOMEM;
+    }
+
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        SLIST_INIT(nffs_hash + i);
+    }
+
+    return 0;
+}
diff --git a/src/nffs_inode.c b/src/nffs_inode.c
new file mode 100644
index 0000000..90285ce
--- /dev/null
+++ b/src/nffs_inode.c
@@ -0,0 +1,1293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include "testutil/testutil.h"
+#include "os/os_mempool.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+/* Partition the flash buffer into two equal halves; used for filename
+ * comparisons.
+ */
+#define NFFS_INODE_FILENAME_BUF_SZ   (sizeof nffs_flash_buf / 2)
+static uint8_t *nffs_inode_filename_buf0 = nffs_flash_buf;
+static uint8_t *nffs_inode_filename_buf1 =
+    nffs_flash_buf + NFFS_INODE_FILENAME_BUF_SZ;
+
+/** A list of directory inodes with pending unlink operations. */
+static struct nffs_hash_list nffs_inode_unlink_list;
+
+struct nffs_inode_entry *
+nffs_inode_entry_alloc(void)
+{
+    struct nffs_inode_entry *inode_entry;
+
+    inode_entry = os_memblock_get(&nffs_inode_entry_pool);
+    if (inode_entry != NULL) {
+        memset(inode_entry, 0, sizeof *inode_entry);
+    }
+
+    return inode_entry;
+}
+
+void
+nffs_inode_entry_free(struct nffs_inode_entry *inode_entry)
+{
+    if (inode_entry != NULL) {
+        assert(!nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH));
+        assert(nffs_hash_id_is_inode(inode_entry->nie_hash_entry.nhe_id));
+        os_memblock_put(&nffs_inode_entry_pool, inode_entry);
+    }
+}
+
+/**
+ * Allocates a inode entry.  If allocation fails due to memory exhaustion,
+ * garbage collection is performed and the allocation is retried.  This
+ * process is repeated until allocation is successful or all areas have been
+ * garbage collected.
+ *
+ * @param out_inode_entry           On success, the address of the allocated
+ *                                      inode gets written here.
+ *
+ * @return                          0 on successful allocation;
+ *                                  FS_ENOMEM on memory exhaustion;
+ *                                  other nonzero on garbage collection error.
+ */
+int
+nffs_inode_entry_reserve(struct nffs_inode_entry **out_inode_entry)
+{
+    int rc;
+
+    do {
+        *out_inode_entry = nffs_inode_entry_alloc();
+    } while (nffs_misc_gc_if_oom(*out_inode_entry, &rc));
+
+    return rc;
+}
+
+uint32_t
+nffs_inode_disk_size(const struct nffs_inode *inode)
+{
+    return sizeof (struct nffs_disk_inode) + inode->ni_filename_len;
+}
+
+int
+nffs_inode_read_disk(uint8_t area_idx, uint32_t offset,
+                    struct nffs_disk_inode *out_disk_inode)
+{
+    int rc;
+
+    STATS_INC(nffs_stats, nffs_readcnt_inode);
+    rc = nffs_flash_read(area_idx, offset, out_disk_inode,
+                         sizeof *out_disk_inode);
+    if (rc != 0) {
+        return rc;
+    }
+    if (!nffs_hash_id_is_inode(out_disk_inode->ndi_id)) {
+        return FS_EUNEXP;
+    }
+
+    return 0;
+}
+
+int
+nffs_inode_write_disk(const struct nffs_disk_inode *disk_inode,
+                      const char *filename, uint8_t area_idx,
+                      uint32_t area_offset)
+{
+    int rc;
+
+    /* Only the DELETED flag is ever written out to flash */
+    assert((disk_inode->ndi_flags & ~NFFS_INODE_FLAG_DELETED) == 0);
+
+    rc = nffs_flash_write(area_idx, area_offset, disk_inode,
+                          sizeof *disk_inode);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (disk_inode->ndi_filename_len != 0) {
+        rc = nffs_flash_write(area_idx, area_offset + sizeof *disk_inode,
+                              filename, disk_inode->ndi_filename_len);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    ASSERT_IF_TEST(nffs_crc_disk_inode_validate(disk_inode, area_idx,
+                                                area_offset) == 0);
+
+    return 0;
+}
+
+int
+nffs_inode_calc_data_length(struct nffs_inode_entry *inode_entry,
+                            uint32_t *out_len)
+{
+    struct nffs_hash_entry *cur;
+    struct nffs_block block;
+    int rc;
+
+    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+
+    *out_len = 0;
+
+    inode_entry->nie_blkcnt = 0;
+    cur = inode_entry->nie_last_block_entry;
+    while (cur != NULL) {
+        rc = nffs_block_from_hash_entry(&block, cur);
+        if (rc != 0) {
+            return rc;
+        }
+
+        *out_len += block.nb_data_len;
+        inode_entry->nie_blkcnt++;
+
+        cur = block.nb_prev;
+    }
+
+    return 0;
+}
+
+int
+nffs_inode_data_len(struct nffs_inode_entry *inode_entry, uint32_t *out_len)
+{
+    struct nffs_cache_inode *cache_inode;
+    int rc;
+
+    rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    *out_len = cache_inode->nci_file_size;
+
+    return 0;
+}
+
+static void
+nffs_inode_restore_from_dummy_entry(struct nffs_inode *out_inode,
+                                    struct nffs_inode_entry *inode_entry)
+{
+    memset(out_inode, 0, sizeof *out_inode);
+    out_inode->ni_inode_entry = inode_entry;
+}
+
+int
+nffs_inode_from_entry(struct nffs_inode *out_inode,
+                      struct nffs_inode_entry *entry)
+{
+    struct nffs_disk_inode disk_inode;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int cached_name_len;
+    int rc;
+
+    memset(out_inode, 0, sizeof *out_inode);
+
+    if (nffs_inode_is_dummy(entry)) {
+        nffs_inode_restore_from_dummy_entry(out_inode, entry);
+        return FS_ENOENT;
+    }
+
+    nffs_flash_loc_expand(entry->nie_hash_entry.nhe_flash_loc,
+                          &area_idx, &area_offset);
+
+    rc = nffs_inode_read_disk(area_idx, area_offset, &disk_inode);
+    if (rc != 0) {
+        return rc;
+    }
+
+    out_inode->ni_inode_entry = entry;
+    out_inode->ni_seq = disk_inode.ndi_seq;
+
+    /*
+     * Relink to parent if possible
+     * XXX does this belong here?
+     */
+    if (disk_inode.ndi_parent_id == NFFS_ID_NONE) {
+        out_inode->ni_parent = NULL;
+    } else {
+        out_inode->ni_parent = nffs_hash_find_inode(disk_inode.ndi_parent_id);
+    }
+    out_inode->ni_filename_len = disk_inode.ndi_filename_len;
+
+    if (out_inode->ni_filename_len > NFFS_SHORT_FILENAME_LEN) {
+        cached_name_len = NFFS_SHORT_FILENAME_LEN;
+    } else {
+        cached_name_len = out_inode->ni_filename_len;
+    }
+    if (cached_name_len != 0) {
+        STATS_INC(nffs_stats, nffs_readcnt_inodeent);
+        rc = nffs_flash_read(area_idx, area_offset + sizeof disk_inode,
+                             out_inode->ni_filename, cached_name_len);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    if (disk_inode.ndi_flags & NFFS_INODE_FLAG_DELETED) {
+        nffs_inode_setflags(out_inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
+        nffs_inode_setflags(entry, NFFS_INODE_FLAG_DELETED);
+    }
+
+    return 0;
+}
+
+uint32_t
+nffs_inode_parent_id(const struct nffs_inode *inode)
+{
+    if (inode->ni_parent == NULL) {
+        return NFFS_ID_NONE;
+    } else {
+        return inode->ni_parent->nie_hash_entry.nhe_id;
+    }
+}
+
+static int
+nffs_inode_delete_blocks_from_ram(struct nffs_inode_entry *inode_entry)
+{
+    int rc;
+
+    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+
+    while (inode_entry->nie_last_block_entry != NULL) {
+        rc = nffs_block_delete_from_ram(inode_entry->nie_last_block_entry);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Deletes the specified inode entry from the RAM representation.
+ *
+ * @param inode_entry           The inode entry to delete.
+ * @param ignore_corruption     Whether to proceed, despite file system
+ *                                  corruption errors (0/1).  This should only
+ *                                  be used when the file system is in a known
+ *                                  bad state (e.g., during the sweep phase of
+ *                                  the restore process).  If corruption
+ *                                  detected and ignored, this function deletes
+ *                                  what it can and returns a success code.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_inode_delete_from_ram(struct nffs_inode_entry *inode_entry,
+                           int ignore_corruption)
+{
+    int rc;
+
+    if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
+        /*
+         * Record the intention to delete the file
+         */
+        nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
+
+        rc = nffs_inode_delete_blocks_from_ram(inode_entry);
+        if (rc == FS_ECORRUPT && ignore_corruption) {
+            inode_entry->nie_last_block_entry = NULL;
+        } else if (rc != 0) {
+            return rc;
+        }
+    }
+
+    nffs_cache_inode_delete(inode_entry);
+    /*
+     * XXX Not deleting empty inode delete records from hash could prevent
+     * a case where we could lose delete records in a gc operation
+     */
+    nffs_hash_remove(&inode_entry->nie_hash_entry);
+    nffs_inode_entry_free(inode_entry);
+
+    return 0;
+}
+
+/**
+ * Inserts the specified inode entry into the unlink list.  Because a hash
+ * entry only has a single 'next' pointer, this function removes the entry from
+ * the hash table prior to inserting it into the unlink list.
+ *
+ * @param inode_entry           The inode entry to insert.
+ */
+static void
+nffs_inode_insert_unlink_list(struct nffs_inode_entry *inode_entry)
+{
+    nffs_hash_remove(&inode_entry->nie_hash_entry);
+    SLIST_INSERT_HEAD(&nffs_inode_unlink_list, &inode_entry->nie_hash_entry,
+                      nhe_next);
+}
+
+static int
+nffs_inode_dec_refcnt_priv(struct nffs_inode_entry *inode_entry,
+                           int ignore_corruption)
+{
+    int rc;
+
+    assert(inode_entry);
+    assert(inode_entry->nie_refcnt > 0);
+
+    inode_entry->nie_refcnt--;
+    if (inode_entry->nie_refcnt == 0) {
+        if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
+            rc = nffs_inode_delete_from_ram(inode_entry, ignore_corruption);
+            if (rc != 0) {
+                return rc;
+            }
+        } else {
+            nffs_inode_insert_unlink_list(inode_entry);
+        }
+    }
+
+    return 0;
+}
+
+int
+nffs_inode_inc_refcnt(struct nffs_inode_entry *inode_entry)
+{
+    assert(inode_entry);
+    inode_entry->nie_refcnt++;
+    return 0;
+}
+
+/**
+ * Decrements the reference count of the specified inode entry.
+ *
+ * @param inode_entry               The inode entry whose reference count
+ *                                      should be decremented.
+ */
+int
+nffs_inode_dec_refcnt(struct nffs_inode_entry *inode_entry)
+{
+    int rc;
+
+    rc = nffs_inode_dec_refcnt_priv(inode_entry, 0);
+    return rc;
+}
+
+/**
+ * Unlinks every directory inode entry present in the unlink list.  After this
+ * function completes:
+ *     o Each descendant directory is deleted from RAM.
+ *     o Each descendant file has its reference count decremented (and deleted
+ *       from RAM if its reference count reaches zero).
+ *
+ * @param inout_next            This parameter is only necessary if you are
+ *                                  calling this function during an iteration
+ *                                  of the entire hash table; pass null
+ *                                  otherwise.
+ *                              On input, this points to the next hash entry
+ *                                  you plan on processing.
+ *                              On output, this points to the next hash entry
+ *                                  that should be processed.
+ */
+static int
+nffs_inode_process_unlink_list(struct nffs_hash_entry **inout_next,
+                               int ignore_corruption)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_inode_entry *child_next;
+    struct nffs_inode_entry *child;
+    struct nffs_hash_entry *hash_entry;
+    int rc;
+
+    while ((hash_entry = SLIST_FIRST(&nffs_inode_unlink_list)) != NULL) {
+        assert(nffs_hash_id_is_dir(hash_entry->nhe_id));
+
+        SLIST_REMOVE_HEAD(&nffs_inode_unlink_list, nhe_next);
+
+        inode_entry = (struct nffs_inode_entry *)hash_entry;
+
+        /* Recursively unlink each child. */
+        child = SLIST_FIRST(&inode_entry->nie_child_list);
+        while (child != NULL) {
+            child_next = SLIST_NEXT(child, nie_sibling_next);
+
+            if (inout_next != NULL && *inout_next == &child->nie_hash_entry) {
+                *inout_next = &child_next->nie_hash_entry;
+            }
+
+            rc = nffs_inode_dec_refcnt_priv(child, ignore_corruption);
+            if (rc != 0) {
+                return rc;
+            }
+
+            child = child_next;
+        }
+
+
+
+        /* The directory is already removed from the hash table; just free its
+         * memory.
+         */
+        nffs_inode_entry_free(inode_entry);
+    }
+
+    return 0;
+}
+
+int
+nffs_inode_delete_from_disk(struct nffs_inode *inode)
+{
+    struct nffs_disk_inode disk_inode;
+    uint32_t offset;
+    uint8_t area_idx;
+    int rc;
+
+    /* Make sure it isn't already deleted. */
+    assert(inode->ni_parent != NULL);
+
+    rc = nffs_misc_reserve_space(sizeof disk_inode, &area_idx, &offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    inode->ni_seq++;
+
+    memset(&disk_inode, 0, sizeof disk_inode);
+    disk_inode.ndi_id = inode->ni_inode_entry->nie_hash_entry.nhe_id;
+    disk_inode.ndi_seq = inode->ni_seq;
+    disk_inode.ndi_parent_id = NFFS_ID_NONE;
+    disk_inode.ndi_flags = NFFS_INODE_FLAG_DELETED;
+    if (inode->ni_inode_entry->nie_last_block_entry) {
+        disk_inode.ndi_lastblock_id =
+                          inode->ni_inode_entry->nie_last_block_entry->nhe_id;
+    } else {
+        disk_inode.ndi_lastblock_id = NFFS_ID_NONE;
+    }
+    disk_inode.ndi_filename_len = 0;
+    nffs_crc_disk_inode_fill(&disk_inode, "");
+
+    rc = nffs_inode_write_disk(&disk_inode, "", area_idx, offset);
+    NFFS_LOG(DEBUG, "inode_del_disk: wrote unlinked ino %x to disk ref %d\n",
+               (unsigned int)disk_inode.ndi_id,
+               inode->ni_inode_entry->nie_refcnt);
+
+    /*
+     * Flag the incore inode as deleted to the inode won't get updated to
+     * disk. This could happen if the refcnt > 0 and there are future appends
+     * XXX only do this for files and not directories
+     */
+    if (nffs_hash_id_is_file(inode->ni_inode_entry->nie_hash_entry.nhe_id)) {
+        nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
+        NFFS_LOG(DEBUG, "inode_delete_from_disk: ino %x flag DELETE\n",
+                   (unsigned int)inode->ni_inode_entry->nie_hash_entry.nhe_id);
+
+    }
+
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Determines if an inode is an ancestor of another.
+ *
+ * NOTE: If the two inodes are equal, a positive result is indicated.
+ *
+ * @param anc                   The potential ancestor.
+ * @param des                   The potential descendent.
+ * @param out_is_ancestor       On success, the result gets written here
+ *                                  (1 if an ancestor relationship is
+ *                                  detected).
+ *
+ * @return                      0 on success; FS_E[...] on error.
+ */
+static int
+nffs_inode_is_ancestor(struct nffs_inode_entry *anc,
+                       struct nffs_inode_entry *des,
+                       int *out_is_ancestor)
+{
+    struct nffs_inode_entry *cur;
+    struct nffs_inode inode;
+    int rc;
+
+    /* Only directories can be ancestors. */
+    if (!nffs_hash_id_is_dir(anc->nie_hash_entry.nhe_id)) {
+        *out_is_ancestor = 0;
+        return 0;
+    }
+
+    /* Trace des's heritage until we hit anc or there are no more parents. */
+    cur = des;
+    while (cur != NULL && cur != anc) {
+        rc = nffs_inode_from_entry(&inode, cur);
+        if (rc != 0) {
+            *out_is_ancestor = 0;
+            return rc;
+        }
+        cur = inode.ni_parent;
+    }
+
+    *out_is_ancestor = cur == anc;
+    return 0;
+}
+
+int
+nffs_inode_rename(struct nffs_inode_entry *inode_entry,
+                  struct nffs_inode_entry *new_parent,
+                  const char *new_filename)
+{
+    struct nffs_disk_inode disk_inode;
+    struct nffs_inode inode;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int filename_len;
+    int ancestor;
+    int rc;
+
+    /* Don't allow a directory to be moved into a descendent directory. */
+    rc = nffs_inode_is_ancestor(inode_entry, new_parent, &ancestor);
+    if (rc != 0) {
+        return rc;
+    }
+    if (ancestor) {
+        return FS_EINVAL;
+    }
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (inode.ni_parent != new_parent) {
+        if (inode.ni_parent != NULL) {
+            nffs_inode_remove_child(&inode);
+        }
+        if (new_parent != NULL) {
+            rc = nffs_inode_add_child(new_parent, inode.ni_inode_entry);
+            if (rc != 0) {
+                return rc;
+            }
+        }
+        inode.ni_parent = new_parent;
+    }
+
+    if (new_filename != NULL) {
+        filename_len = strlen(new_filename);
+    } else {
+        filename_len = inode.ni_filename_len;
+        nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
+                              &area_idx, &area_offset);
+
+        STATS_INC(nffs_stats, nffs_readcnt_rename);
+        rc = nffs_flash_read(area_idx,
+                             area_offset + sizeof (struct nffs_disk_inode),
+                             nffs_flash_buf, filename_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        new_filename = (char *)nffs_flash_buf;
+    }
+
+    rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len,
+                                 &area_idx, &area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    memset(&disk_inode, 0, sizeof disk_inode);
+    disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id;
+    disk_inode.ndi_seq = inode.ni_seq + 1;
+    disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode);
+    disk_inode.ndi_flags = 0;
+    disk_inode.ndi_filename_len = filename_len;
+    if (inode_entry->nie_last_block_entry &&
+        inode_entry->nie_last_block_entry->nhe_id != NFFS_ID_NONE)
+        disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id;
+    else 
+        disk_inode.ndi_lastblock_id = NFFS_ID_NONE;
+    nffs_crc_disk_inode_fill(&disk_inode, new_filename);
+
+    rc = nffs_inode_write_disk(&disk_inode, new_filename, area_idx,
+                               area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    inode_entry->nie_hash_entry.nhe_flash_loc =
+        nffs_flash_loc(area_idx, area_offset);
+
+    return 0;
+}
+
+int
+nffs_inode_update(struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_disk_inode disk_inode;
+    struct nffs_inode inode;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    char *filename;
+    int filename_len;
+    int rc;
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    /*
+     * if rc == FS_ENOENT, file is dummy is unlinked and so
+     * can not be updated to disk.
+     */
+    if (rc == FS_ENOENT)
+        assert(nffs_inode_is_dummy(inode_entry));
+    if (rc != 0) {
+        return rc;
+    }
+
+    assert(inode_entry->nie_hash_entry.nhe_flash_loc != NFFS_FLASH_LOC_NONE);
+
+    filename_len = inode.ni_filename_len;
+    nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
+                          &area_idx, &area_offset);
+    STATS_INC(nffs_stats, nffs_readcnt_update);
+    rc = nffs_flash_read(area_idx,
+                         area_offset + sizeof (struct nffs_disk_inode),
+                         nffs_flash_buf, filename_len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    filename = (char *)nffs_flash_buf;
+
+    rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len,
+                                 &area_idx, &area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    memset(&disk_inode, 0, sizeof disk_inode);
+    disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id;
+    disk_inode.ndi_seq = inode.ni_seq + 1;
+    disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode);
+    disk_inode.ndi_flags = 0;
+    disk_inode.ndi_filename_len = filename_len;
+
+    assert(nffs_hash_id_is_block(inode_entry->nie_last_block_entry->nhe_id));
+    disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id;
+
+    nffs_crc_disk_inode_fill(&disk_inode, filename);
+
+    NFFS_LOG(DEBUG, "nffs_inode_update writing inode %x last block %x\n",
+             (unsigned int)disk_inode.ndi_id,
+             (unsigned int)disk_inode.ndi_lastblock_id);
+
+    rc = nffs_inode_write_disk(&disk_inode, filename, area_idx,
+                               area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    inode_entry->nie_hash_entry.nhe_flash_loc =
+        nffs_flash_loc(area_idx, area_offset);
+    return 0;
+}
+
+static int
+nffs_inode_read_filename_chunk(const struct nffs_inode *inode,
+                               uint8_t filename_offset, void *buf, int len)
+{
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    assert(filename_offset + len <= inode->ni_filename_len);
+
+    nffs_flash_loc_expand(inode->ni_inode_entry->nie_hash_entry.nhe_flash_loc,
+                          &area_idx, &area_offset);
+    area_offset += sizeof (struct nffs_disk_inode) + filename_offset;
+
+    STATS_INC(nffs_stats, nffs_readcnt_filename);
+    rc = nffs_flash_read(area_idx, area_offset, buf, len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Retrieves the filename of the specified inode.  The retrieved
+ * filename is always null-terminated.  To ensure enough space to hold the full
+ * filename plus a null-termintor, a destination buffer of size
+ * (NFFS_FILENAME_MAX_LEN + 1) should be used.
+ *
+ * @param inode_entry           The inode entry to query.
+ * @param max_len               The size of the "out_name" character buffer.
+ * @param out_name              On success, the entry's filename is written
+ *                                  here; always null-terminated.
+ * @param out_name_len          On success, contains the actual length of the
+ *                                  filename, NOT including the
+ *                                  null-terminator.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_inode_read_filename(struct nffs_inode_entry *inode_entry, size_t max_len,
+                         char *out_name, uint8_t *out_full_len)
+{
+    struct nffs_inode inode;
+    int read_len;
+    int rc;
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (max_len > inode.ni_filename_len) {
+        read_len = inode.ni_filename_len;
+    } else {
+        read_len = max_len - 1;
+    }
+
+    rc = nffs_inode_read_filename_chunk(&inode, 0, out_name, read_len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    out_name[read_len] = '\0';
+
+    return 0;
+}
+
+int
+nffs_inode_add_child(struct nffs_inode_entry *parent,
+                     struct nffs_inode_entry *child)
+{
+    struct nffs_inode_entry *prev;
+    struct nffs_inode_entry *cur;
+    struct nffs_inode child_inode;
+    struct nffs_inode cur_inode;
+    int cmp;
+    int rc;
+
+    assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id));
+    assert(!nffs_inode_getflags(child, NFFS_INODE_FLAG_INTREE));
+
+    rc = nffs_inode_from_entry(&child_inode, child);
+    if (rc != 0) {
+        return rc;
+    }
+
+    prev = NULL;
+    SLIST_FOREACH(cur, &parent->nie_child_list, nie_sibling_next) {
+        assert(cur != child);
+        rc = nffs_inode_from_entry(&cur_inode, cur);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = nffs_inode_filename_cmp_flash(&child_inode, &cur_inode, &cmp);
+        if (rc != 0) {
+            return rc;
+        }
+
+        if (cmp < 0) {
+            break;
+        }
+
+        prev = cur;
+    }
+
+    if (prev == NULL) {
+        SLIST_INSERT_HEAD(&parent->nie_child_list, child, nie_sibling_next);
+    } else {
+        SLIST_INSERT_AFTER(prev, child, nie_sibling_next);
+    }
+    nffs_inode_setflags(child, NFFS_INODE_FLAG_INTREE);
+
+    return 0;
+}
+
+void
+nffs_inode_remove_child(struct nffs_inode *child)
+{
+    struct nffs_inode_entry *parent;
+
+    assert(nffs_inode_getflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE));
+    parent = child->ni_parent;
+    assert(parent != NULL);
+    assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id));
+    SLIST_REMOVE(&parent->nie_child_list, child->ni_inode_entry,
+                 nffs_inode_entry, nie_sibling_next);
+    SLIST_NEXT(child->ni_inode_entry, nie_sibling_next) = NULL;
+    nffs_inode_unsetflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE);
+}
+
+int
+nffs_inode_filename_cmp_ram(const struct nffs_inode *inode,
+                            const char *name, int name_len,
+                            int *result)
+{
+    int short_len;
+    int chunk_len;
+    int rem_len;
+    int off;
+    int rc;
+
+    if (name_len < inode->ni_filename_len) {
+        short_len = name_len;
+    } else {
+        short_len = inode->ni_filename_len;
+    }
+
+    if (short_len <= NFFS_SHORT_FILENAME_LEN) {
+        chunk_len = short_len;
+    } else {
+        chunk_len = NFFS_SHORT_FILENAME_LEN;
+    }
+    *result = strncmp((char *)inode->ni_filename, name, chunk_len);
+
+    off = chunk_len;
+    while (*result == 0 && off < short_len) {
+        rem_len = short_len - off;
+        if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) {
+            chunk_len = NFFS_INODE_FILENAME_BUF_SZ;
+        } else {
+            chunk_len = rem_len;
+        }
+
+        rc = nffs_inode_read_filename_chunk(inode, off,
+                                            nffs_inode_filename_buf0,
+                                            chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        *result = strncmp((char *)nffs_inode_filename_buf0, name + off,
+                          chunk_len);
+        off += chunk_len;
+    }
+
+    if (*result == 0) {
+        *result = inode->ni_filename_len - name_len;
+    }
+
+    return 0;
+}
+
+/*
+ * Compare filenames in flash
+ */
+int
+nffs_inode_filename_cmp_flash(const struct nffs_inode *inode1,
+                              const struct nffs_inode *inode2,
+                              int *result)
+{
+    int short_len;
+    int chunk_len;
+    int rem_len;
+    int off;
+    int rc;
+
+    if (inode1->ni_filename_len < inode2->ni_filename_len) {
+        short_len = inode1->ni_filename_len;
+    } else {
+        short_len = inode2->ni_filename_len;
+    }
+
+    if (short_len <= NFFS_SHORT_FILENAME_LEN) {
+        chunk_len = short_len;
+    } else {
+        chunk_len = NFFS_SHORT_FILENAME_LEN;
+    }
+    *result = strncmp((char *)inode1->ni_filename,
+                      (char *)inode2->ni_filename,
+                      chunk_len);
+
+    off = chunk_len;
+    while (*result == 0 && off < short_len) {
+        rem_len = short_len - off;
+        if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) {
+            chunk_len = NFFS_INODE_FILENAME_BUF_SZ;
+        } else {
+            chunk_len = rem_len;
+        }
+
+        rc = nffs_inode_read_filename_chunk(inode1, off,
+                                            nffs_inode_filename_buf0,
+                                            chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = nffs_inode_read_filename_chunk(inode2, off,
+                                            nffs_inode_filename_buf1,
+                                            chunk_len);
+        if (rc != 0) {
+            return rc;
+        }
+
+        *result = strncmp((char *)nffs_inode_filename_buf0,
+                          (char *)nffs_inode_filename_buf1,
+                          chunk_len);
+        off += chunk_len;
+    }
+
+    if (*result == 0) {
+        *result = inode1->ni_filename_len - inode2->ni_filename_len;
+    }
+
+    return 0;
+}
+
+/**
+ * Finds the set of blocks composing the specified address range within the
+ * supplied file inode.  This information is returned in the form of a
+ * seek_info object.
+ *
+ * @param inode_entry           The file inode to seek within.
+ * @param offset                The start address of the region to seek to.
+ * @param length                The length of the region to seek to.
+ * @param out_seek_info         On success, this gets populated with the result
+ *                                  of the seek operation.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_inode_seek(struct nffs_inode_entry *inode_entry, uint32_t offset,
+                uint32_t length, struct nffs_seek_info *out_seek_info)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_hash_entry *cur_entry;
+    struct nffs_block block;
+    uint32_t block_start;
+    uint32_t cur_offset;
+    uint32_t seek_end;
+    int rc;
+
+    assert(length > 0);
+    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+
+    rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (offset > cache_inode->nci_file_size) {
+        return FS_EOFFSET;
+    }
+    if (offset == cache_inode->nci_file_size) {
+        memset(&out_seek_info->nsi_last_block, 0,
+               sizeof out_seek_info->nsi_last_block);
+        out_seek_info->nsi_last_block.nb_hash_entry = NULL;
+        out_seek_info->nsi_block_file_off = 0;
+        out_seek_info->nsi_file_len = cache_inode->nci_file_size;
+        return 0;
+    }
+
+    seek_end = offset + length;
+
+    cur_entry = inode_entry->nie_last_block_entry;
+    cur_offset = cache_inode->nci_file_size;
+
+    while (1) {
+        rc = nffs_block_from_hash_entry(&block, cur_entry);
+        if (rc != 0) {
+            return rc;
+        }
+
+        block_start = cur_offset - block.nb_data_len;
+        if (seek_end > block_start) {
+            out_seek_info->nsi_last_block = block;
+            out_seek_info->nsi_block_file_off = block_start;
+            out_seek_info->nsi_file_len = cache_inode->nci_file_size;
+            return 0;
+        }
+
+        cur_offset = block_start;
+        cur_entry = block.nb_prev;
+    }
+}
+
+/**
+ * Reads data from the specified file inode.
+ *
+ * @param inode_entry           The inode to read from.
+ * @param offset                The offset within the file to start the read
+ *                                  at.
+ * @param len                   The number of bytes to attempt to read.
+ * @param out_data              On success, the read data gets written here.
+ * @param out_len               On success, the number of bytes actually read
+ *                                  gets written here.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_inode_read(struct nffs_inode_entry *inode_entry, uint32_t offset,
+                uint32_t len, void *out_data, uint32_t *out_len)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_cache_block *cache_block;
+    uint32_t block_end;
+    uint32_t dst_off;
+    uint32_t src_off;
+    uint32_t src_end;
+    uint16_t block_off;
+    uint16_t chunk_sz;
+    uint8_t *dptr;
+    int rc;
+
+    if (len == 0) {
+        if (out_len != NULL) {
+            *out_len = 0;
+        }
+        return 0;
+    }
+
+    rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    src_end = offset + len;
+    if (src_end > cache_inode->nci_file_size) {
+        src_end = cache_inode->nci_file_size;
+    }
+
+    /* Initialize variables for the first iteration. */
+    dst_off = src_end - offset;
+    src_off = src_end;
+    dptr = out_data;
+    cache_block = NULL;
+
+    /* Read each relevant block into the destination buffer, iterating in
+     * reverse.
+     */
+    while (dst_off > 0) {
+        if (cache_block == NULL) {
+            rc = nffs_cache_seek(cache_inode, src_off - 1, &cache_block);
+            if (rc != 0) {
+                return rc;
+            }
+        }
+
+        if (cache_block->ncb_file_offset < offset) {
+            block_off = offset - cache_block->ncb_file_offset;
+        } else {
+            block_off = 0;
+        }
+
+        block_end = cache_block->ncb_file_offset +
+                    cache_block->ncb_block.nb_data_len;
+        chunk_sz = cache_block->ncb_block.nb_data_len - block_off;
+        if (block_end > src_end) {
+            chunk_sz -= block_end - src_end;
+        }
+
+        dst_off -= chunk_sz;
+        src_off -= chunk_sz;
+
+        rc = nffs_block_read_data(&cache_block->ncb_block, block_off, chunk_sz,
+                                  dptr + dst_off);
+        if (rc != 0) {
+            return rc;
+        }
+
+        cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, ncb_link);
+    }
+
+    if (out_len != NULL) {
+        *out_len = src_end - offset;
+    }
+
+    return 0;
+}
+
+static int
+nffs_inode_unlink_from_ram_priv(struct nffs_inode *inode,
+                                int ignore_corruption,
+                                struct nffs_hash_entry **out_next)
+{
+    int rc;
+
+    if (inode->ni_parent != NULL) {
+        nffs_inode_remove_child(inode);
+    }
+
+    /*
+     * Regardless of whether the inode is removed from hashlist, we record
+     * the intention to delete it here.
+     */
+    nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
+
+    if (nffs_hash_id_is_dir(inode->ni_inode_entry->nie_hash_entry.nhe_id)) {
+        nffs_inode_insert_unlink_list(inode->ni_inode_entry);
+        rc = nffs_inode_process_unlink_list(out_next, ignore_corruption);
+    } else {
+        rc = nffs_inode_dec_refcnt_priv(inode->ni_inode_entry,
+                                        ignore_corruption);
+    }
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Unlinks the specified inode from the RAM representation.  If this procedure
+ * causes the inode's reference count to drop to zero, the inode is deleted
+ * from RAM.  This function does not write anything to disk.
+ *
+ * @param inode                 The inode to unlink.
+ * @param out_next              This parameter is only necessary if you are
+ *                                  calling this function during an iteration
+ *                                  of the entire hash table; pass null
+ *                                  otherwise.
+ *                              On output, this points to the next hash entry
+ *                                  that should be processed.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_inode_unlink_from_ram(struct nffs_inode *inode,
+                           struct nffs_hash_entry **out_next)
+{
+    int rc;
+
+    rc = nffs_inode_unlink_from_ram_priv(inode, 0, out_next);
+    return rc;
+}
+
+/**
+ * Deletes the specified inode entry from the RAM representation.  If file
+ * system corruption is detected, this function completes as much of the unlink
+ * process as it cam and returns a success code.  This should only be used when
+ * the file system is in a known bad state (e.g., during the sweep phase of the
+ * restore process).
+ *
+ * @param inode_entry           The inode entry to delete.
+ * @param out_next              This parameter is only necessary if you are
+ *                                  calling this function during an iteration
+ *                                  of the entire hash table; pass null
+ *                                  otherwise.
+ *                              On output, this points to the next hash entry
+ *                                  that should be processed.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_inode_unlink_from_ram_corrupt_ok(struct nffs_inode *inode,
+                                      struct nffs_hash_entry **out_next)
+{
+    int rc;
+
+    rc = nffs_inode_unlink_from_ram_priv(inode, 1, out_next);
+    return rc;
+}
+
+
+/**
+ * Unlinks the file or directory represented by the specified inode.  If the
+ * inode represents a directory, all the directory's descendants are
+ * recursively unlinked.  Any open file handles refering to an unlinked file
+ * remain valid, and can be read from and written to.
+ *
+ * When an inode is unlinked, the following events occur:
+ *     o inode deletion record is written to disk.
+ *     o inode is removed from parent's child list.
+ *     o inode's reference count is decreased (if this brings it to zero, the
+ *       inode is fully deleted from RAM).
+ *
+ * @param inode             The inode to unlink.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_inode_unlink(struct nffs_inode *inode)
+{
+    int rc;
+
+    rc = nffs_inode_delete_from_disk(inode);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_inode_unlink_from_ram(inode, NULL);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/*
+ * Return true if inode is a dummy inode, that was allocated as a
+ * place holder in the case that an inode is restored before it's parent.
+ */
+int
+nffs_inode_is_dummy(struct nffs_inode_entry *inode_entry)
+{
+    if (inode_entry->nie_flash_loc == NFFS_FLASH_LOC_NONE) {
+        /*
+         * set if not already XXX can delete after debug
+         */
+        nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
+        return 1;
+    }
+
+    if (inode_entry == nffs_root_dir) {
+        return 0;
+    } else {
+        return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
+    }
+}
+
+/*
+ * Return true if inode is marked as deleted.
+ */
+int
+nffs_inode_is_deleted(struct nffs_inode_entry *inode_entry)
+{
+    assert(inode_entry);
+
+    return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED);
+}
+
+int
+nffs_inode_setflags(struct nffs_inode_entry *entry, uint8_t flag)
+{
+    /*
+     * We shouldn't be setting flags to already deleted inodes
+     */
+    entry->nie_flags |= flag;
+    return (int)entry->nie_flags;
+}
+
+int
+nffs_inode_unsetflags(struct nffs_inode_entry *entry, uint8_t flag)
+{
+    entry->nie_flags &= ~flag;
+    return (int)entry->nie_flags;
+}
+
+int
+nffs_inode_getflags(struct nffs_inode_entry *entry, uint8_t flag)
+{
+    return (int)(entry->nie_flags & flag);
+}
diff --git a/src/nffs_misc.c b/src/nffs_misc.c
new file mode 100644
index 0000000..f79d9df
--- /dev/null
+++ b/src/nffs_misc.c
@@ -0,0 +1,511 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include "flash_map/flash_map.h"
+#include "hal/hal_bsp.h"
+#include "hal/hal_flash_int.h"
+#include "os/os_malloc.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+/**
+ * Determines if the file system contains a valid root directory.  For the root
+ * directory to be valid, it must be present and have the following traits:
+ *     o ID equal to NFFS_ID_ROOT_DIR.
+ *     o No parent inode.
+ *
+ * @return                      0 if there is a valid root directory;
+ *                              FS_ECORRUPT if there is not a valid root
+ *                                  directory;
+ *                              nonzero on other error.
+ */
+int
+nffs_misc_validate_root_dir(void)
+{
+    struct nffs_inode inode;
+    int rc;
+
+    if (nffs_root_dir == NULL) {
+        return FS_ECORRUPT;
+    }
+
+    if (nffs_root_dir->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) {
+        return FS_ECORRUPT;
+    }
+
+    rc = nffs_inode_from_entry(&inode, nffs_root_dir);
+    /*
+     * nffs_root_dir is automatically flagged a "dummy" inode but it's special
+     */
+    if (rc != 0 && rc != FS_ENOENT) {
+        return rc;
+    }
+
+    if (inode.ni_parent != NULL) {
+        return FS_ECORRUPT;
+    }
+
+    return 0;
+}
+
+/**
+ * Determines if the system contains a valid scratch area.  For a scratch area
+ * to be valid, it must be at least as large as the other areas in the file
+ * system.
+ *
+ * @return                      0 if there is a valid scratch area;
+ *                              FS_ECORRUPT otherwise.
+ */
+int
+nffs_misc_validate_scratch(void)
+{
+    uint32_t scratch_len;
+    int i;
+
+    if (nffs_scratch_area_idx == NFFS_AREA_ID_NONE) {
+        /* No scratch area. */
+        return FS_ECORRUPT;
+    }
+
+    scratch_len = nffs_areas[nffs_scratch_area_idx].na_length;
+    for (i = 0; i < nffs_num_areas; i++) {
+        if (nffs_areas[i].na_length > scratch_len) {
+            return FS_ECORRUPT;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Performs a garbage cycle to free up memory, if necessary.  This function
+ * should be called repeatedly until either:
+ *   o The subsequent allocation is successful, or
+ *   o Garbage collection is not successfully performed (indicated by a return
+ *     code other than FS_EAGAIN).
+ *
+ * This function determines if garbage collection is necessary by inspecting
+ * the value of the supplied "resource" parameter.  If resource is null, that
+ * implies that allocation failed.
+ *
+ * This function will not initiate garbage collection if all areas have already
+ * been collected in an attempt to free memory for the allocation in question.
+ *
+ * @param resource              The result of the allocation attempt; null
+ *                                  implies that garbage collection is
+ *                                  necessary.
+ * @param out_rc                The status of this operation gets written here.
+ *                                   0: garbage collection was successful or
+ *                                       unnecessary.
+ *                                   FS_EFULL: Garbage collection was not
+ *                                       performed because all areas have
+ *                                       already been collected.
+ *                                   Other nonzero: garbage collection failed.
+ *
+ * @return                      FS_EAGAIN if garbage collection was
+ *                                  successfully performed and the allocation
+ *                                  should be retried;
+ *                              Other value if the allocation should not be
+ *                                  retried; the value of the out_rc parameter
+ *                                  indicates whether allocation was successful
+ *                                  or there was an error.
+ */
+int
+nffs_misc_gc_if_oom(void *resource, int *out_rc)
+{
+    /**
+     * Keeps track of the number of repeated garbage collection cycles.
+     * Necessary for ensuring GC stops after all areas have been collected.
+     */
+    static uint8_t total_gc_cycles;
+
+    if (resource != NULL) {
+        /* Allocation succeeded.  Reset cycle count in preparation for the next
+         * allocation failure.
+         */
+        total_gc_cycles = 0;
+        *out_rc = 0;
+        return 0;
+    }
+
+    /* If every area has already been garbage collected, there is nothing else
+     * that can be done ("- 1" to account for the scratch area).
+     */
+    if (total_gc_cycles >= nffs_num_areas - 1) {
+        *out_rc = FS_ENOMEM;
+        return 0;
+    }
+
+    /* Attempt a garbage collection on the next area. */
+    *out_rc = nffs_gc(NULL);
+    total_gc_cycles++;
+    STATS_INC(nffs_stats, nffs_gccnt);
+    if (*out_rc != 0) {
+        return 0;
+    }
+
+    /* Indicate that garbage collection was successfully performed. */
+    return 1;
+}
+
+/**
+ * Reserves the specified number of bytes within the specified area.
+ *
+ * @param area_idx              The index of the area to reserve from.
+ * @param space                 The number of bytes of free space required.
+ * @param out_area_offset       On success, the offset within the area gets
+ *                                  written here.
+ *
+ * @return                      0 on success;
+ *                              FS_EFULL if the area has insufficient free
+ *                                  space.
+ */
+static int
+nffs_misc_reserve_space_area(uint8_t area_idx, uint16_t space,
+                             uint32_t *out_area_offset)
+{
+    const struct nffs_area *area;
+    uint32_t available;
+
+    area = nffs_areas + area_idx;
+    available = area->na_length - area->na_cur;
+    if (available >= space) {
+        *out_area_offset = area->na_cur;
+        return 0;
+    }
+
+    return FS_EFULL;
+}
+
+/**
+ * Finds an area that can accommodate an object of the specified size.  If no
+ * such area exists, this function performs a garbage collection cycle.
+ *
+ * @param space                 The number of bytes of free space required.
+ * @param out_area_idx          On success, the index of the suitable area gets
+ *                                  written here.
+ * @param out_area_offset       On success, the offset within the suitable area
+ *                                  gets written here.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_misc_reserve_space(uint16_t space,
+                        uint8_t *out_area_idx, uint32_t *out_area_offset)
+{
+    uint8_t area_idx;
+    int rc;
+    int i;
+
+    /* Find the first area with sufficient free space. */
+    for (i = 0; i < nffs_num_areas; i++) {
+        if (i != nffs_scratch_area_idx) {
+            rc = nffs_misc_reserve_space_area(i, space, out_area_offset);
+            if (rc == 0) {
+                *out_area_idx = i;
+                return 0;
+            }
+        }
+    }
+
+    /* No area can accommodate the request.  Garbage collect until an area
+     * has enough space.
+     */
+    rc = nffs_gc_until(space, &area_idx);
+    if (rc != 0) {
+        return rc;
+    }
+
+    /* Now try to reserve space.  If insufficient space was reclaimed with
+     * garbage collection, the above call would have failed, so this should
+     * succeed.
+     */
+    rc = nffs_misc_reserve_space_area(area_idx, space, out_area_offset);
+    assert(rc == 0);
+
+    *out_area_idx = area_idx;
+
+    return rc;
+}
+
+int
+nffs_misc_set_num_areas(uint8_t num_areas)
+{
+    if (num_areas == 0) {
+        free(nffs_areas);
+        nffs_areas = NULL;
+    } else {
+        nffs_areas = realloc(nffs_areas, num_areas * sizeof *nffs_areas);
+        if (nffs_areas == NULL) {
+            return FS_ENOMEM;
+        }
+    }
+
+    nffs_num_areas = num_areas;
+
+    return 0;
+}
+
+/**
+ * Calculates the data length of the largest block that could fit in an area of
+ * the specified size.
+ */
+static uint32_t
+nffs_misc_area_capacity_one(uint32_t area_length)
+{
+    return area_length -
+           sizeof (struct nffs_disk_area) -
+           sizeof (struct nffs_disk_block);
+}
+
+/**
+ * Calculates the data length of the largest block that could fit as a pair in
+ * an area of the specified size.
+ */
+static uint32_t
+nffs_misc_area_capacity_two(uint32_t area_length)
+{
+    return (area_length - sizeof (struct nffs_disk_area)) / 2 -
+           sizeof (struct nffs_disk_block);
+}
+
+/**
+ * Calculates and sets the maximum block data length that the system supports.
+ * The result of the calculation is the greatest number which satisfies all of
+ * the following restrictions:
+ *     o No more than half the size of the smallest area.
+ *     o No more than 2048.
+ *     o No smaller than the data length of any existing data block.
+ *
+ * @param min_size              The minimum allowed data length.  This is the
+ *                                  data length of the largest block currently
+ *                                  in the file system.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_misc_set_max_block_data_len(uint16_t min_data_len)
+{
+    uint32_t smallest_area;
+    uint32_t half_smallest;
+    int i;
+
+    smallest_area = -1;
+    for (i = 0; i < nffs_num_areas; i++) {
+        if (nffs_areas[i].na_length < smallest_area) {
+            smallest_area = nffs_areas[i].na_length;
+        }
+    }
+
+    /* Don't allow a data block size bigger than the smallest area. */
+    if (nffs_misc_area_capacity_one(smallest_area) < min_data_len) {
+        return FS_ECORRUPT;
+    }
+
+    half_smallest = nffs_misc_area_capacity_two(smallest_area);
+    if (half_smallest < NFFS_BLOCK_MAX_DATA_SZ_MAX) {
+        nffs_block_max_data_sz = half_smallest;
+    } else {
+        nffs_block_max_data_sz = NFFS_BLOCK_MAX_DATA_SZ_MAX;
+    }
+
+    if (nffs_block_max_data_sz < min_data_len) {
+        nffs_block_max_data_sz = min_data_len;
+    }
+
+    return 0;
+}
+
+int
+nffs_misc_create_lost_found_dir(void)
+{
+    int rc;
+
+    rc = nffs_path_new_dir("/lost+found", &nffs_lost_found_dir);
+    switch (rc) {
+    case 0:
+        return 0;
+
+    case FS_EEXIST:
+        rc = nffs_path_find_inode_entry("/lost+found", &nffs_lost_found_dir);
+        return rc;
+
+    default:
+        return rc;
+    }
+}
+
+
+/**
+ * Fully resets the nffs RAM representation.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_misc_reset(void)
+{
+    int rc;
+
+    nffs_cache_clear();
+
+    rc = os_mempool_init(&nffs_file_pool, nffs_config.nc_num_files,
+                         sizeof (struct nffs_file), nffs_file_mem,
+                         "nffs_file_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mempool_init(&nffs_inode_entry_pool, nffs_config.nc_num_inodes,
+                         sizeof (struct nffs_inode_entry), nffs_inode_mem,
+                         "nffs_inode_entry_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mempool_init(&nffs_block_entry_pool, nffs_config.nc_num_blocks,
+                         sizeof (struct nffs_hash_entry), nffs_block_entry_mem,
+                         "nffs_block_entry_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mempool_init(&nffs_cache_inode_pool,
+                         nffs_config.nc_num_cache_inodes,
+                         sizeof (struct nffs_cache_inode),
+                         nffs_cache_inode_mem, "nffs_cache_inode_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mempool_init(&nffs_cache_block_pool,
+                         nffs_config.nc_num_cache_blocks,
+                         sizeof (struct nffs_cache_block),
+                         nffs_cache_block_mem, "nffs_cache_block_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = os_mempool_init(&nffs_dir_pool,
+                         nffs_config.nc_num_dirs,
+                         sizeof (struct nffs_dir),
+                         nffs_dir_mem, "nffs_dir_pool");
+    if (rc != 0) {
+        return FS_EOS;
+    }
+
+    rc = nffs_hash_init();
+    if (rc != 0) {
+        return rc;
+    }
+
+    free(nffs_areas);
+    nffs_areas = NULL;
+    nffs_num_areas = 0;
+
+    nffs_root_dir = NULL;
+    nffs_lost_found_dir = NULL;
+    nffs_scratch_area_idx = NFFS_AREA_ID_NONE;
+
+    nffs_hash_next_file_id = NFFS_ID_FILE_MIN;
+    nffs_hash_next_dir_id = NFFS_ID_DIR_MIN;
+    nffs_hash_next_block_id = NFFS_ID_BLOCK_MIN;
+
+    return 0;
+}
+
+/**
+ * Indicates whether a valid filesystem has been initialized, either via
+ * detection or formatting.
+ *
+ * @return                  1 if a file system is present; 0 otherwise.
+ */
+int
+nffs_misc_ready(void)
+{
+    return nffs_root_dir != NULL;
+}
+
+
+/*
+ * Turn flash region into a set of areas for NFFS use.
+ *
+ * Limit the number of regions we return to be less than *cnt.
+ * If sector count within region exceeds that, collect multiple sectors
+ * to a region.
+ */
+int
+nffs_misc_desc_from_flash_area(int id, int *cnt, struct nffs_area_desc *nad)
+{
+    int i, j;
+    const struct hal_flash *hf;
+    const struct flash_area *fa;
+    int max_cnt, move_on;
+    int first_idx, last_idx;
+    uint32_t start, size;
+    uint32_t min_size;
+    int rc;
+
+    first_idx = last_idx = -1;
+    max_cnt = *cnt;
+    *cnt = 0;
+
+    rc = flash_area_open(id, &fa);
+    if (rc != 0) {
+        return -1;
+    }
+
+    hf = hal_bsp_flash_dev(fa->fa_device_id);
+    for (i = 0; i < hf->hf_sector_cnt; i++) {
+        hf->hf_itf->hff_sector_info(hf, i, &start, &size);
+        if (start >= fa->fa_off && start < fa->fa_off + fa->fa_size) {
+            if (first_idx == -1) {
+                first_idx = i;
+            }
+            last_idx = i;
+            *cnt = *cnt + 1;
+        }
+    }
+    if (*cnt > max_cnt) {
+        min_size = fa->fa_size / max_cnt;
+    } else {
+        min_size = 0;
+    }
+    *cnt = 0;
+
+    move_on = 1;
+    for (i = first_idx, j = 0; i < last_idx + 1; i++) {
+        hf->hf_itf->hff_sector_info(hf, i, &start, &size);
+        if (move_on) {
+            nad[j].nad_flash_id = fa->fa_device_id;
+            nad[j].nad_offset = start;
+            nad[j].nad_length = size;
+            *cnt = *cnt + 1;
+            move_on = 0;
+        } else {
+            nad[j].nad_length += size;
+        }
+        if (nad[j].nad_length >= min_size) {
+            j++;
+            move_on = 1;
+        }
+    }
+    nad[*cnt].nad_length = 0;
+    return 0;
+}
diff --git a/src/nffs_path.c b/src/nffs_path.c
new file mode 100644
index 0000000..03a8a99
--- /dev/null
+++ b/src/nffs_path.c
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+int
+nffs_path_parse_next(struct nffs_path_parser *parser)
+{
+    const char *slash_start;
+    int token_end;
+    int token_len;
+
+    if (parser->npp_token_type == NFFS_PATH_TOKEN_LEAF) {
+        return FS_EINVAL;
+    }
+
+    slash_start = strchr(parser->npp_path + parser->npp_off, '/');
+    if (slash_start == NULL) {
+        if (parser->npp_token_type == NFFS_PATH_TOKEN_NONE) {
+            return FS_EINVAL;
+        }
+        parser->npp_token_type = NFFS_PATH_TOKEN_LEAF;
+        token_len = strlen(parser->npp_path + parser->npp_off);
+    } else {
+        parser->npp_token_type = NFFS_PATH_TOKEN_BRANCH;
+        token_end = slash_start - parser->npp_path;
+        token_len = token_end - parser->npp_off;
+    }
+
+    if (token_len > NFFS_FILENAME_MAX_LEN) {
+        return FS_EINVAL;
+    }
+
+    parser->npp_token = parser->npp_path + parser->npp_off;
+    parser->npp_token_len = token_len;
+    parser->npp_off += token_len + 1;
+
+    return 0;
+}
+
+void
+nffs_path_parser_new(struct nffs_path_parser *parser, const char *path)
+{
+    parser->npp_token_type = NFFS_PATH_TOKEN_NONE;
+    parser->npp_path = path;
+    parser->npp_off = 0;
+}
+
+static int
+nffs_path_find_child(struct nffs_inode_entry *parent,
+                     const char *name, int name_len,
+                     struct nffs_inode_entry **out_inode_entry)
+{
+    struct nffs_inode_entry *cur;
+    struct nffs_inode inode;
+    int cmp;
+    int rc;
+
+    SLIST_FOREACH(cur, &parent->nie_child_list, nie_sibling_next) {
+        rc = nffs_inode_from_entry(&inode, cur);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = nffs_inode_filename_cmp_ram(&inode, name, name_len, &cmp);
+        if (rc != 0) {
+            return rc;
+        }
+
+        if (cmp == 0) {
+            *out_inode_entry = cur;
+            return 0;
+        }
+        if (cmp > 0) {
+            break;
+        }
+    }
+
+    return FS_ENOENT;
+}
+
+/*
+ * Return the inode and optionally it's parent associated with the input path
+ * nffs_path_parser struct used to track location in hierarchy
+ */
+int
+nffs_path_find(struct nffs_path_parser *parser,
+               struct nffs_inode_entry **out_inode_entry,
+               struct nffs_inode_entry **out_parent)
+{
+    struct nffs_inode_entry *parent;
+    struct nffs_inode_entry *inode_entry;
+    int rc;
+
+    *out_inode_entry = NULL;
+    if (out_parent != NULL) {
+        *out_parent = NULL;
+    }
+
+    inode_entry = NULL;
+    while (1) {
+        parent = inode_entry;
+        rc = nffs_path_parse_next(parser);
+        if (rc != 0) {
+            return rc;
+        }
+
+        switch (parser->npp_token_type) {
+        case NFFS_PATH_TOKEN_BRANCH:
+            if (parent == NULL) {
+                /* First directory must be root. */
+                if (parser->npp_token_len != 0) {
+                    return FS_ENOENT;
+                }
+
+                inode_entry = nffs_root_dir;
+            } else {
+                /* Ignore empty intermediate directory names. */
+                if (parser->npp_token_len == 0) {
+                    break;
+                }
+
+                rc = nffs_path_find_child(parent, parser->npp_token,
+                                          parser->npp_token_len, &inode_entry);
+                if (rc != 0) {
+                    goto done;
+                }
+            }
+            break;
+        case NFFS_PATH_TOKEN_LEAF:
+            if (parent == NULL) {
+                /* First token must be root directory. */
+                return FS_ENOENT;
+            }
+
+            if (parser->npp_token_len == 0) {
+                /* If the path ends with a slash, the leaf is the parent, not
+                 * the trailing empty token.
+                 */
+                inode_entry = parent;
+                rc = 0;
+            } else {
+                rc = nffs_path_find_child(parent, parser->npp_token,
+                                          parser->npp_token_len, &inode_entry);
+            }
+            goto done;
+        }
+    }
+
+done:
+    *out_inode_entry = inode_entry;
+    if (out_parent != NULL) {
+        *out_parent = parent;
+    }
+    return rc;
+}
+
+int
+nffs_path_find_inode_entry(const char *filename,
+                           struct nffs_inode_entry **out_inode_entry)
+{
+    struct nffs_path_parser parser;
+    int rc;
+
+    nffs_path_parser_new(&parser, filename);
+    rc = nffs_path_find(&parser, out_inode_entry, NULL);
+
+    return rc;
+}
+
+/**
+ * Unlinks the file or directory at the specified path.  If the path refers to
+ * a directory, all the directory's descendants are recursively unlinked.  Any
+ * open file handles refering to an unlinked file remain valid, and can be
+ * read from and written to.
+ *
+ * @path                    The path of the file or directory to unlink.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+int
+nffs_path_unlink(const char *path)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_inode inode;
+    int rc;
+
+    rc = nffs_path_find_inode_entry(path, &inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_inode_unlink(&inode);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Performs a rename and / or move of the specified source path to the
+ * specified destination.  The source path can refer to either a file or a
+ * directory.  All intermediate directories in the destination path must
+ * already have been created.  If the source path refers to a file, the
+ * destination path must contain a full filename path (i.e., if performing a
+ * move, the destination path should end with the same filename in the source
+ * path).  If an object already exists at the specified destination path, this
+ * function causes it to be unlinked prior to the rename (i.e., the destination
+ * gets clobbered).
+ *
+ * @param from              The source path.
+ * @param to                The destination path.
+ *
+ * @return                  0 on success;
+ *                          nonzero on failure.
+ */
+int
+nffs_path_rename(const char *from, const char *to)
+{
+    struct nffs_path_parser parser;
+    struct nffs_inode_entry *from_inode_entry;
+    struct nffs_inode_entry *to_inode_entry;
+    struct nffs_inode_entry *from_parent;
+    struct nffs_inode_entry *to_parent;
+    struct nffs_inode inode;
+    int rc;
+
+    nffs_path_parser_new(&parser, from);
+    rc = nffs_path_find(&parser, &from_inode_entry, &from_parent);
+    if (rc != 0) {
+        return rc;
+    }
+
+    nffs_path_parser_new(&parser, to);
+    rc = nffs_path_find(&parser, &to_inode_entry, &to_parent);
+    switch (rc) {
+    case 0:
+        /* The user is clobbering something with the rename. */
+        if (nffs_hash_id_is_dir(from_inode_entry->nie_hash_entry.nhe_id) ^
+            nffs_hash_id_is_dir(to_inode_entry->nie_hash_entry.nhe_id)) {
+
+            /* Cannot clobber one type of file with another. */
+            return FS_EINVAL;
+        }
+
+        rc = nffs_inode_from_entry(&inode, to_inode_entry);
+        if (rc != 0) {
+            return rc;
+        }
+
+        /*
+         * Don't allow renames if the inode has been deleted
+         * Side-effect is that we've restored the inode as needed.
+         */
+        if (nffs_inode_is_deleted(from_inode_entry)) {
+            assert(0);
+            return FS_ENOENT;
+        }
+
+        rc = nffs_inode_unlink(&inode);
+        if (rc != 0) {
+            return rc;
+        }
+        break;
+
+    case FS_ENOENT:
+        assert(to_parent != NULL);
+        if (parser.npp_token_type != NFFS_PATH_TOKEN_LEAF) {
+            /* Intermediate directory doesn't exist. */
+            return FS_EINVAL;
+        }
+        break;
+
+    default:
+        return rc;
+    }
+
+    rc = nffs_inode_rename(from_inode_entry, to_parent, parser.npp_token);
+    if (rc != 0) {
+        return rc;
+    }
+
+    return 0;
+}
+
+/**
+ * Creates a new directory at the specified path.
+ *
+ * @param path                  The path of the directory to create.
+ *
+ * @return                      0 on success;
+ *                              FS_EEXIST if there is another file or
+ *                                  directory at the specified path.
+ *                              FS_ENONT if a required intermediate directory
+ *                                  does not exist.
+ */
+int
+nffs_path_new_dir(const char *path, struct nffs_inode_entry **out_inode_entry)
+{
+    struct nffs_path_parser parser;
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_inode_entry *parent;
+    int rc;
+
+    nffs_path_parser_new(&parser, path);
+    rc = nffs_path_find(&parser, &inode_entry, &parent);
+    if (rc == 0) {
+        return FS_EEXIST;
+    }
+    if (rc != FS_ENOENT) {
+        return rc;
+    }
+    if (parser.npp_token_type != NFFS_PATH_TOKEN_LEAF || parent == NULL) {
+        return FS_ENOENT;
+    }
+
+    rc = nffs_file_new(parent, parser.npp_token, parser.npp_token_len, 1, 
+                       &inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    if (out_inode_entry != NULL) {
+        *out_inode_entry = inode_entry;
+    }
+
+    return 0;
+}
diff --git a/src/nffs_priv.h b/src/nffs_priv.h
new file mode 100644
index 0000000..887850e
--- /dev/null
+++ b/src/nffs_priv.h
@@ -0,0 +1,534 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NFFS_PRIV_
+#define H_NFFS_PRIV_
+
+#include <inttypes.h>
+#include "log/log.h"
+#include "os/queue.h"
+#include "os/os_mempool.h"
+#include "nffs/nffs.h"
+#include "fs/fs.h"
+#include "crc/crc16.h"
+#include "stats/stats.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NFFS_HASH_SIZE               256
+
+#define NFFS_ID_DIR_MIN              0
+#define NFFS_ID_DIR_MAX              0x10000000
+#define NFFS_ID_FILE_MIN             0x10000000
+#define NFFS_ID_FILE_MAX             0x80000000
+#define NFFS_ID_BLOCK_MIN            0x80000000
+#define NFFS_ID_BLOCK_MAX            0xffffffff
+
+#define NFFS_ID_ROOT_DIR             0
+#define NFFS_ID_NONE                 0xffffffff
+#define NFFS_HASH_ENTRY_NONE         0xffffffff
+
+#define NFFS_AREA_MAGIC0             0xb98a31e2
+#define NFFS_AREA_MAGIC1             0x7fb0428c
+#define NFFS_AREA_MAGIC2             0xace08253
+#define NFFS_AREA_MAGIC3             0xb185fc8e
+#define NFFS_BLOCK_MAGIC             0x53ba23b9
+#define NFFS_INODE_MAGIC             0x925f8bc0
+
+#define NFFS_AREA_ID_NONE            0xff
+#define NFFS_AREA_VER_0                 0
+#define NFFS_AREA_VER_1              1
+#define NFFS_AREA_VER                NFFS_AREA_VER_1
+#define NFFS_AREA_OFFSET_ID          23
+
+#define NFFS_SHORT_FILENAME_LEN      3
+
+#define NFFS_BLOCK_MAX_DATA_SZ_MAX   2048
+
+#define NFFS_DETECT_FAIL_IGNORE     1
+#define NFFS_DETECT_FAIL_FORMAT     2
+
+/** On-disk representation of an area header. */
+struct nffs_disk_area {
+    uint32_t nda_magic[4];  /* NFFS_AREA_MAGIC{0,1,2,3} */
+    uint32_t nda_length;    /* Total size of area, in bytes. */
+    uint8_t nda_ver;        /* Current nffs version: 0 */
+    uint8_t nda_gc_seq;     /* Garbage collection count. */
+    uint8_t reserved8;
+    uint8_t nda_id;         /* 0xff if scratch area. */
+};
+
+/** On-disk representation of an inode (file or directory). */
+struct nffs_disk_inode {
+    uint32_t ndi_id;            /* Unique object ID. */
+    uint32_t ndi_parent_id;     /* Object ID of parent directory inode. */
+    uint32_t ndi_lastblock_id;     /* Object ID of parent directory inode. */
+    uint16_t ndi_seq;           /* Sequence number; greater supersedes
+                                   lesser. */
+    uint16_t reserved16;
+    uint8_t ndi_flags;            /* flags */
+    uint8_t ndi_filename_len;   /* Length of filename, in bytes. */
+    uint16_t ndi_crc16;         /* Covers rest of header and filename. */
+    /* Followed by filename. */
+};
+
+#define NFFS_DISK_INODE_OFFSET_CRC  18
+
+/** On-disk representation of a data block. */
+struct nffs_disk_block {
+    uint32_t ndb_id;        /* Unique object ID. */
+    uint32_t ndb_inode_id;  /* Object ID of owning inode. */
+    uint32_t ndb_prev_id;   /* Object ID of previous block in file;
+                               NFFS_ID_NONE if this is the first block. */
+    uint16_t ndb_seq;       /* Sequence number; greater supersedes lesser. */
+    uint16_t reserved16;
+    uint16_t ndb_data_len;  /* Length of data contents, in bytes. */
+    uint16_t ndb_crc16;     /* Covers rest of header and data. */
+    /* Followed by 'ndb_data_len' bytes of data. */
+};
+
+#define NFFS_DISK_BLOCK_OFFSET_CRC  18
+
+/**
+ * What gets stored in the hash table.  Each entry represents a data block or
+ * an inode.
+ */
+struct nffs_hash_entry {
+    SLIST_ENTRY(nffs_hash_entry) nhe_next;
+    uint32_t nhe_id;        /* 0 - 0x7fffffff if inode; else if block. */
+    uint32_t nhe_flash_loc; /* Upper-byte = area idx; rest = area offset. */
+};
+
+
+SLIST_HEAD(nffs_hash_list, nffs_hash_entry);
+SLIST_HEAD(nffs_inode_list, nffs_inode_entry);
+
+/** Each inode hash entry is actually one of these. */
+struct nffs_inode_entry {
+    struct nffs_hash_entry nie_hash_entry;
+    SLIST_ENTRY(nffs_inode_entry) nie_sibling_next;
+    union {
+        struct nffs_inode_list nie_child_list;           /* If directory */
+        struct nffs_hash_entry *nie_last_block_entry;    /* If file */
+        uint32_t nie_lastblock_id;
+    };
+    uint8_t nie_refcnt;
+    uint8_t nie_flags;
+    uint8_t nie_blkcnt;
+    uint8_t reserved8;
+};
+
+#define    NFFS_INODE_FLAG_FREE        0x00
+#define    NFFS_INODE_FLAG_DUMMY       0x01    /* inode is a dummy */
+#define    NFFS_INODE_FLAG_DUMMYPARENT 0x02    /* parent not in cache */
+#define    NFFS_INODE_FLAG_DUMMYLSTBLK 0x04    /* lastblock not in cache */
+#define    NFFS_INODE_FLAG_DUMMYINOBLK 0x08    /* dummy inode for blk */
+#define    NFFS_INODE_FLAG_OBSOLETE    0x10    /* always replace if same ID */
+#define    NFFS_INODE_FLAG_INTREE      0x20    /* in directory structure */
+#define    NFFS_INODE_FLAG_INHASH      0x40    /* in hash table */
+#define    NFFS_INODE_FLAG_DELETED     0x80    /* inode deleted */
+
+#define nie_id            nie_hash_entry.nhe_id
+#define nie_flash_loc    nie_hash_entry.nhe_flash_loc
+
+/** Full inode representation; not stored permanently RAM. */
+struct nffs_inode {
+    struct nffs_inode_entry *ni_inode_entry; /* Points to real inode entry. */
+    uint32_t ni_seq;                         /* Sequence number; greater
+                                                supersedes lesser. */
+    struct nffs_inode_entry *ni_parent;      /* Points to parent directory. */
+    uint8_t ni_filename_len;                 /* # chars in filename. */
+    uint8_t ni_filename[NFFS_SHORT_FILENAME_LEN]; /* First 3 bytes. */
+};
+
+/** Full data block representation; not stored permanently RAM. */
+struct nffs_block {
+    struct nffs_hash_entry *nb_hash_entry;   /* Points to real block entry. */
+    uint32_t nb_seq;                         /* Sequence number; greater
+                                                supersedes lesser. */
+    struct nffs_inode_entry *nb_inode_entry; /* Owning inode. */
+    struct nffs_hash_entry *nb_prev;         /* Previous block in file. */
+    uint16_t nb_data_len;                    /* # of data bytes in block. */
+};
+
+struct nffs_file {
+    struct fs_ops *fops;
+    struct nffs_inode_entry *nf_inode_entry;
+    uint32_t nf_offset;
+    uint8_t nf_access_flags;
+};
+
+struct nffs_area {
+    uint32_t na_offset;
+    uint32_t na_length;
+    uint32_t na_cur;
+    uint16_t na_id;
+    uint8_t na_gc_seq;
+    uint8_t na_flash_id;
+    uint32_t na_obsolete;   /* deleted bytecount */
+};
+
+struct nffs_disk_object {
+    int ndo_type;
+    uint8_t ndo_area_idx;
+    uint32_t ndo_offset;
+    union {
+        struct nffs_disk_inode ndo_disk_inode;
+        struct nffs_disk_block ndo_disk_block;
+    } ndo_un_obj;
+};
+
+#define ndo_disk_inode    ndo_un_obj.ndo_disk_inode
+#define ndo_disk_block    ndo_un_obj.ndo_disk_block
+
+struct nffs_seek_info {
+    struct nffs_block nsi_last_block;
+    uint32_t nsi_block_file_off;
+    uint32_t nsi_file_len;
+};
+
+#define NFFS_OBJECT_TYPE_INODE   1
+#define NFFS_OBJECT_TYPE_BLOCK   2
+
+#define NFFS_PATH_TOKEN_NONE     0
+#define NFFS_PATH_TOKEN_BRANCH   1
+#define NFFS_PATH_TOKEN_LEAF     2
+
+struct nffs_path_parser {
+    int npp_token_type;
+    const char *npp_path;
+    const char *npp_token;
+    int npp_token_len;
+    int npp_off;
+};
+
+/** Represents a single cached data block. */
+struct nffs_cache_block {
+    TAILQ_ENTRY(nffs_cache_block) ncb_link; /* Next / prev cached block. */
+    struct nffs_block ncb_block;            /* Full data block. */
+    uint32_t ncb_file_offset;               /* File offset of this block. */
+};
+
+TAILQ_HEAD(nffs_cache_block_list, nffs_cache_block);
+
+/** Represents a single cached file inode. */
+struct nffs_cache_inode {
+    TAILQ_ENTRY(nffs_cache_inode) nci_link;        /* Sorted; LRU at tail. */
+    struct nffs_inode nci_inode;                   /* Full inode. */
+    struct nffs_cache_block_list nci_block_list;   /* List of cached blocks. */
+    uint32_t nci_file_size;                        /* Total file size. */
+};
+
+struct nffs_dirent {
+    struct fs_ops *fops;
+    struct nffs_inode_entry *nde_inode_entry;
+};
+
+struct nffs_dir {
+    struct fs_ops *fops;
+    struct nffs_inode_entry *nd_parent_inode_entry;
+    struct nffs_dirent nd_dirent;
+};
+
+STATS_SECT_START(nffs_stats)
+    STATS_SECT_ENTRY(nffs_hashcnt_ins)
+    STATS_SECT_ENTRY(nffs_hashcnt_rm)
+    STATS_SECT_ENTRY(nffs_object_count)
+    STATS_SECT_ENTRY(nffs_iocnt_read)
+    STATS_SECT_ENTRY(nffs_iocnt_write)
+    STATS_SECT_ENTRY(nffs_gccnt)
+    STATS_SECT_ENTRY(nffs_readcnt_data)
+    STATS_SECT_ENTRY(nffs_readcnt_block)
+    STATS_SECT_ENTRY(nffs_readcnt_crc)
+    STATS_SECT_ENTRY(nffs_readcnt_copy)
+    STATS_SECT_ENTRY(nffs_readcnt_format)
+    STATS_SECT_ENTRY(nffs_readcnt_gccollate)
+    STATS_SECT_ENTRY(nffs_readcnt_inode)
+    STATS_SECT_ENTRY(nffs_readcnt_inodeent)
+    STATS_SECT_ENTRY(nffs_readcnt_rename)
+    STATS_SECT_ENTRY(nffs_readcnt_update)
+    STATS_SECT_ENTRY(nffs_readcnt_filename)
+    STATS_SECT_ENTRY(nffs_readcnt_object)
+    STATS_SECT_ENTRY(nffs_readcnt_detect)
+STATS_SECT_END
+extern STATS_SECT_DECL(nffs_stats) nffs_stats;
+
+extern void *nffs_file_mem;
+extern void *nffs_block_entry_mem;
+extern void *nffs_inode_mem;
+extern void *nffs_cache_inode_mem;
+extern void *nffs_cache_block_mem;
+extern void *nffs_dir_mem;
+extern struct os_mempool nffs_file_pool;
+extern struct os_mempool nffs_dir_pool;
+extern struct os_mempool nffs_inode_entry_pool;
+extern struct os_mempool nffs_block_entry_pool;
+extern struct os_mempool nffs_cache_inode_pool;
+extern struct os_mempool nffs_cache_block_pool;
+extern uint32_t nffs_hash_next_file_id;
+extern uint32_t nffs_hash_next_dir_id;
+extern uint32_t nffs_hash_next_block_id;
+extern struct nffs_area *nffs_areas;
+extern uint8_t nffs_num_areas;
+extern uint8_t nffs_scratch_area_idx;
+extern uint16_t nffs_block_max_data_sz;
+extern unsigned int nffs_gc_count;
+extern struct nffs_area_desc *nffs_current_area_descs;
+
+#define NFFS_FLASH_BUF_SZ        256
+extern uint8_t nffs_flash_buf[NFFS_FLASH_BUF_SZ];
+
+extern struct nffs_hash_list *nffs_hash;
+extern struct nffs_inode_entry *nffs_root_dir;
+extern struct nffs_inode_entry *nffs_lost_found_dir;
+
+extern struct log nffs_log;
+
+/* @area */
+int nffs_area_magic_is_set(const struct nffs_disk_area *disk_area);
+int nffs_area_is_scratch(const struct nffs_disk_area *disk_area);
+int nffs_area_is_current_version(const struct nffs_disk_area *disk_area);
+void nffs_area_to_disk(const struct nffs_area *area,
+                       struct nffs_disk_area *out_disk_area);
+uint32_t nffs_area_free_space(const struct nffs_area *area);
+int nffs_area_find_corrupt_scratch(uint16_t *out_good_idx,
+                                   uint16_t *out_bad_idx);
+
+/* @block */
+struct nffs_hash_entry *nffs_block_entry_alloc(void);
+void nffs_block_entry_free(struct nffs_hash_entry *entry);
+int nffs_block_entry_reserve(struct nffs_hash_entry **out_block_entry);
+int nffs_block_read_disk(uint8_t area_idx, uint32_t area_offset,
+                         struct nffs_disk_block *out_disk_block);
+int nffs_block_write_disk(const struct nffs_disk_block *disk_block,
+                          const void *data,
+                          uint8_t *out_area_idx, uint32_t *out_area_offset);
+int nffs_block_delete_from_ram(struct nffs_hash_entry *entry);
+void nffs_block_delete_list_from_ram(struct nffs_block *first,
+                                     struct nffs_block *last);
+void nffs_block_delete_list_from_disk(const struct nffs_block *first,
+                                      const struct nffs_block *last);
+void nffs_block_to_disk(const struct nffs_block *block,
+                        struct nffs_disk_block *out_disk_block);
+int nffs_block_find_predecessor(struct nffs_hash_entry *start,
+                                uint32_t sought_id);
+int nffs_block_from_hash_entry_no_ptrs(struct nffs_block *out_block,
+                                       struct nffs_hash_entry *entry);
+int nffs_block_from_hash_entry(struct nffs_block *out_block,
+                               struct nffs_hash_entry *entry);
+int nffs_block_read_data(const struct nffs_block *block, uint16_t offset,
+                         uint16_t length, void *dst);
+int nffs_block_is_dummy(struct nffs_hash_entry *entry);
+
+/* @cache */
+void nffs_cache_inode_delete(const struct nffs_inode_entry *inode_entry);
+int nffs_cache_inode_ensure(struct nffs_cache_inode **out_entry,
+                            struct nffs_inode_entry *inode_entry);
+int nffs_cache_inode_refresh(void);
+void nffs_cache_inode_range(const struct nffs_cache_inode *cache_inode,
+                            uint32_t *out_start, uint32_t *out_end);
+int nffs_cache_seek(struct nffs_cache_inode *cache_inode, uint32_t to,
+                    struct nffs_cache_block **out_cache_block);
+void nffs_cache_clear(void);
+
+/* @crc */
+int nffs_crc_flash(uint16_t initial_crc, uint8_t area_idx,
+                   uint32_t area_offset, uint32_t len, uint16_t *out_crc);
+uint16_t nffs_crc_disk_block_hdr(const struct nffs_disk_block *disk_block);
+int nffs_crc_disk_block_validate(const struct nffs_disk_block *disk_block,
+                                uint8_t area_idx, uint32_t area_offset);
+void nffs_crc_disk_block_fill(struct nffs_disk_block *disk_block,
+                              const void *data);
+int nffs_crc_disk_inode_validate(const struct nffs_disk_inode *disk_inode,
+                                 uint8_t area_idx, uint32_t area_offset);
+void nffs_crc_disk_inode_fill(struct nffs_disk_inode *disk_inode,
+                              const char *filename);
+
+/* @config */
+void nffs_config_init(void);
+
+/* @dir */
+int nffs_dir_open(const char *path, struct nffs_dir **out_dir);
+int nffs_dir_read(struct nffs_dir *dir, struct nffs_dirent **out_dirent);
+int nffs_dir_close(struct nffs_dir *dir);
+
+/* @file */
+int nffs_file_open(struct nffs_file **out_file, const char *filename,
+                   uint8_t access_flags);
+int nffs_file_seek(struct nffs_file *file, uint32_t offset);
+int nffs_file_read(struct nffs_file *file, uint32_t len, void *out_data,
+                   uint32_t *out_len);
+int nffs_file_close(struct nffs_file *file);
+int nffs_file_new(struct nffs_inode_entry *parent, const char *filename,
+                  uint8_t filename_len, int is_dir,
+                  struct nffs_inode_entry **out_inode_entry);
+
+/* @format */
+int nffs_format_area(uint8_t area_idx, int is_scratch);
+int nffs_format_from_scratch_area(uint8_t area_idx, uint8_t area_id);
+int nffs_format_full(const struct nffs_area_desc *area_descs);
+
+/* @gc */
+int nffs_gc(uint8_t *out_area_idx);
+int nffs_gc_until(uint32_t space, uint8_t *out_area_idx);
+
+/* @flash */
+struct nffs_area *nffs_flash_find_area(uint16_t logical_id);
+int nffs_flash_read(uint8_t area_idx, uint32_t offset,
+                    void *data, uint32_t len);
+int nffs_flash_write(uint8_t area_idx, uint32_t offset,
+                     const void *data, uint32_t len);
+int nffs_flash_copy(uint8_t area_id_from, uint32_t offset_from,
+                    uint8_t area_id_to, uint32_t offset_to,
+                    uint32_t len);
+uint32_t nffs_flash_loc(uint8_t area_idx, uint32_t offset);
+void nffs_flash_loc_expand(uint32_t flash_loc, uint8_t *out_area_idx,
+                           uint32_t *out_area_offset);
+
+/* @hash */
+int nffs_hash_id_is_dir(uint32_t id);
+int nffs_hash_id_is_file(uint32_t id);
+int nffs_hash_id_is_inode(uint32_t id);
+int nffs_hash_id_is_block(uint32_t id);
+struct nffs_hash_entry *nffs_hash_find(uint32_t id);
+struct nffs_inode_entry *nffs_hash_find_inode(uint32_t id);
+struct nffs_hash_entry *nffs_hash_find_block(uint32_t id);
+void nffs_hash_insert(struct nffs_hash_entry *entry);
+void nffs_hash_remove(struct nffs_hash_entry *entry);
+int nffs_hash_init(void);
+int nffs_hash_entry_is_dummy(struct nffs_hash_entry *he);
+int nffs_hash_id_is_dummy(uint32_t id);
+
+/* @inode */
+struct nffs_inode_entry *nffs_inode_entry_alloc(void);
+void nffs_inode_entry_free(struct nffs_inode_entry *inode_entry);
+int nffs_inode_entry_reserve(struct nffs_inode_entry **out_inode_entry);
+int nffs_inode_calc_data_length(struct nffs_inode_entry *inode_entry,
+                                uint32_t *out_len);
+int nffs_inode_data_len(struct nffs_inode_entry *inode_entry,
+                        uint32_t *out_len);
+uint32_t nffs_inode_parent_id(const struct nffs_inode *inode);
+int nffs_inode_delete_from_disk(struct nffs_inode *inode);
+int nffs_inode_entry_from_disk(struct nffs_inode_entry *out_inode,
+                               const struct nffs_disk_inode *disk_inode,
+                               uint8_t area_idx, uint32_t offset);
+int nffs_inode_rename(struct nffs_inode_entry *inode_entry,
+                      struct nffs_inode_entry *new_parent,
+                      const char *new_filename);
+int nffs_inode_update(struct nffs_inode_entry *inode_entry);
+void nffs_inode_insert_block(struct nffs_inode *inode,
+                             struct nffs_block *block);
+int nffs_inode_read_disk(uint8_t area_idx, uint32_t offset,
+                         struct nffs_disk_inode *out_disk_inode);
+int nffs_inode_write_disk(const struct nffs_disk_inode *disk_inode,
+                          const char *filename, uint8_t area_idx,
+                          uint32_t offset);
+int nffs_inode_inc_refcnt(struct nffs_inode_entry *inode_entry);
+int nffs_inode_dec_refcnt(struct nffs_inode_entry *inode_entry);
+int nffs_inode_add_child(struct nffs_inode_entry *parent,
+                         struct nffs_inode_entry *child);
+void nffs_inode_remove_child(struct nffs_inode *child);
+int nffs_inode_is_root(const struct nffs_disk_inode *disk_inode);
+int nffs_inode_read_filename(struct nffs_inode_entry *inode_entry,
+                             size_t max_len, char *out_name,
+                             uint8_t *out_full_len);
+int nffs_inode_filename_cmp_ram(const struct nffs_inode *inode,
+                                const char *name, int name_len,
+                                int *result);
+int nffs_inode_filename_cmp_flash(const struct nffs_inode *inode1,
+                                  const struct nffs_inode *inode2,
+                                  int *result);
+int nffs_inode_read(struct nffs_inode_entry *inode_entry, uint32_t offset,
+                    uint32_t len, void *data, uint32_t *out_len);
+int nffs_inode_seek(struct nffs_inode_entry *inode_entry, uint32_t offset,
+                    uint32_t length, struct nffs_seek_info *out_seek_info);
+int nffs_inode_from_entry(struct nffs_inode *out_inode,
+                          struct nffs_inode_entry *entry);
+int nffs_inode_unlink_from_ram(struct nffs_inode *inode,
+                               struct nffs_hash_entry **out_next);
+int nffs_inode_unlink_from_ram_corrupt_ok(struct nffs_inode *inode,
+                                          struct nffs_hash_entry **out_next);
+int nffs_inode_unlink(struct nffs_inode *inode);
+int nffs_inode_is_dummy(struct nffs_inode_entry *inode_entry);
+int nffs_inode_is_deleted(struct nffs_inode_entry *inode_entry);
+int nffs_inode_setflags(struct nffs_inode_entry *entry, uint8_t flag);
+int nffs_inode_unsetflags(struct nffs_inode_entry *entry, uint8_t flag);
+int nffs_inode_getflags(struct nffs_inode_entry *entry, uint8_t flag);
+
+/* @misc */
+int nffs_misc_gc_if_oom(void *resource, int *out_rc);
+int nffs_misc_reserve_space(uint16_t space,
+                            uint8_t *out_area_idx, uint32_t *out_area_offset);
+int nffs_misc_set_num_areas(uint8_t num_areas);
+int nffs_misc_validate_root_dir(void);
+int nffs_misc_validate_scratch(void);
+int nffs_misc_create_lost_found_dir(void);
+int nffs_misc_set_max_block_data_len(uint16_t min_data_len);
+int nffs_misc_reset(void);
+int nffs_misc_ready(void);
+
+/* @path */
+int nffs_path_parse_next(struct nffs_path_parser *parser);
+void nffs_path_parser_new(struct nffs_path_parser *parser, const char *path);
+int nffs_path_find(struct nffs_path_parser *parser,
+                   struct nffs_inode_entry **out_inode_entry,
+                   struct nffs_inode_entry **out_parent);
+int nffs_path_find_inode_entry(const char *filename,
+                               struct nffs_inode_entry **out_inode_entry);
+int nffs_path_unlink(const char *filename);
+int nffs_path_rename(const char *from, const char *to);
+int nffs_path_new_dir(const char *path,
+                      struct nffs_inode_entry **out_inode_entry);
+
+/* @restore */
+int nffs_restore_full(const struct nffs_area_desc *area_descs);
+
+/* @write */
+int nffs_write_to_file(struct nffs_file *file, const void *data, int len);
+
+
+#define NFFS_HASH_FOREACH(entry, i, next)                               \
+    for ((i) = 0; (i) < NFFS_HASH_SIZE; (i)++)                          \
+        for ((entry) = SLIST_FIRST(nffs_hash + (i));                    \
+             (entry) && (((next)) = SLIST_NEXT((entry), nhe_next), 1);  \
+             (entry) = ((next)))
+
+#define NFFS_FLASH_LOC_NONE  nffs_flash_loc(NFFS_AREA_ID_NONE, 0)
+
+#if 0
+#ifdef ARCH_sim
+#include <stdio.h>
+#define NFFS_LOG(lvl, ...) \
+    printf(__VA_ARGS__)
+#else
+#define NFFS_LOG(lvl, ...) \
+    LOG_ ## lvl(&nffs_log, LOG_MODULE_NFFS, __VA_ARGS__)
+#endif
+#endif /* 0 */
+
+#define NFFS_LOG(lvl, ...) \
+    LOG_ ## lvl(&nffs_log, LOG_MODULE_NFFS, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/nffs_restore.c b/src/nffs_restore.c
new file mode 100644
index 0000000..0a3253c
--- /dev/null
+++ b/src/nffs_restore.c
@@ -0,0 +1,1420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "hal/hal_flash.h"
+#include "os/os_mempool.h"
+#include "os/os_malloc.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+/**
+ * The size of the largest data block encountered during detection.  This is
+ * used to ensure that the maximum block data size is not set lower than the
+ * size of an existing block.
+ */
+static uint16_t nffs_restore_largest_block_data_len;
+
+/**
+ * Checks that each block a chain of data blocks was properly restored.
+ *
+ * @param last_block_entry      The entry corresponding to the last block in
+ *                                  the chain.
+ *
+ * @return                      0 if the block chain is OK;
+ *                              FS_ECORRUPT if corruption is detected;
+ *                              nonzero on other error.
+ */
+static int
+nffs_restore_validate_block_chain(struct nffs_hash_entry *last_block_entry)
+{
+    struct nffs_disk_block disk_block;
+    struct nffs_hash_entry *cur;
+    struct nffs_block block;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    cur = last_block_entry;
+
+    while (cur != NULL) {
+        if (nffs_hash_entry_is_dummy(cur)) {
+            return FS_ENOENT;
+        }
+
+        nffs_flash_loc_expand(cur->nhe_flash_loc, &area_idx, &area_offset);
+
+        rc = nffs_block_read_disk(area_idx, area_offset, &disk_block);
+        if (rc != 0) {
+            return rc;
+        }
+
+        rc = nffs_block_from_hash_entry(&block, cur);
+        if (rc != 0) {
+            return rc;
+        }
+
+        cur = block.nb_prev;
+    }
+
+    return 0;
+}
+
+static void
+u32toa(char *dst, uint32_t val)
+{
+    uint8_t tmp;
+    int idx = 0;
+    int i;
+    int print = 0;
+
+    for (i = 0; i < 8; i++) {
+        tmp = val >> 28;
+        if (tmp || i == 7) {
+            print = 1;
+        }
+        if (tmp < 10) {
+            tmp += '0';
+        } else {
+            tmp += 'a' - 10;
+        }
+        if (print) {
+            dst[idx++] = tmp;
+        }
+        val <<= 4;
+    }
+    dst[idx++] = '\0';
+}
+
+/**
+ * If the specified inode entry is a dummy directory, this function moves
+ * all its children to the lost+found directory.
+ *
+ * @param inode_entry           The parent inode to test and empty.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_migrate_orphan_children(struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_inode_entry *lost_found_sub;
+    struct nffs_inode_entry *child_entry;
+    char buf[32];
+    int rc;
+
+    if (!nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) {
+        /* Not a directory. */
+        return 0;
+    }
+
+    if (!nffs_inode_is_dummy(inode_entry)) {
+        /* Not a dummy. */
+        return 0;
+    }
+
+    if (SLIST_EMPTY(&inode_entry->nie_child_list)) {
+        /* No children to migrate. */
+        return 0;
+    }
+
+    /* Create a directory in lost+found to hold the dummy directory's
+     * contents.
+     */
+    strcpy(buf, "/lost+found/");
+    u32toa(&buf[strlen(buf)], inode_entry->nie_hash_entry.nhe_id);
+
+    rc = nffs_path_new_dir(buf, &lost_found_sub);
+    if (rc != 0 && rc != FS_EEXIST) {
+        return rc;
+    }
+
+    /* Move each child into the new subdirectory. */
+    while ((child_entry = SLIST_FIRST(&inode_entry->nie_child_list)) != NULL) {
+        rc = nffs_inode_rename(child_entry, lost_found_sub, NULL);
+        if (rc != 0) {
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+static int
+nffs_restore_should_sweep_inode_entry(struct nffs_inode_entry *inode_entry,
+                                      int *out_should_sweep)
+{
+    struct nffs_inode inode;
+    int rc;
+
+
+    /*
+     * if this inode was tagged to have a dummy block entry and the
+     * flag is still set, that means we didn't find the block and so
+     * should remove this inode and all associated blocks.
+     */
+    if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK)) {
+        *out_should_sweep = 1;
+        /*nffs_inode_inc_refcnt(inode_entry);*/
+        inode_entry->nie_refcnt = 1;
+        assert(inode_entry->nie_refcnt >= 1);
+        return 0;
+    }
+
+    /*
+     * This inode was originally created to hold a block that was restored
+     * before the owning inode. If the flag is still set, it means we never
+     * restored the inode from disk and so this entry should be deleted.
+     */
+    if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK)) {
+        *out_should_sweep = 2;
+        inode_entry->nie_refcnt = 1;
+        assert(inode_entry->nie_refcnt >= 1);
+        return 0;
+    }
+
+    /*
+     * Determine if the inode is a dummy. Dummy inodes have a flash
+     * location set to LOC_NONE and should have a flag set for the reason.
+     * The presence of a dummy inode during the final sweep step indicates
+     * file system corruption.  It's assumed that directories have
+     * previously migrated all children to /lost+found.
+     */
+    if (nffs_inode_is_dummy(inode_entry)) {
+        *out_should_sweep = 3;
+        nffs_inode_inc_refcnt(inode_entry);
+        assert(inode_entry->nie_refcnt >= 1);
+        return 0;
+    }
+
+    /* Determine if the inode has been deleted.  If an inode has no parent (and
+     * it isn't the root directory), it has been deleted from the disk and
+     * should be swept from the RAM representation.
+     */
+    if (inode_entry->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) {
+        rc = nffs_inode_from_entry(&inode, inode_entry);
+        if (rc != 0 && rc != FS_ENOENT) {
+            *out_should_sweep = 0;
+            return rc;
+        }
+        if (inode.ni_parent == NULL) {
+            *out_should_sweep = 4;
+            return 0;
+        }
+    }
+
+    /*
+     * If this inode has been marked as deleted, we can unlink it here.
+     *
+     * XXX Note that the record of a deletion could be lost if garbage
+     * collection erases the delete but leaves inode updates on other
+     * partitions which can then be restored.
+     */
+    if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED)) {
+        rc = nffs_inode_from_entry(&inode, inode_entry);
+        if (rc != 0) {
+            *out_should_sweep = 0;
+            return rc;
+        }
+        *out_should_sweep = 5;
+    }
+
+    /* If this is a file inode, verify that all of its constituent blocks are
+     * present.
+     */
+    if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
+        rc = nffs_restore_validate_block_chain(
+                inode_entry->nie_last_block_entry);
+        if (rc == FS_ECORRUPT) {
+            *out_should_sweep = 6;
+            return 0;
+        } else if (rc == FS_ENOENT) {
+            *out_should_sweep = 7;
+            return 0;
+        } else if (rc != 0) {
+            *out_should_sweep = 0;
+            return rc;
+        }
+    }
+
+    /* This is a valid inode; don't sweep it. */
+    *out_should_sweep = 0;
+    return 0;
+}
+
+/**
+ * Performs a sweep of the RAM representation at the end of a successful
+ * restore.  The sweep phase performs the following actions of each inode in
+ * the file system:
+ *     1. If the inode is a dummy directory, its children are migrated to the
+ *        lost+found directory.
+ *     2. Else if the inode is a dummy file, it is fully deleted from RAM.
+ *     3. Else, a CRC check is performed on each of the inode's constituent
+ *        blocks.  If corruption is detected, the inode is fully deleted from
+ *        RAM.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_restore_sweep(void)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *next;
+    struct nffs_hash_list *list;
+    struct nffs_inode inode;
+    struct nffs_block block;
+    int del = 0;
+    int rc;
+    int i;
+
+    /* Iterate through every object in the hash table, deleting all inodes that
+     * should be removed.
+     */
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        list = nffs_hash + i;
+
+        entry = SLIST_FIRST(list);
+        while (entry != NULL) {
+            next = SLIST_NEXT(entry, nhe_next);
+            if (nffs_hash_id_is_inode(entry->nhe_id)) {
+                inode_entry = (struct nffs_inode_entry *)entry;
+
+                /*
+                 * If this is a dummy inode directory, the file system
+                 * is corrupt.  Move the directory's children inodes to
+                 * the lost+found directory.
+                 */
+                rc = nffs_restore_migrate_orphan_children(inode_entry);
+                if (rc != 0) {
+                    return rc;
+                }
+
+                /* Determine if this inode needs to be deleted. */
+                rc = nffs_restore_should_sweep_inode_entry(inode_entry, &del);
+                if (rc != 0) {
+                    return rc;
+                }
+
+                rc = nffs_inode_from_entry(&inode, inode_entry);
+                if (rc != 0 && rc != FS_ENOENT) {
+                    return rc;
+                }
+
+                if (del) {
+
+                    /* Remove the inode and all its children from RAM.  We
+                     * expect some file system corruption; the children are
+                     * subject to garbage collection and may not exist in the
+                     * hash.  Remove what is actually present and ignore
+                     * corruption errors.
+                     */
+                    rc = nffs_inode_unlink_from_ram_corrupt_ok(&inode, &next);
+                    if (rc != 0) {
+                        return rc;
+                    }
+                    next = SLIST_FIRST(list);
+                }
+            } else if (nffs_hash_id_is_block(entry->nhe_id)) {
+                if (nffs_hash_id_is_dummy(entry->nhe_id)) {
+                    del = 1;
+                    nffs_block_delete_from_ram(entry);
+                } else {
+                    rc = nffs_block_from_hash_entry(&block, entry);
+                    if (rc != 0 && rc != FS_ENOENT) {
+                        del = 1;
+                        nffs_block_delete_from_ram(entry);
+                    }
+                }
+                if (del) {
+                    del = 0;
+                    next = SLIST_FIRST(list);
+                }
+            }
+
+            entry = next;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Creates a dummy inode and inserts it into the hash table.  A dummy inode is
+ * a temporary placeholder for a real inode that has not been restored yet.
+ * These are necessary so that the inter-object links can be maintained until
+ * the absent inode is eventually restored.  Dummy inodes are identified by a
+ * inaccessible flash location (NFFS_FLASH_LOC_NONE). When the real inode
+ * is restored, this flash location will be udpated.
+ *
+ * @param id                    The ID of the dummy inode to create.
+ * @param out_inode_entry       On success, the dummy inode gets written here.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_dummy_inode(uint32_t id,
+                         struct nffs_inode_entry **out_inode_entry)
+{
+    struct nffs_inode_entry *inode_entry;
+
+    inode_entry = nffs_inode_entry_alloc();
+    if (inode_entry == NULL) {
+        return FS_ENOMEM;
+    }
+    inode_entry->nie_hash_entry.nhe_id = id;
+    inode_entry->nie_hash_entry.nhe_flash_loc = NFFS_FLASH_LOC_NONE;
+    inode_entry->nie_refcnt = 0;
+    inode_entry->nie_last_block_entry = NULL; /* lastblock not available yet */
+    nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
+
+    nffs_hash_insert(&inode_entry->nie_hash_entry);
+
+    *out_inode_entry = inode_entry;
+
+    return 0;
+}
+
+/**
+ * Determines if an already-restored inode should be replaced by another inode
+ * just read from flash.  This function should only be called if both inodes
+ * share the same ID.  The existing inode gets replaced if:
+ *     o It is a dummy inode.
+ *     o Its sequence number is less than that of the new inode.
+ *
+ * @param old_inode_entry       The already-restored inode to test.
+ * @param disk_inode            The inode just read from flash.
+ * @param out_should_replace    On success, 0=don't replace; 1=do replace.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_inode_gets_replaced(struct nffs_inode_entry *old_inode_entry,
+                                 const struct nffs_disk_inode *disk_inode,
+                                 int *out_should_replace)
+{
+    struct nffs_inode old_inode;
+    int rc;
+
+    assert(old_inode_entry->nie_hash_entry.nhe_id == disk_inode->ndi_id);
+
+
+    if (nffs_inode_is_dummy(old_inode_entry)) {
+        NFFS_LOG(DEBUG, "inode_gets_replaced dummy!\n");
+        *out_should_replace = 1;
+        return 0;
+    }
+
+    /*
+     * inode is known to be obsolete and needs to be replaced no matter what
+     */
+    if (nffs_inode_getflags(old_inode_entry, NFFS_INODE_FLAG_OBSOLETE)) {
+        NFFS_LOG(DEBUG, "inode_gets_replaced obsolete\n");
+        *out_should_replace = 2;
+        return 0;
+    }
+
+    rc = nffs_inode_from_entry(&old_inode, old_inode_entry);
+    if (rc != 0) {
+        *out_should_replace = 0;
+        return rc;
+    }
+
+    if (old_inode.ni_seq < disk_inode->ndi_seq) {
+        NFFS_LOG(DEBUG, "inode_gets_replaced seq\n");
+        *out_should_replace = 3;
+        return 0;
+    }
+
+    if (old_inode.ni_seq == disk_inode->ndi_seq) {
+        /* This is a duplicate of a previously-read inode.  This should never
+         * happen.
+         */
+        *out_should_replace = 0;
+        return FS_EEXIST;
+    }
+
+    *out_should_replace = 0;
+    return 0;
+}
+
+/**
+ * Determines if the specified inode should be added to the RAM representation
+ * and adds it if appropriate.
+ *
+ * @param disk_inode            The inode just read from flash.
+ * @param area_idx              The index of the area containing the inode.
+ * @param area_offset           The offset within the area of the inode.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_inode(const struct nffs_disk_inode *disk_inode, uint8_t area_idx,
+                   uint32_t area_offset)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_inode_entry *parent;
+    struct nffs_inode inode;
+    struct nffs_hash_entry *lastblock_entry = NULL;
+    int new_inode;
+    int do_add;
+    int rc;
+
+    new_inode = 0;
+
+    /* Check the inode's CRC.  If the inode is corrupt, discard it. */
+    rc = nffs_crc_disk_inode_validate(disk_inode, area_idx, area_offset);
+    if (rc != 0) {
+        goto err;
+    }
+
+    inode_entry = nffs_hash_find_inode(disk_inode->ndi_id);
+
+    /*
+     * Inode has already been restored. Determine whether this version
+     * from disk should replace the previous version referenced in RAM.
+     */
+    if (inode_entry != NULL) {
+
+        if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) {
+            /*
+             * Restore this inode even though deleted on disk
+             * so the additional restored blocks have a place to go
+             */
+            NFFS_LOG(DEBUG, "restoring deleted inode %x\n",
+                     (unsigned int)disk_inode->ndi_id);
+            nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
+        }
+
+        /*
+         * inodes get replaced if they're dummy entries (i.e. allocated
+         * as place holders until the actual inode is restored), or this is
+         * a more recent version of the inode which supercedes the old.
+         */
+        rc = nffs_restore_inode_gets_replaced(inode_entry, disk_inode, &do_add);
+        if (rc != 0) {
+            goto err;
+        }
+
+        if (do_add) { /* replace in this case */
+            if (!nffs_inode_is_dummy(inode_entry)) {
+                /*
+                 * if it's not a dummy, read block from flash
+                 */
+                rc = nffs_inode_from_entry(&inode, inode_entry);
+                if (rc != 0) {
+                    return rc;
+                }
+
+                /*
+                 * inode is known to be obsolete
+                 */
+                if (nffs_inode_getflags(inode_entry, 
+                                        NFFS_INODE_FLAG_OBSOLETE)) {
+                    nffs_inode_unsetflags(inode_entry,
+                                          NFFS_INODE_FLAG_OBSOLETE);
+                }
+
+                if (inode.ni_parent != NULL) {
+                    nffs_inode_remove_child(&inode);
+                }
+
+                /*
+                 * If this is a delete record, subsequent inode and restore
+                 * records from flash may be ignored.
+                 * If the parent is NULL, this inode has been deleted. (old)
+                 * XXX if we could count on delete records for every inode,
+                 * we wouldn't need to check for the root directory looking
+                 * like a delete record because of it's parent ID.
+                 */
+                if (inode_entry->nie_hash_entry.nhe_id != NFFS_ID_ROOT_DIR) {
+                    if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED ||
+                        disk_inode->ndi_parent_id == NFFS_ID_NONE) {
+
+                        nffs_inode_setflags(inode_entry,
+                                            NFFS_INODE_FLAG_DELETED);
+                    }
+                }
+
+            } else {
+                /*
+                 * The existing inode entry was added as dummy.
+                 * The restore operation clears that state.
+                 */
+
+                /* If it's a directory, it was added as a parent to
+                 * one of it's children who were restored first.
+                 */
+                if (nffs_inode_getflags(inode_entry, 
+                                         NFFS_INODE_FLAG_DUMMYPARENT)) {
+                    assert(nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id));
+                    nffs_inode_unsetflags(inode_entry, 
+                                         NFFS_INODE_FLAG_DUMMYPARENT);
+                }
+
+                /*
+                 * If it's a file, it was added to store a lastblock
+                 */
+                if (nffs_inode_getflags(inode_entry, 
+                                         NFFS_INODE_FLAG_DUMMYINOBLK)) {
+                    assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+                    nffs_inode_unsetflags(inode_entry, 
+                                         NFFS_INODE_FLAG_DUMMYINOBLK);
+                }
+
+                /*
+                 * Also, since it's a dummy, clear this flag too
+                 */
+                if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY)) {
+                    nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
+                }
+            }
+ 
+            /*
+             * Update location to reference new location in flash
+             */
+            inode_entry->nie_hash_entry.nhe_flash_loc =
+                                    nffs_flash_loc(area_idx, area_offset);
+        }
+        
+    } else {
+        inode_entry = nffs_inode_entry_alloc();
+        if (inode_entry == NULL) {
+            rc = FS_ENOMEM;
+            goto err;
+        }
+        new_inode = 1;
+        do_add = 1;
+
+        inode_entry->nie_hash_entry.nhe_id = disk_inode->ndi_id;
+        inode_entry->nie_hash_entry.nhe_flash_loc =
+                              nffs_flash_loc(area_idx, area_offset);
+        inode_entry->nie_last_block_entry = NULL; /* for now */
+
+        nffs_hash_insert(&inode_entry->nie_hash_entry);
+    }
+
+    /*
+     * inode object has been restored and the entry is in the hash
+     * Check whether the lastblock and parent have also been restored
+     * and link up or allocate dummy entries as appropriate.
+     */
+    if (do_add) {
+        inode_entry->nie_refcnt = 1;
+
+        if (disk_inode->ndi_flags & NFFS_INODE_FLAG_DELETED) {
+            /*
+             * Restore this inode even though deleted on disk
+             * so the additional restored blocks have a place to go
+             */
+            NFFS_LOG(DEBUG, "restoring deleted inode %x\n",
+                     (unsigned int)disk_inode->ndi_id);
+            nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
+        }
+
+        /*
+         * Inode has a lastblock on disk.
+         * Add reference to last block entry if in hash table
+         * otherwise add a dummy block entry for later update
+         */
+        if (disk_inode->ndi_lastblock_id != NFFS_ID_NONE &&
+                nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
+            lastblock_entry =
+              nffs_hash_find_block(disk_inode->ndi_lastblock_id);
+
+            /*
+             * Lastblock has already been restored.
+             */
+            if (lastblock_entry != NULL) {
+                if (lastblock_entry->nhe_id == disk_inode->ndi_lastblock_id) {
+                    inode_entry->nie_last_block_entry = lastblock_entry;
+                }
+
+            } else {
+                /*
+                 * Insert a temporary reference to a 'dummy' block entry
+                 * When block is restored, it will update this dummy and
+                 * the entry of this inode is updated to flash location
+                 */
+                rc = nffs_block_entry_reserve(&lastblock_entry);
+                if (lastblock_entry == NULL) {
+                    rc = FS_ENOMEM;
+                    goto err;
+                }
+
+                lastblock_entry->nhe_id = disk_inode->ndi_lastblock_id;
+                lastblock_entry->nhe_flash_loc = NFFS_FLASH_LOC_NONE;
+                inode_entry->nie_last_block_entry = lastblock_entry;
+                nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK);
+                nffs_hash_insert(lastblock_entry);
+
+                if (lastblock_entry->nhe_id >= nffs_hash_next_block_id) {
+                    nffs_hash_next_block_id = lastblock_entry->nhe_id + 1;
+                }
+            }
+        }
+
+        if (disk_inode->ndi_parent_id != NFFS_ID_NONE) {
+            
+            parent = nffs_hash_find_inode(disk_inode->ndi_parent_id);
+            /*
+             * The parent directory for this inode hasn't been restored yet.
+             * Add a dummy directory so it can be added as a child.
+             * When the parent inode is restored, it's hash entry will be
+             * updated with the flash location.
+             */
+            if (parent == NULL) {
+                rc = nffs_restore_dummy_inode(disk_inode->ndi_parent_id,
+                                             &parent);
+                /*
+                 * Set the dummy parent flag in the new parent.
+                 * It's turned off above when restored.
+                 */
+                nffs_inode_setflags(parent, NFFS_INODE_FLAG_DUMMYPARENT);
+                if (rc != 0) {
+                    goto err;
+                }
+            }
+
+            rc = nffs_inode_add_child(parent, inode_entry);
+            if (rc != 0) {
+                goto err;
+            }
+        } 
+
+        if (inode_entry->nie_hash_entry.nhe_id == NFFS_ID_ROOT_DIR) {
+            nffs_root_dir = inode_entry;
+            nffs_inode_setflags(nffs_root_dir, NFFS_INODE_FLAG_INTREE);
+        }
+    }
+
+    if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
+        NFFS_LOG(DEBUG, "restoring file; id=0x%08x\n",
+                 (unsigned int)inode_entry->nie_hash_entry.nhe_id);
+        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_file_id) {
+            nffs_hash_next_file_id = inode_entry->nie_hash_entry.nhe_id + 1;
+        }
+    } else {
+        NFFS_LOG(DEBUG, "restoring dir; id=0x%08x\n",
+                 (unsigned int)inode_entry->nie_hash_entry.nhe_id);
+        if (inode_entry->nie_hash_entry.nhe_id >= nffs_hash_next_dir_id) {
+            nffs_hash_next_dir_id = inode_entry->nie_hash_entry.nhe_id + 1;
+        }
+    }
+
+    return 0;
+
+err:
+    if (new_inode) {
+        assert(nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH));
+        nffs_hash_remove(&inode_entry->nie_hash_entry);
+        nffs_inode_entry_free(inode_entry);
+    }
+    return rc;
+}
+
+/**
+ * Indicates whether the specified data block is superseded by the just-read
+ * disk data block.  A data block supersedes another if its ID is equal and its
+ * sequence number is greater than that of the other block.
+ *
+ * @param out_should_replace    On success, 0 or 1 gets written here, to
+ *                                  indicate whether replacement should occur.
+ * @param old_block             The data block which has already been read and
+ *                                  converted to its RAM representation.  This
+ *                                  is the block that may be superseded.
+ * @param disk_block            The disk data block that was just read from
+ *                                  flash.  This is the block which may
+ *                                  supersede the other.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_block_gets_replaced(const struct nffs_block *old_block,
+                                 const struct nffs_disk_block *disk_block,
+                                 int *out_should_replace)
+{
+    assert(old_block->nb_hash_entry->nhe_id == disk_block->ndb_id);
+
+    if (nffs_block_is_dummy(old_block->nb_hash_entry)) {
+        assert(0);
+        *out_should_replace = 1;
+        return 0;
+    }
+
+    if (old_block->nb_seq < disk_block->ndb_seq) {
+        *out_should_replace = 2;
+        return 0;
+    }
+
+    if (old_block->nb_seq == disk_block->ndb_seq) {
+        /* This is a duplicate of an previously-read block.  This should never
+         * happen.
+         */
+        return FS_EEXIST;
+    }
+
+    *out_should_replace = 0;
+    return 0;
+}
+
+/**
+ * Populates the nffs RAM state with the memory representation of the specified
+ * disk data block.
+ *
+ * @param disk_block            The source disk block to insert.
+ * @param area_idx              The ID of the area containing the block.
+ * @param area_offset           The area_offset within the area of the block.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_block(const struct nffs_disk_block *disk_block, uint8_t area_idx,
+                   uint32_t area_offset)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_block block;
+    int do_replace;
+    int new_block;
+    int rc;
+
+    new_block = 0;
+
+    /* Check the block's CRC.  If the block is corrupt, discard it.  If this
+     * block would have superseded another, the old block becomes current.
+     */
+    rc = nffs_crc_disk_block_validate(disk_block, area_idx, area_offset);
+    if (rc != 0) {
+        goto err;
+    }
+
+    entry = nffs_hash_find_block(disk_block->ndb_id);
+    if (entry != NULL) {
+
+        rc = nffs_block_from_hash_entry_no_ptrs(&block, entry);
+        if (rc != 0 && rc != FS_ENOENT) {
+            goto err;
+        }
+
+        /*
+         * If the old block reference is for a 'dummy' block, it was added
+         * because the owning inode's lastblock was not yet restored.
+         * Update the block hash entry and inode to reference the entry.
+         */
+        if (nffs_block_is_dummy(entry)) {
+
+            assert(entry->nhe_id == disk_block->ndb_id);
+
+            /*
+             * Entry is no longer dummy as it references the correct location
+             */
+            entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);
+
+            inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
+
+            /*
+             * Turn off flags in previously restored inode recording the
+             * allocation of a dummy block
+             */
+            if (inode_entry) {
+                nffs_inode_unsetflags(inode_entry, NFFS_INODE_FLAG_DUMMYLSTBLK);
+            }
+        }
+
+        rc = nffs_restore_block_gets_replaced(&block, disk_block,
+                                              &do_replace);
+        if (rc != 0) {
+            goto err;
+        }
+
+        if (!do_replace) {
+            /* The new block is superseded by the old; nothing to do. */
+            return 0;
+        }
+
+        /*
+         * update the existing hash entry to reference the new flash location
+         */
+        entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);
+
+    } else {
+        entry = nffs_block_entry_alloc();
+        if (entry == NULL) {
+            rc = FS_ENOMEM;
+            goto err;
+        }
+        new_block = 1;
+        entry->nhe_id = disk_block->ndb_id;
+        entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);
+
+        /* The block is ready to be inserted into the hash. */
+
+        nffs_hash_insert(entry);
+
+        if (disk_block->ndb_id >= nffs_hash_next_block_id) {
+            nffs_hash_next_block_id = disk_block->ndb_id + 1;
+        }
+    }
+
+    /* Make sure the maximum block data size is not set lower than the size of
+     * an existing block.
+     */
+    if (disk_block->ndb_data_len > nffs_restore_largest_block_data_len) {
+        nffs_restore_largest_block_data_len = disk_block->ndb_data_len;
+    }
+
+    NFFS_LOG(DEBUG, "restoring block; id=0x%08x seq=%u inode_id=%u prev_id=%u "
+             "data_len=%u\n",
+             (unsigned int)disk_block->ndb_id,
+             (unsigned int)disk_block->ndb_seq,
+             (unsigned int)disk_block->ndb_inode_id,
+             (unsigned int)disk_block->ndb_prev_id,
+             disk_block->ndb_data_len);
+
+    inode_entry = nffs_hash_find_inode(disk_block->ndb_inode_id);
+
+    if (inode_entry == NULL) {
+        /*
+         * Owning inode not yet restored.
+         * Allocate a dummy inode which temporarily owns this block.
+         * It is not yet linked to a parent.
+         */
+        rc = nffs_restore_dummy_inode(disk_block->ndb_inode_id, &inode_entry);
+        if (rc != 0) {
+            goto err;
+        }
+        /*
+         * Record that this inode was created because a block was restored
+         * before the inode
+         */
+        nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMYINOBLK);
+        inode_entry->nie_last_block_entry = entry;
+    } else {
+        if (nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED)) {
+            /*
+             * don't restore blocks for deleted inodes
+             */
+            rc = FS_ENOENT;
+            goto err;
+        }
+    }
+
+    return 0;
+
+err:
+    if (new_block) {
+        nffs_hash_remove(entry);
+        nffs_block_entry_free(entry);
+    }
+    return rc;
+}
+
+/**
+ * Populates the nffs RAM state with the memory representation of the specified
+ * disk object.
+ *
+ * @param disk_object           The source disk object to convert.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_object(const struct nffs_disk_object *disk_object)
+{
+    int rc;
+
+    switch (disk_object->ndo_type) {
+    case NFFS_OBJECT_TYPE_INODE:
+        rc = nffs_restore_inode(&disk_object->ndo_disk_inode,
+                                disk_object->ndo_area_idx,
+                                disk_object->ndo_offset);
+        break;
+
+    case NFFS_OBJECT_TYPE_BLOCK:
+        rc = nffs_restore_block(&disk_object->ndo_disk_block,
+                                disk_object->ndo_area_idx,
+                                disk_object->ndo_offset);
+        break;
+
+    default:
+        rc = FS_EINVAL;
+        break;
+    }
+
+    return rc;
+}
+
+/**
+ * Reads a single disk object from flash.
+ *
+ * @param area_idx              The area to read the object from.
+ * @param area_offset           The offset within the area to read from.
+ * @param out_disk_object       On success, the restored object gets written
+ *                                  here.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_disk_object(int area_idx, uint32_t area_offset,
+                         struct nffs_disk_object *out_disk_object)
+{
+    int rc;
+
+    memset(out_disk_object, 0, sizeof *out_disk_object);
+
+    rc = nffs_flash_read(area_idx, area_offset,
+                         &out_disk_object->ndo_un_obj,
+                         sizeof(out_disk_object->ndo_un_obj));
+    if (rc != 0) {
+        return rc;
+    }
+    STATS_INC(nffs_stats, nffs_readcnt_object);
+
+    if (nffs_hash_id_is_inode(out_disk_object->ndo_disk_inode.ndi_id)) {
+        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_INODE;
+
+    } else if (nffs_hash_id_is_block(out_disk_object->ndo_disk_block.ndb_id)) {
+        out_disk_object->ndo_type = NFFS_OBJECT_TYPE_BLOCK;
+
+    } else if (out_disk_object->ndo_disk_block.ndb_id == NFFS_ID_NONE) {
+        return FS_EEMPTY;
+
+    } else {
+        return FS_ECORRUPT;
+    }
+
+    out_disk_object->ndo_area_idx = area_idx;
+    out_disk_object->ndo_offset = area_offset;
+
+    return 0;
+}
+
+/**
+ * Calculates the disk space occupied by the specified disk object.
+ *
+ * @param disk_object
+ */
+static int
+nffs_restore_disk_object_size(const struct nffs_disk_object *disk_object)
+{
+    switch (disk_object->ndo_type) {
+    case NFFS_OBJECT_TYPE_INODE:
+        return sizeof disk_object->ndo_disk_inode +
+                      disk_object->ndo_disk_inode.ndi_filename_len;
+
+    case NFFS_OBJECT_TYPE_BLOCK:
+        return sizeof disk_object->ndo_disk_block +
+                      disk_object->ndo_disk_block.ndb_data_len;
+
+    default:
+        assert(0);
+        return 1;
+    }
+}
+
+/**
+ * Reads the specified area from disk and loads its contents into the RAM
+ * representation.
+ *
+ * @param area_idx              The index of the area to read.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_area_contents(int area_idx)
+{
+    struct nffs_disk_object disk_object;
+    struct nffs_area *area;
+    int rc;
+
+    area = nffs_areas + area_idx;
+
+    area->na_cur = sizeof (struct nffs_disk_area);
+    while (1) {
+        rc = nffs_restore_disk_object(area_idx, area->na_cur,  &disk_object);
+        switch (rc) {
+        case 0:
+
+            /* Valid object; restore it into the RAM representation. */
+            rc = nffs_restore_object(&disk_object);
+
+            /*
+             * If the restore fails the CRC check, the object length field
+             * can't be trusted so just start looking for the next valid
+             * object in the flash area.
+             * XXX Deal with file system corruption
+             */
+            if (rc == FS_ECORRUPT) {
+                area->na_cur++;
+            } else {
+                STATS_INC(nffs_stats, nffs_object_count); /* restored objects */
+                area->na_cur += nffs_restore_disk_object_size(&disk_object);
+            }
+            break;
+
+        case FS_ECORRUPT:
+            /*
+             * Invalid object; keep scanning for a valid object ID and CRC
+             * Can nffs_restore_disk_object return FS_ECORRUPT? XXX
+             */
+            area->na_cur++;
+            break;
+
+        case FS_EEMPTY:
+        case FS_EOFFSET:
+            /* End of disk encountered; area fully restored. */
+            return 0;
+
+        default:
+            return rc;
+        }
+    }
+}
+
+/**
+ * Reads and parses one area header.  This function does not read the area's
+ * contents.
+ *
+ * @param out_is_scratch        On success, 0 or 1 gets written here,
+ *                                  indicating whether the area is a scratch
+ *                                  area.
+ * @param area_offset           The flash offset of the start of the area.
+ *
+ * @return                      0 on success;
+ *                              nonzero on failure.
+ */
+static int
+nffs_restore_detect_one_area(uint8_t flash_id, uint32_t area_offset,
+                             struct nffs_disk_area *out_disk_area)
+{
+    int rc;
+
+    STATS_INC(nffs_stats, nffs_readcnt_detect);
+    rc = hal_flash_read(flash_id, area_offset, out_disk_area,
+                        sizeof *out_disk_area);
+    if (rc != 0) {
+        return FS_EHW;
+    }
+
+    if (!nffs_area_magic_is_set(out_disk_area)) {
+        return FS_ECORRUPT;
+    }
+
+    if (!nffs_area_is_current_version(out_disk_area)) {
+        return FS_EUNEXP;
+    }
+
+    return 0;
+}
+
+/**
+ * Repairs the effects of a corrupt scratch area.  Scratch area corruption can
+ * occur when the system resets while a garbage collection cycle is in
+ * progress.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_restore_corrupt_scratch(void)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *next;
+    uint32_t area_offset;
+    uint16_t good_idx;
+    uint16_t bad_idx;
+    uint8_t area_idx;
+    int rc;
+    int i;
+
+    /* Search for a pair of areas with identical IDs.  If found, these areas
+     * represent the source and destination areas of a garbage collection
+     * cycle.  The shorter of the two areas was the destination area.  Since
+     * the garbage collection cycle did not finish, the source area contains a
+     * more complete set of objects than the destination area.
+     *
+     * good_idx = index of source area.
+     * bad_idx  = index of destination area; this will be turned into the
+     *            scratch area.
+     */
+    rc = nffs_area_find_corrupt_scratch(&good_idx, &bad_idx);
+    if (rc != 0) {
+        return rc;
+    }
+
+    /* Invalidate all objects resident in the bad area. */
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        entry = SLIST_FIRST(&nffs_hash[i]);
+        while (entry != NULL) {
+            next = SLIST_NEXT(entry, nhe_next);
+
+            nffs_flash_loc_expand(entry->nhe_flash_loc,
+                                 &area_idx, &area_offset);
+            if (area_idx == bad_idx) {
+                if (nffs_hash_id_is_block(entry->nhe_id)) {
+                    rc = nffs_block_delete_from_ram(entry);
+                    if (rc != 0) {
+                        return rc;
+                    }
+                } else {
+                    inode_entry = (struct nffs_inode_entry *)entry;
+                    nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_OBSOLETE);
+                }
+            }
+
+            entry = next;
+        }
+    }
+
+    /* Now that the objects in the scratch area have been invalidated, reload
+     * everything from the good area.
+     */
+    rc = nffs_restore_area_contents(good_idx);
+    if (rc != 0) {
+        return rc;
+    }
+
+    /* Convert the bad area into a scratch area. */
+    rc = nffs_format_area(bad_idx, 1);
+    if (rc != 0) {
+        return rc;
+    }
+    nffs_scratch_area_idx = bad_idx;
+
+    return 0;
+}
+
+static void
+nffs_log_contents(void)
+{
+#if MYNEWT_VAL(LOG_LEVEL) > LOG_LEVEL_DEBUG
+    return;
+#endif
+
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *next;
+    struct nffs_block block;
+    struct nffs_inode inode;
+    int rc;
+    int i;
+
+    NFFS_HASH_FOREACH(entry, i, next) {
+        if (nffs_hash_id_is_block(entry->nhe_id)) {
+            rc = nffs_block_from_hash_entry(&block, entry);
+            assert(rc == 0 || rc == FS_ENOENT);
+            NFFS_LOG(DEBUG, "block; id=%u inode_id=",
+                     (unsigned int)entry->nhe_id);
+            if (block.nb_inode_entry == NULL) {
+                NFFS_LOG(DEBUG, "null ");
+            } else {
+                NFFS_LOG(DEBUG, "%u ",
+                     (unsigned int)block.nb_inode_entry->nie_hash_entry.nhe_id);
+            }
+
+            NFFS_LOG(DEBUG, "prev_id=");
+            if (block.nb_prev == NULL) {
+                NFFS_LOG(DEBUG, "null ");
+            } else {
+                NFFS_LOG(DEBUG, "%u ", (unsigned int)block.nb_prev->nhe_id);
+            }
+
+            NFFS_LOG(DEBUG, "data_len=%u\n", block.nb_data_len);
+        } else {
+            inode_entry = (void *)entry;
+            rc = nffs_inode_from_entry(&inode, inode_entry);
+            if (rc == FS_ENOENT) {
+                NFFS_LOG(DEBUG, "DUMMY file; id=%x ref=%d block_id=",
+                         (unsigned int)entry->nhe_id, inode_entry->nie_refcnt);
+                if (inode_entry->nie_last_block_entry == NULL) {
+                    NFFS_LOG(DEBUG, "null");
+                } else {
+                    NFFS_LOG(DEBUG, "%x",
+                     (unsigned int)inode_entry->nie_last_block_entry->nhe_id);
+                }
+            } else if (rc != 0) {
+                continue;
+            }
+            /*assert(rc == 0);*/
+
+            if (nffs_hash_id_is_file(entry->nhe_id)) {
+                NFFS_LOG(DEBUG, "file; id=%u name=%.3s block_id=",
+                         (unsigned int)entry->nhe_id, inode.ni_filename);
+                if (inode_entry->nie_last_block_entry == NULL) {
+                    NFFS_LOG(DEBUG, "null");
+                } else {
+                    NFFS_LOG(DEBUG, "%u",
+                     (unsigned int)inode_entry->nie_last_block_entry->nhe_id);
+                }
+                NFFS_LOG(DEBUG, "\n");
+            } else {
+                inode_entry = (void *)entry;
+                NFFS_LOG(DEBUG, "dir; id=%u name=%.3s\n",
+                         (unsigned int)entry->nhe_id, inode.ni_filename);
+
+            }
+        }
+    }
+}
+
+/**
+ * Searches for a valid nffs file system among the specified areas.  This
+ * function succeeds if a file system is detected among any subset of the
+ * supplied areas.  If the area set does not contain a valid file system,
+ * a new one can be created via a call to nffs_format().
+ *
+ * @param area_descs        The area set to search.  This array must be
+ *                              terminated with a 0-length area.
+ *
+ * @return                  0 on success;
+ *                          FS_ECORRUPT if no valid file system was detected;
+ *                          other nonzero on error.
+ */
+int
+nffs_restore_full(const struct nffs_area_desc *area_descs)
+{
+    struct nffs_disk_area disk_area;
+    int cur_area_idx;
+    int use_area;
+    int rc;
+    int i;
+
+    /* Start from a clean state. */
+    rc = nffs_misc_reset();
+    if (rc) {
+        return rc;
+    }
+    nffs_restore_largest_block_data_len = 0;
+    nffs_current_area_descs = (struct nffs_area_desc*) area_descs;
+
+    /* Read each area from flash. */
+    for (i = 0; area_descs[i].nad_length != 0; i++) {
+        if (i > NFFS_MAX_AREAS) {
+            rc = FS_EINVAL;
+            goto err;
+        }
+
+        rc = nffs_restore_detect_one_area(area_descs[i].nad_flash_id,
+                                          area_descs[i].nad_offset,
+                                          &disk_area);
+        switch (rc) {
+        case 0:
+            use_area = 1;
+            break;
+
+        case FS_EUNEXP:    /* not formatted with current on-disk NFFS format */
+        case FS_ECORRUPT:
+            use_area = 0;
+            break;
+
+        default:
+            goto err;
+        }
+
+        if (use_area) {
+            if (disk_area.nda_id == NFFS_AREA_ID_NONE &&
+                nffs_scratch_area_idx != NFFS_AREA_ID_NONE) {
+
+                /* Don't allow more than one scratch area. */
+                use_area = 0;
+            }
+        }
+
+        if (use_area) {
+            /* Populate RAM with a representation of this area. */
+            cur_area_idx = nffs_num_areas;
+
+            rc = nffs_misc_set_num_areas(nffs_num_areas + 1);
+            if (rc != 0) {
+                goto err;
+            }
+
+            nffs_areas[cur_area_idx].na_offset = area_descs[i].nad_offset;
+            nffs_areas[cur_area_idx].na_length = area_descs[i].nad_length;
+            nffs_areas[cur_area_idx].na_flash_id = area_descs[i].nad_flash_id;
+            nffs_areas[cur_area_idx].na_gc_seq = disk_area.nda_gc_seq;
+            nffs_areas[cur_area_idx].na_id = disk_area.nda_id;
+
+            if (disk_area.nda_id == NFFS_AREA_ID_NONE) {
+                nffs_areas[cur_area_idx].na_cur = NFFS_AREA_OFFSET_ID;
+                nffs_scratch_area_idx = cur_area_idx;
+            } else {
+                nffs_areas[cur_area_idx].na_cur =
+                    sizeof (struct nffs_disk_area);
+                nffs_restore_area_contents(cur_area_idx);
+            }
+        }
+    }
+
+    /* All areas have been restored from flash. */
+
+    if (nffs_scratch_area_idx == NFFS_AREA_ID_NONE) {
+        /* No scratch area.  The system may have been rebooted in the middle of
+         * a garbage collection cycle.  Look for a candidate scratch area.
+         */
+        rc = nffs_restore_corrupt_scratch();
+        if (rc != 0) {
+            if (rc == FS_ENOENT) {
+                rc = FS_ECORRUPT;
+            }
+            goto err;
+        }
+    }
+
+    /* Ensure this file system contains a valid scratch area. */
+    rc = nffs_misc_validate_scratch();
+    if (rc != 0) {
+        goto err;
+    }
+
+    /* Make sure the file system contains a valid root directory. */
+    rc = nffs_misc_validate_root_dir();
+    if (rc != 0) {
+        goto err;
+    }
+
+    /* Ensure there is a "/lost+found" directory. */
+    rc = nffs_misc_create_lost_found_dir();
+    if (rc != 0) {
+        goto err;
+    }
+
+    /* Delete from RAM any objects that were invalidated when subsequent areas
+     * were restored.
+     */
+    nffs_restore_sweep();
+
+    /* Set the maximum data block size according to the size of the smallest
+     * area.
+     */
+    rc = nffs_misc_set_max_block_data_len(nffs_restore_largest_block_data_len);
+    if (rc != 0) {
+        goto err;
+    }
+
+    NFFS_LOG(DEBUG, "CONTENTS\n");
+    nffs_log_contents();
+
+    return 0;
+
+err:
+    nffs_misc_reset();
+    return rc;
+}
diff --git a/src/nffs_write.c b/src/nffs_write.c
new file mode 100644
index 0000000..2ce2248
--- /dev/null
+++ b/src/nffs_write.c
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "testutil/testutil.h"
+#include "nffs/nffs.h"
+#include "nffs_priv.h"
+
+static int
+nffs_write_fill_crc16_overwrite(struct nffs_disk_block *disk_block,
+                                uint8_t src_area_idx, uint32_t src_area_offset,
+                                uint16_t left_copy_len, uint16_t right_copy_len,
+                                const void *new_data, uint16_t new_data_len)
+{
+    uint16_t block_off;
+    uint16_t crc16;
+    int rc;
+
+    block_off = 0;
+
+    crc16 = nffs_crc_disk_block_hdr(disk_block);
+    block_off += sizeof *disk_block;
+
+    /* Copy data from the start of the old block, in case the new data starts
+     * at a non-zero offset.
+     */
+    if (left_copy_len > 0) {
+        rc = nffs_crc_flash(crc16, src_area_idx, src_area_offset + block_off,
+                            left_copy_len, &crc16);
+        if (rc != 0) {
+            return rc;
+        }
+        block_off += left_copy_len;
+    }
+
+    /* Write the new data into the data block.  This may extend the block's
+     * length beyond its old value.
+     */
+    crc16 = crc16_ccitt(crc16, new_data, new_data_len);
+    block_off += new_data_len;
+
+    /* Copy data from the end of the old block, in case the new data doesn't
+     * extend to the end of the block.
+     */
+    if (right_copy_len > 0) {
+        rc = nffs_crc_flash(crc16, src_area_idx, src_area_offset + block_off,
+                            right_copy_len, &crc16);
+        if (rc != 0) {
+            return rc;
+        }
+        block_off += right_copy_len;
+    }
+
+    assert(block_off == sizeof *disk_block + disk_block->ndb_data_len);
+
+    disk_block->ndb_crc16 = crc16;
+
+    return 0;
+}
+
+/**
+ * Overwrites an existing data block.  The resulting block has the same ID as
+ * the old one, but it supersedes it with a greater sequence number.
+ *
+ * @param entry                 The data block to overwrite.
+ * @param left_copy_len         The number of bytes of existing data to retain
+ *                                  before the new data begins.
+ * @param new_data              The new data to write to the block.
+ * @param new_data_len          The number of new bytes to write to the block.
+ *                                  If this value plus left_copy_len is less
+ *                                  than the existing block's data length,
+ *                                  previous data at the end of the block is
+ *                                  retained.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_write_over_block(struct nffs_hash_entry *entry, uint16_t left_copy_len,
+                      const void *new_data, uint16_t new_data_len)
+{
+    struct nffs_disk_block disk_block;
+    struct nffs_block block;
+    uint32_t src_area_offset;
+    uint32_t dst_area_offset;
+    uint16_t right_copy_len;
+    uint16_t block_off;
+    uint8_t src_area_idx;
+    uint8_t dst_area_idx;
+    int rc;
+
+    rc = nffs_block_from_hash_entry(&block, entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    assert(left_copy_len <= block.nb_data_len);
+
+    /* Determine how much old data at the end of the block needs to be
+     * retained.  If the new data doesn't extend to the end of the block, the
+     * the rest of the block retains its old contents.
+     */
+    if (left_copy_len + new_data_len > block.nb_data_len) {
+        right_copy_len = 0;
+    } else {
+        right_copy_len = block.nb_data_len - left_copy_len - new_data_len;
+    }
+
+    block.nb_seq++;
+    block.nb_data_len = left_copy_len + new_data_len + right_copy_len;
+    nffs_block_to_disk(&block, &disk_block);
+
+    nffs_flash_loc_expand(entry->nhe_flash_loc,
+                          &src_area_idx, &src_area_offset);
+
+    rc = nffs_write_fill_crc16_overwrite(&disk_block,
+                                         src_area_idx, src_area_offset,
+                                         left_copy_len, right_copy_len,
+                                         new_data, new_data_len);
+    if (rc != 0) {
+        return rc;
+    }
+
+    rc = nffs_misc_reserve_space(sizeof disk_block + disk_block.ndb_data_len,
+                                 &dst_area_idx, &dst_area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    block_off = 0;
+
+    /* Write the block header. */
+    rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off,
+                          &disk_block, sizeof disk_block);
+    if (rc != 0) {
+        return rc;
+    }
+    block_off += sizeof disk_block;
+
+    /* Copy data from the start of the old block, in case the new data starts
+     * at a non-zero offset.
+     */
+    if (left_copy_len > 0) {
+        rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off,
+                             dst_area_idx, dst_area_offset + block_off,
+                             left_copy_len);
+        if (rc != 0) {
+            return rc;
+        }
+        block_off += left_copy_len;
+    }
+
+    /* Write the new data into the data block.  This may extend the block's
+     * length beyond its old value.
+     */
+    rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off,
+                          new_data, new_data_len);
+    if (rc != 0) {
+        return rc;
+    }
+    block_off += new_data_len;
+
+    /* Copy data from the end of the old block, in case the new data doesn't
+     * extend to the end of the block.
+     */
+    if (right_copy_len > 0) {
+        rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off,
+                             dst_area_idx, dst_area_offset + block_off,
+                             right_copy_len);
+        if (rc != 0) {
+            return rc;
+        }
+        block_off += right_copy_len;
+    }
+
+    assert(block_off == sizeof disk_block + block.nb_data_len);
+
+    entry->nhe_flash_loc = nffs_flash_loc(dst_area_idx, dst_area_offset);
+
+    ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, dst_area_idx,
+                                                dst_area_offset) == 0);
+
+    return 0;
+}
+
+/**
+ * Appends a new block to an inode block chain.
+ *
+ * @param inode_entry           The inode to append a block to.
+ * @param data                  The contents of the new block.
+ * @param len                   The number of bytes of data to write.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_write_append(struct nffs_cache_inode *cache_inode, const void *data,
+                  uint16_t len)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_disk_block disk_block;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    rc = nffs_block_entry_reserve(&entry);
+    if (entry == NULL) {
+        return FS_ENOMEM;
+    }
+
+    inode_entry = cache_inode->nci_inode.ni_inode_entry;
+
+    memset(&disk_block, 0, sizeof disk_block);
+    disk_block.ndb_id = nffs_hash_next_block_id++;
+    disk_block.ndb_seq = 0;
+    disk_block.ndb_inode_id = inode_entry->nie_hash_entry.nhe_id;
+    if (inode_entry->nie_last_block_entry == NULL) {
+        disk_block.ndb_prev_id = NFFS_ID_NONE;
+    } else {
+        disk_block.ndb_prev_id = inode_entry->nie_last_block_entry->nhe_id;
+    }
+    disk_block.ndb_data_len = len;
+    nffs_crc_disk_block_fill(&disk_block, data);
+
+    rc = nffs_block_write_disk(&disk_block, data, &area_idx, &area_offset);
+    if (rc != 0) {
+        return rc;
+    }
+
+    entry->nhe_id = disk_block.ndb_id;
+    entry->nhe_flash_loc = nffs_flash_loc(area_idx, area_offset);
+    nffs_hash_insert(entry);
+
+    inode_entry->nie_last_block_entry = entry;
+
+    /*
+     * When a new block is appended to a file, the inode must be written
+     * out to flash so the last block id is is stored persistently.
+     * Need to be written atomically with writing the block out so filesystem
+     * remains consistent. nffs_lock is held in nffs_write().
+     * The inode will not be updated if it's been unlinked on disk and this
+     * is signaled by setting the hash entry's flash location to NONE
+     */
+    if (inode_entry->nie_hash_entry.nhe_flash_loc != NFFS_FLASH_LOC_NONE) {
+        rc = nffs_inode_update(inode_entry);
+    }
+
+    /* Update cached inode with the new file size. */
+    cache_inode->nci_file_size += len;
+
+    /* Add appended block to the cache. */
+    nffs_cache_seek(cache_inode, cache_inode->nci_file_size - 1, NULL);
+
+    return 0;
+}
+
+/**
+ * Performs a single write operation.  The data written must be no greater
+ * than the maximum block data length.  If old data gets overwritten, then
+ * the existing data blocks are superseded as necessary.
+ *
+ * @param write_info            Describes the write operation being perfomred.
+ * @param inode_entry           The file inode to write to.
+ * @param data                  The new data to write.
+ * @param data_len              The number of bytes of new data to write.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+static int
+nffs_write_chunk(struct nffs_inode_entry *inode_entry, uint32_t file_offset,
+                 const void *data, uint16_t data_len)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_cache_block *cache_block;
+    unsigned int gc_count;
+    uint32_t append_len;
+    uint32_t data_offset;
+    uint32_t block_end;
+    uint32_t dst_off;
+    uint16_t chunk_off;
+    uint16_t chunk_sz;
+    int rc;
+
+    assert(data_len <= nffs_block_max_data_sz);
+
+    rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    /** Handle the simple append case first. */
+    if (file_offset == cache_inode->nci_file_size) {
+        rc = nffs_write_append(cache_inode, data, data_len);
+        return rc;
+    }
+
+    /** This is not an append; i.e., old data is getting overwritten. */
+
+    dst_off = file_offset + data_len;
+    data_offset = data_len;
+    cache_block = NULL;
+
+    if (dst_off > cache_inode->nci_file_size) {
+        append_len = dst_off - cache_inode->nci_file_size;
+    } else {
+        append_len = 0;
+    }
+
+    do {
+        if (cache_block == NULL) {
+            rc = nffs_cache_seek(cache_inode, dst_off - 1, &cache_block);
+            if (rc != 0) {
+                return rc;
+            }
+        }
+
+        if (cache_block->ncb_file_offset < file_offset) {
+            chunk_off = file_offset - cache_block->ncb_file_offset;
+        } else {
+            chunk_off = 0;
+        }
+
+        chunk_sz = cache_block->ncb_block.nb_data_len - chunk_off;
+        block_end = cache_block->ncb_file_offset +
+                    cache_block->ncb_block.nb_data_len;
+        if (block_end != dst_off) {
+            chunk_sz += (int)(dst_off - block_end);
+        }
+
+        /* Remember the current garbage collection count.  If the count
+         * increases during the write, then the block cache has been
+         * invalidated and we need to reset our pointers.
+         */
+        gc_count = nffs_gc_count;
+
+        data_offset = cache_block->ncb_file_offset + chunk_off - file_offset;
+        rc = nffs_write_over_block(cache_block->ncb_block.nb_hash_entry,
+                                   chunk_off, data + data_offset, chunk_sz);
+        if (rc != 0) {
+            return rc;
+        }
+
+        dst_off -= chunk_sz;
+
+        if (gc_count != nffs_gc_count) {
+            /* Garbage collection occurred; the current cached block pointer is
+             * invalid, so reset it.  The cached inode is still valid.
+             */
+            cache_block = NULL;
+        } else {
+            cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list,
+                                     ncb_link);
+        }
+    } while (data_offset > 0);
+
+    cache_inode->nci_file_size += append_len;
+    return 0;
+}
+
+/**
+ * Writes a chunk of contiguous data to a file.
+ *
+ * @param file                  The file to write to.
+ * @param data                  The data to write.
+ * @param len                   The length of data to write.
+ *
+ * @return                      0 on success; nonzero on failure.
+ */
+int
+nffs_write_to_file(struct nffs_file *file, const void *data, int len)
+{
+    struct nffs_cache_inode *cache_inode;
+    const uint8_t *data_ptr;
+    uint16_t chunk_size;
+    int rc;
+
+    if (!(file->nf_access_flags & FS_ACCESS_WRITE)) {
+        return FS_EACCESS;
+    }
+
+    if (len == 0) {
+        return 0;
+    }
+
+    rc = nffs_cache_inode_ensure(&cache_inode, file->nf_inode_entry);
+    if (rc != 0) {
+        return rc;
+    }
+
+    /* The append flag forces all writes to the end of the file, regardless of
+     * seek position.
+     */
+    if (file->nf_access_flags & FS_ACCESS_APPEND) {
+        file->nf_offset = cache_inode->nci_file_size;
+    }
+
+    /* Write data as a sequence of blocks. */
+    data_ptr = data;
+    while (len > 0) {
+        if (len > nffs_block_max_data_sz) {
+            chunk_size = nffs_block_max_data_sz;
+        } else {
+            chunk_size = len;
+        }
+
+        rc = nffs_write_chunk(file->nf_inode_entry, file->nf_offset, data_ptr,
+                              chunk_size);
+        if (rc != 0) {
+            return rc;
+        }
+
+        len -= chunk_size;
+        data_ptr += chunk_size;
+        file->nf_offset += chunk_size;
+    }
+
+    return 0;
+}
diff --git a/syscfg.yml b/syscfg.yml
new file mode 100644
index 0000000..33358d4
--- /dev/null
+++ b/syscfg.yml
@@ -0,0 +1,37 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Package: fs/nffs
+
+syscfg.defs:
+    NFFS_FLASH_AREA:
+        description: 'Flash area to use for NFFS.'
+        type: flash_owner
+        value:
+        restrictions:
+            - $notnull
+
+    NFFS_DETECT_FAIL:
+        description: 'Controls behaviour when encountering corrupt NFFS area.'
+        value: 'NFFS_DETECT_FAIL_FORMAT'
+
+    NFFS_NUM_AREAS:
+        description: >
+            Number of areas to allocate in the NFFS disk.  A smaller number is
+            used if the flash hardware cannot support this value.
+        value: 8
diff --git a/test/pkg.yml b/test/pkg.yml
new file mode 100644
index 0000000..9e64e71
--- /dev/null
+++ b/test/pkg.yml
@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+pkg.name: fs/nffs/test
+pkg.type: unittest
+pkg.description: "NFFS unit tests."
+pkg.author: "Apache Mynewt <dev@mynewt.apache.org>"
+pkg.homepage: "http://mynewt.apache.org/"
+pkg.keywords:
+
+pkg.deps: 
+    - fs/nffs
+    - test/testutil
+
+pkg.deps.SELFTEST:
+    - sys/console/stub
+    - sys/log/full
+    - sys/stats/stub
diff --git a/test/src/nffs_test.c b/test/src/nffs_test.c
new file mode 100644
index 0000000..369fc1c
--- /dev/null
+++ b/test/src/nffs_test.c
@@ -0,0 +1,739 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "sysinit/sysinit.h"
+#include "syscfg/syscfg.h"
+#include "hal/hal_flash.h"
+#include "testutil/testutil.h"
+#include "fs/fs.h"
+#include "nffs/nffs.h"
+#include "nffs/nffs_test.h"
+#include "nffs_test_priv.h"
+#include "nffs_priv.h"
+#include "nffs_test.h"
+
+#if MYNEWT_VAL(SELFTEST)
+struct nffs_area_desc nffs_selftest_area_descs[] = {
+        { 0x00000000, 16 * 1024 },
+        { 0x00004000, 16 * 1024 },
+        { 0x00008000, 16 * 1024 },
+        { 0x0000c000, 16 * 1024 },
+        { 0x00010000, 64 * 1024 },
+        { 0x00020000, 128 * 1024 },
+        { 0x00040000, 128 * 1024 },
+        { 0x00060000, 128 * 1024 },
+        { 0x00080000, 128 * 1024 },
+        { 0x000a0000, 128 * 1024 },
+        { 0x000c0000, 128 * 1024 },
+        { 0x000e0000, 128 * 1024 },
+        { 0, 0 },
+};
+
+struct nffs_area_desc *save_area_descs;
+
+void
+nffs_testcase_pre(void* arg)
+{
+    save_area_descs = nffs_current_area_descs;
+    nffs_current_area_descs = nffs_selftest_area_descs;
+    return;
+}
+
+void
+nffs_testcase_post(void* arg)
+{
+    nffs_current_area_descs = save_area_descs;
+    return;
+}
+
+TEST_CASE_DECL(nffs_test_unlink)
+TEST_CASE_DECL(nffs_test_mkdir)
+TEST_CASE_DECL(nffs_test_rename)
+TEST_CASE_DECL(nffs_test_truncate)
+TEST_CASE_DECL(nffs_test_append)
+TEST_CASE_DECL(nffs_test_read)
+TEST_CASE_DECL(nffs_test_open)
+TEST_CASE_DECL(nffs_test_overwrite_one)
+TEST_CASE_DECL(nffs_test_overwrite_two)
+TEST_CASE_DECL(nffs_test_overwrite_three)
+TEST_CASE_DECL(nffs_test_overwrite_many)
+TEST_CASE_DECL(nffs_test_long_filename)
+TEST_CASE_DECL(nffs_test_large_write)
+TEST_CASE_DECL(nffs_test_many_children)
+TEST_CASE_DECL(nffs_test_gc)
+TEST_CASE_DECL(nffs_test_wear_level)
+TEST_CASE_DECL(nffs_test_corrupt_scratch)
+TEST_CASE_DECL(nffs_test_incomplete_block)
+TEST_CASE_DECL(nffs_test_corrupt_block)
+TEST_CASE_DECL(nffs_test_large_unlink)
+TEST_CASE_DECL(nffs_test_large_system)
+TEST_CASE_DECL(nffs_test_lost_found)
+TEST_CASE_DECL(nffs_test_readdir)
+TEST_CASE_DECL(nffs_test_split_file)
+TEST_CASE_DECL(nffs_test_gc_on_oom)
+
+void
+nffs_test_suite_gen_1_1_init(void)
+{
+    nffs_config.nc_num_cache_inodes = 1;
+    nffs_config.nc_num_cache_blocks = 1;
+
+    tu_suite_set_pre_test_cb(nffs_testcase_pre, NULL);
+    tu_suite_set_post_test_cb(nffs_testcase_post, NULL);
+    return;
+}
+    
+void
+nffs_test_suite_gen_4_32_init(void)
+{
+    nffs_config.nc_num_cache_inodes = 4;
+    nffs_config.nc_num_cache_blocks = 32;
+
+    tu_suite_set_pre_test_cb(nffs_testcase_pre, NULL);
+    tu_suite_set_post_test_cb(nffs_testcase_post, NULL);
+    return;
+}
+    
+void
+nffs_test_suite_gen_32_1024_init(void)
+{
+    nffs_config.nc_num_cache_inodes = 32;
+    nffs_config.nc_num_cache_blocks = 1024;
+
+    tu_suite_set_pre_test_cb(nffs_testcase_pre, NULL);
+    tu_suite_set_post_test_cb(nffs_testcase_post, NULL);
+    return;
+}
+
+TEST_SUITE(nffs_test_suite)
+{
+    int rc;
+
+    rc = nffs_init();
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_unlink();
+    nffs_test_mkdir();
+    nffs_test_rename();
+    nffs_test_truncate();
+    nffs_test_append();
+    nffs_test_read();
+    nffs_test_open();
+    nffs_test_overwrite_one();
+    nffs_test_overwrite_two();
+    nffs_test_overwrite_three();
+    nffs_test_overwrite_many();
+    nffs_test_long_filename();
+    nffs_test_large_write();
+    nffs_test_many_children();
+    nffs_test_gc();
+    nffs_test_wear_level();
+    nffs_test_corrupt_scratch();
+    nffs_test_incomplete_block();
+    nffs_test_corrupt_block();
+    nffs_test_large_unlink();
+    nffs_test_large_system();
+    nffs_test_lost_found();
+    nffs_test_readdir();
+    nffs_test_split_file();
+    nffs_test_gc_on_oom();
+}
+
+TEST_CASE_DECL(nffs_test_cache_large_file)
+
+TEST_SUITE(nffs_suite_cache)
+{
+    int rc;
+
+    rc = nffs_init();
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_cache_large_file();
+}
+
+void
+nffs_test_suite_cache_init(void)
+{
+    memset(&nffs_config, 0, sizeof nffs_config);
+    nffs_config.nc_num_cache_inodes = 4;
+    nffs_config.nc_num_cache_blocks = 64;
+
+    tu_suite_set_pre_test_cb(nffs_testcase_pre, NULL);
+    tu_suite_set_post_test_cb(nffs_testcase_post, NULL);
+    return;
+}
+
+int
+main(void)
+{
+    nffs_config.nc_num_inodes = 1024 * 8;
+    nffs_config.nc_num_blocks = 1024 * 20;
+    nffs_current_area_descs = nffs_selftest_area_descs;
+
+    sysinit();
+
+    tu_suite_set_init_cb((void*)nffs_test_suite_gen_1_1_init, NULL);
+    nffs_test_suite();
+
+    tu_suite_set_init_cb((void*)nffs_test_suite_gen_4_32_init, NULL);
+    nffs_test_suite();
+
+    tu_suite_set_init_cb((void*)nffs_test_suite_gen_32_1024_init, NULL);
+    nffs_test_suite();
+
+    tu_suite_set_init_cb((void*)nffs_test_suite_cache_init, NULL);
+    nffs_suite_cache();
+
+    return tu_any_failed;
+}
+
+#if 0
+#include <unistd.h>
+
+void
+nffs_assert_handler(const char *file, int line, const char *func, const char *e)
+{
+    char msg[256];
+
+    snprintf(msg, sizeof(msg), "assert at %s:%d\n", file, line);
+    write(1, msg, strlen(msg));
+    _exit(1);
+}
+#endif
+
+#ifdef NFFS_DEBUG
+/*
+ * All debug stuff below this
+ */
+int print_verbose;
+
+void
+print_inode_entry(struct nffs_inode_entry *inode_entry, int indent)
+{
+    struct nffs_inode inode;
+    char name[NFFS_FILENAME_MAX_LEN + 1];
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    if (inode_entry == nffs_root_dir) {
+        printf("%*s/\n", indent, "");
+        return;
+    }
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    /*
+     * Dummy inode
+     */
+    if (rc == FS_ENOENT) {
+        printf("    DUMMY %d\n", rc);
+        return;
+    }
+
+    nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
+                         &area_idx, &area_offset);
+
+    rc = nffs_flash_read(area_idx,
+                         area_offset + sizeof (struct nffs_disk_inode),
+                         name, inode.ni_filename_len);
+
+    name[inode.ni_filename_len] = '\0';
+
+    /*printf("%*s%s\n", indent, "", name[0] == '\0' ? "/" : name);*/
+    printf("%*s%s %d %d %x\n", indent, "", name[0] == '\0' ? "/" : name,
+           inode.ni_filename_len, inode.ni_seq,
+           inode.ni_inode_entry->nie_flags);
+}
+
+void
+process_inode_entry(struct nffs_inode_entry *inode_entry, int indent)
+{
+    struct nffs_inode_entry *child;
+
+    print_inode_entry(inode_entry, indent);
+
+    if (nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) {
+        SLIST_FOREACH(child, &inode_entry->nie_child_list, nie_sibling_next) {
+            process_inode_entry(child, indent + 2);
+        }
+    }
+}
+
+int
+print_nffs_flash_inode(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_inode ndi;
+    char filename[128];
+    int len;
+    int rc;
+
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off,
+                         &ndi, sizeof(ndi));
+    assert(rc == 0);
+
+    memset(filename, 0, sizeof(filename));
+    len = min(sizeof(filename) - 1, ndi.ndi_filename_len);
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off + sizeof(ndi),
+                         filename, len);
+
+    printf("  off %x %s id %x flen %d seq %d last %x prnt %x flgs %x %s\n",
+           off,
+           (nffs_hash_id_is_file(ndi.ndi_id) ? "File" :
+            (nffs_hash_id_is_dir(ndi.ndi_id) ? "Dir" : "???")),
+           ndi.ndi_id,
+           ndi.ndi_filename_len,
+           ndi.ndi_seq,
+           ndi.ndi_lastblock_id,
+           ndi.ndi_parent_id,
+           ndi.ndi_flags,
+           filename);
+    return sizeof(ndi) + ndi.ndi_filename_len;
+}
+
+int
+print_nffs_flash_block(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_block ndb;
+    int rc;
+
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off,
+                        &ndb, sizeof(ndb));
+    assert(rc == 0);
+
+    printf("  off %x Block id %x len %d seq %d prev %x own ino %x\n",
+           off,
+           ndb.ndb_id,
+           ndb.ndb_data_len,
+           ndb.ndb_seq,
+           ndb.ndb_prev_id,
+           ndb.ndb_inode_id);
+    return sizeof(ndb) + ndb.ndb_data_len;
+}
+
+int
+print_nffs_flash_object(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_object ndo;
+
+    hal_flash_read(area->na_flash_id, area->na_offset + off,
+                        &ndo.ndo_un_obj, sizeof(ndo.ndo_un_obj));
+
+    if (nffs_hash_id_is_inode(ndo.ndo_disk_inode.ndi_id)) {
+        return print_nffs_flash_inode(area, off);
+
+    } else if (nffs_hash_id_is_block(ndo.ndo_disk_block.ndb_id)) {
+        return print_nffs_flash_block(area, off);
+
+    } else if (ndo.ndo_disk_block.ndb_id == 0xffffffff) {
+        return area->na_length;
+
+    } else {
+        return 1;
+    }
+}
+
+void
+print_nffs_flash_areas(int verbose)
+{
+    struct nffs_area area;
+    struct nffs_disk_area darea;
+    int off;
+    int i;
+
+    for (i = 0; nffs_current_area_descs[i].nad_length != 0; i++) {
+        if (i > NFFS_MAX_AREAS) {
+            return;
+        }
+        area.na_offset = nffs_current_area_descs[i].nad_offset;
+        area.na_length = nffs_current_area_descs[i].nad_length;
+        area.na_flash_id = nffs_current_area_descs[i].nad_flash_id;
+        hal_flash_read(area.na_flash_id, area.na_offset, &darea, sizeof(darea));
+        area.na_id = darea.nda_id;
+        area.na_cur = nffs_areas[i].na_cur;
+        if (!nffs_area_magic_is_set(&darea)) {
+            printf("Area header corrupt!\n");
+        }
+        printf("area %d: id %d %x-%x cur %x len %d flashid %x gc-seq %d %s%s\n",
+               i, area.na_id, area.na_offset, area.na_offset + area.na_length,
+               area.na_cur, area.na_length, area.na_flash_id, darea.nda_gc_seq,
+               nffs_scratch_area_idx == i ? "(scratch)" : "",
+               !nffs_area_magic_is_set(&darea) ? "corrupt" : "");
+        if (verbose < 2) {
+            off = sizeof (struct nffs_disk_area);
+            while (off < area.na_length) {
+                off += print_nffs_flash_object(&area, off);
+            }
+        }
+    }
+}
+
+static int
+nffs_hash_fn(uint32_t id)
+{
+    return id % NFFS_HASH_SIZE;
+}
+
+void
+print_hashlist(struct nffs_hash_entry *he)
+{
+    struct nffs_hash_list *list;
+    int idx = nffs_hash_fn(he->nhe_id);
+    list = nffs_hash + idx;
+
+    SLIST_FOREACH(he, list, nhe_next) {
+        printf("hash_entry %s 0x%x: id 0x%x flash_loc 0x%x next 0x%x\n",
+                   nffs_hash_id_is_inode(he->nhe_id) ? "inode" : "block",
+                   (unsigned int)he,
+                   he->nhe_id, he->nhe_flash_loc,
+                   (unsigned int)he->nhe_next.sle_next);
+   }
+}
+
+void
+print_hash(void)
+{
+    int i;
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+    struct nffs_inode ni;
+    struct nffs_disk_inode di;
+    struct nffs_block nb;
+    struct nffs_disk_block db;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    NFFS_HASH_FOREACH(he, i, next) {
+        if (nffs_hash_id_is_inode(he->nhe_id)) {
+            printf("hash_entry inode %d 0x%x: id 0x%x flash_loc 0x%x next 0x%x\n",
+                   i, (unsigned int)he,
+                   he->nhe_id, he->nhe_flash_loc,
+                   (unsigned int)he->nhe_next.sle_next);
+            if (he->nhe_id == NFFS_ID_ROOT_DIR) {
+                continue;
+            }
+            nffs_flash_loc_expand(he->nhe_flash_loc,
+                                  &area_idx, &area_offset);
+            rc = nffs_inode_read_disk(area_idx, area_offset, &di);
+            if (rc) {
+                printf("%d: fail inode read id 0x%x rc %d\n",
+                       i, he->nhe_id, rc);
+            }
+            printf("    Disk inode: id %x seq %d parent %x last %x flgs %x\n",
+                   di.ndi_id,
+                   di.ndi_seq,
+                   di.ndi_parent_id,
+                   di.ndi_lastblock_id,
+                   di.ndi_flags);
+            ni.ni_inode_entry = (struct nffs_inode_entry *)he;
+            ni.ni_seq = di.ndi_seq; 
+            ni.ni_parent = nffs_hash_find_inode(di.ndi_parent_id);
+            printf("    RAM inode: entry 0x%x seq %d parent %x filename %s\n",
+                   (unsigned int)ni.ni_inode_entry,
+                   ni.ni_seq,
+                   (unsigned int)ni.ni_parent,
+                   ni.ni_filename);
+
+        } else if (nffs_hash_id_is_block(he->nhe_id)) {
+            printf("hash_entry block %d 0x%x: id 0x%x flash_loc 0x%x next 0x%x\n",
+                   i, (unsigned int)he,
+                   he->nhe_id, he->nhe_flash_loc,
+                   (unsigned int)he->nhe_next.sle_next);
+            rc = nffs_block_from_hash_entry(&nb, he);
+            if (rc) {
+                printf("%d: fail block read id 0x%x rc %d\n",
+                       i, he->nhe_id, rc);
+            }
+            printf("    block: id %x seq %d inode %x prev %x\n",
+                   nb.nb_hash_entry->nhe_id, nb.nb_seq, 
+                   nb.nb_inode_entry->nie_hash_entry.nhe_id, 
+                   nb.nb_prev->nhe_id);
+            nffs_flash_loc_expand(nb.nb_hash_entry->nhe_flash_loc,
+                                  &area_idx, &area_offset);
+            rc = nffs_block_read_disk(area_idx, area_offset, &db);
+            if (rc) {
+                printf("%d: fail disk block read id 0x%x rc %d\n",
+                       i, nb.nb_hash_entry->nhe_id, rc);
+            }
+            printf("    disk block: id %x seq %d inode %x prev %x len %d\n",
+                   db.ndb_id,
+                   db.ndb_seq,
+                   db.ndb_inode_id,
+                   db.ndb_prev_id,
+                   db.ndb_data_len);
+        } else {
+            printf("hash_entry UNKNONN %d 0x%x: id 0x%x flash_loc 0x%x next 0x%x\n",
+                   i, (unsigned int)he,
+                   he->nhe_id, he->nhe_flash_loc,
+                   (unsigned int)he->nhe_next.sle_next);
+        }
+    }
+
+}
+
+void
+nffs_print_object(struct nffs_disk_object *dobj)
+{
+    struct nffs_disk_inode *di = &dobj->ndo_disk_inode;
+    struct nffs_disk_block *db = &dobj->ndo_disk_block;
+
+    if (dobj->ndo_type == NFFS_OBJECT_TYPE_INODE) {
+        printf("    %s id %x seq %d prnt %x last %x\n",
+               nffs_hash_id_is_file(di->ndi_id) ? "File" :
+                nffs_hash_id_is_dir(di->ndi_id) ? "Dir" : "???",
+               di->ndi_id, di->ndi_seq, di->ndi_parent_id,
+               di->ndi_lastblock_id);
+    } else if (dobj->ndo_type != NFFS_OBJECT_TYPE_BLOCK) {
+        printf("    %s: id %x seq %d ino %x prev %x len %d\n",
+               nffs_hash_id_is_block(db->ndb_id) ? "Block" : "Block?",
+               db->ndb_id, db->ndb_seq, db->ndb_inode_id,
+               db->ndb_prev_id, db->ndb_data_len);
+    }
+}
+
+void
+print_nffs_hash_block(struct nffs_hash_entry *he, int verbose)
+{
+    struct nffs_block nb;
+    struct nffs_disk_block db;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    if (he == NULL) {
+        return;
+    }
+    if (!nffs_hash_entry_is_dummy(he)) {
+        nffs_flash_loc_expand(he->nhe_flash_loc,
+                              &area_idx, &area_offset);
+        rc = nffs_block_read_disk(area_idx, area_offset, &db);
+        if (rc) {
+            printf("%p: fail block read id 0x%x rc %d\n",
+                   he, he->nhe_id, rc);
+        }
+        nb.nb_hash_entry = he;
+        nb.nb_seq = db.ndb_seq;
+        if (db.ndb_inode_id != NFFS_ID_NONE) {
+            nb.nb_inode_entry = nffs_hash_find_inode(db.ndb_inode_id);
+        } else {
+            nb.nb_inode_entry = (void*)db.ndb_inode_id;
+        }
+        if (db.ndb_prev_id != NFFS_ID_NONE) {
+            nb.nb_prev = nffs_hash_find_block(db.ndb_prev_id);
+        } else {
+            nb.nb_prev = (void*)db.ndb_prev_id;
+        }
+        nb.nb_data_len = db.ndb_data_len;
+    } else {
+        nb.nb_inode_entry = NULL;
+        db.ndb_id = 0;
+    }
+    if (!verbose) {
+        printf("%s%s id %x idx/off %d/%x seq %d ino %x prev %x len %d\n",
+               nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+               nffs_hash_id_is_block(he->nhe_id) ? "Block" : "Unknown",
+               he->nhe_id, area_idx, area_offset, nb.nb_seq,
+               nb.nb_inode_entry->nie_hash_entry.nhe_id,
+               (unsigned int)db.ndb_prev_id, db.ndb_data_len);
+        return;
+    }
+    printf("%s%s id %x loc %x/%x %x ent %p\n",
+           nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+           nffs_hash_id_is_block(he->nhe_id) ? "Block:" : "Unknown:",
+           he->nhe_id, area_idx, area_offset, he->nhe_flash_loc, he);
+    if (nb.nb_inode_entry) {
+        printf("  Ram: ent %p seq %d ino %p prev %p len %d\n",
+               nb.nb_hash_entry, nb.nb_seq,
+               nb.nb_inode_entry, nb.nb_prev, nb.nb_data_len);
+    }
+    if (db.ndb_id) {
+        printf("  Disk %s id %x seq %d ino %x prev %x len %d\n",
+               nffs_hash_id_is_block(db.ndb_id) ? "Block:" : "???:",
+               db.ndb_id, db.ndb_seq, db.ndb_inode_id,
+               db.ndb_prev_id, db.ndb_data_len);
+    }
+}
+
+void
+print_nffs_hash_inode(struct nffs_hash_entry *he, int verbose)
+{
+    struct nffs_inode ni;
+    struct nffs_disk_inode di;
+    struct nffs_inode_entry *nie = (struct nffs_inode_entry*)he;
+    int cached_name_len;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    if (he == NULL) {
+        return;
+    }
+    if (!nffs_hash_entry_is_dummy(he)) {
+        nffs_flash_loc_expand(he->nhe_flash_loc,
+                              &area_idx, &area_offset);
+        rc = nffs_inode_read_disk(area_idx, area_offset, &di);
+        if (rc) {
+            printf("Entry %p: fail inode read id 0x%x rc %d\n",
+                   he, he->nhe_id, rc);
+        }
+        ni.ni_inode_entry = (struct nffs_inode_entry *)he;
+        ni.ni_seq = di.ndi_seq; 
+        if (di.ndi_parent_id != NFFS_ID_NONE) {
+            ni.ni_parent = nffs_hash_find_inode(di.ndi_parent_id);
+        } else {
+            ni.ni_parent = NULL;
+        }
+        if (ni.ni_filename_len > NFFS_SHORT_FILENAME_LEN) {
+            cached_name_len = NFFS_SHORT_FILENAME_LEN;
+        } else {
+            cached_name_len = ni.ni_filename_len;
+        }
+        if (cached_name_len != 0) {
+            rc = nffs_flash_read(area_idx, area_offset + sizeof di,
+                         ni.ni_filename, cached_name_len);
+            if (rc != 0) {
+                printf("entry %p: fail filename read id 0x%x rc %d\n",
+                       he, he->nhe_id, rc);
+                return;
+            }
+        }
+    } else {
+        ni.ni_inode_entry = NULL;
+        di.ndi_id = 0;
+    }
+    if (!verbose) {
+        printf("%s%s id %x idx/off %x/%x seq %d prnt %x last %x flags %x",
+               nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+
+               nffs_hash_id_is_file(he->nhe_id) ? "File" :
+                he->nhe_id == NFFS_ID_ROOT_DIR ? "**ROOT Dir" : 
+                nffs_hash_id_is_dir(he->nhe_id) ? "Dir" : "Inode",
+
+               he->nhe_id, area_idx, area_offset, ni.ni_seq, di.ndi_parent_id,
+               di.ndi_lastblock_id, nie->nie_flags);
+        if (ni.ni_inode_entry) {
+            printf(" ref %d\n", ni.ni_inode_entry->nie_refcnt);
+        } else {
+            printf("\n");
+        }
+        return;
+    }
+    printf("%s%s id %x loc %x/%x %x entry %p\n",
+           nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+           nffs_hash_id_is_file(he->nhe_id) ? "File:" :
+            he->nhe_id == NFFS_ID_ROOT_DIR ? "**ROOT Dir:" : 
+            nffs_hash_id_is_dir(he->nhe_id) ? "Dir:" : "Inode:",
+           he->nhe_id, area_idx, area_offset, he->nhe_flash_loc, he);
+    if (ni.ni_inode_entry) {
+        printf("  ram: ent %p seq %d prnt %p lst %p ref %d flgs %x nm %s\n",
+               ni.ni_inode_entry, ni.ni_seq, ni.ni_parent,
+               ni.ni_inode_entry->nie_last_block_entry,
+               ni.ni_inode_entry->nie_refcnt, ni.ni_inode_entry->nie_flags,
+               ni.ni_filename);
+    }
+    if (rc == 0) {
+        printf("  Disk %s: id %x seq %d prnt %x lst %x flgs %x\n",
+               nffs_hash_id_is_file(di.ndi_id) ? "File" :
+                nffs_hash_id_is_dir(di.ndi_id) ? "Dir" : "???",
+               di.ndi_id, di.ndi_seq, di.ndi_parent_id,
+               di.ndi_lastblock_id, di.ndi_flags);
+    }
+}
+
+void
+print_hash_entries(int verbose)
+{
+    int i;
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+
+    printf("\nnffs_hash_entries:\n");
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        he = SLIST_FIRST(nffs_hash + i);
+        while (he != NULL) {
+            next = SLIST_NEXT(he, nhe_next);
+            if (nffs_hash_id_is_inode(he->nhe_id)) {
+                print_nffs_hash_inode(he, verbose);
+            } else if (nffs_hash_id_is_block(he->nhe_id)) {
+                print_nffs_hash_block(he, verbose);
+            } else {
+                printf("UNKNOWN type hash entry %d: id 0x%x loc 0x%x\n",
+                       i, he->nhe_id, he->nhe_flash_loc);
+            }
+            he = next;
+        }
+    }
+}
+
+void
+print_nffs_hashlist(int verbose)
+{
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+    int i;
+
+    NFFS_HASH_FOREACH(he, i, next) {
+        if (nffs_hash_id_is_inode(he->nhe_id)) {
+            print_nffs_hash_inode(he, verbose);
+        } else if (nffs_hash_id_is_block(he->nhe_id)) {
+            print_nffs_hash_block(he, verbose);
+        } else {
+            printf("UNKNOWN type hash entry %d: id 0x%x loc 0x%x\n",
+                   i, he->nhe_id, he->nhe_flash_loc);
+        }
+    }
+}
+
+void
+printfs()
+{
+    if (nffs_misc_ready()) {
+        printf("NFFS directory:\n");
+        process_inode_entry(nffs_root_dir, print_verbose);
+
+        printf("\nNFFS hash list:\n");
+        print_nffs_hashlist(print_verbose);
+    }
+    printf("\nNFFS flash areas:\n");
+    print_nffs_flash_areas(print_verbose);
+}
+#endif /* NFFS_DEBUG */
+#endif /* MYNEWT_VAL */
diff --git a/test/src/nffs_test.h b/test/src/nffs_test.h
new file mode 100644
index 0000000..a7b5c3c
--- /dev/null
+++ b/test/src/nffs_test.h
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NFFS_TEST_
+#define H_NFFS_TEST_
+
+#define LOG_BUILD_STRING "{{TARGET}} Build {{BUILD_NUMBER}}:"
+
+int nffs_test_all(void);
+
+#endif
diff --git a/test/src/nffs_test_debug.c b/test/src/nffs_test_debug.c
new file mode 100644
index 0000000..2e2965d
--- /dev/null
+++ b/test/src/nffs_test_debug.c
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "hal/hal_flash.h"
+#include "testutil/testutil.h"
+#include "fs/fs.h"
+#include "nffs/nffs.h"
+#include "nffs_test.h"
+#include "nffs_test_priv.h"
+#include "nffs_priv.h"
+
+int print_verbose;
+
+void
+print_inode_entry(struct nffs_inode_entry *inode_entry, int indent)
+{
+    struct nffs_inode inode;
+    char name[NFFS_FILENAME_MAX_LEN + 1];
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    if (inode_entry == nffs_root_dir) {
+        printf("%*s/\n", indent, "");
+        return;
+    }
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    /*
+     * Dummy inode
+     */
+    if (rc == FS_ENOENT) {
+        printf("    DUMMY %d\n", rc);
+        return;
+    }
+
+    nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
+                         &area_idx, &area_offset);
+
+
+    rc = nffs_flash_read(area_idx,
+                         area_offset + sizeof (struct nffs_disk_inode),
+                         name, inode.ni_filename_len);
+
+    name[inode.ni_filename_len] = '\0';
+
+    printf("%*s%s %ju %jx\n", indent, "", name[0] == '\0' ? "/" : name,
+           (uintmax_t) inode.ni_seq,
+           (uintmax_t) inode.ni_inode_entry->nie_flags);
+}
+
+void
+process_inode_entry(struct nffs_inode_entry *inode_entry, int indent)
+{
+    struct nffs_inode_entry *child;
+
+    print_inode_entry(inode_entry, indent);
+
+    if (nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) {
+        SLIST_FOREACH(child, &inode_entry->nie_child_list, nie_sibling_next) {
+            process_inode_entry(child, indent + 2);
+        }
+    }
+}
+
+int
+print_nffs_flash_inode(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_inode ndi;
+    char filename[128];
+    int len;
+    int rc;
+
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off,
+                         &ndi, sizeof(ndi));
+    assert(rc == 0);
+
+    memset(filename, 0, sizeof(filename));
+    len = min(sizeof(filename) - 1, ndi.ndi_filename_len);
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off + sizeof(ndi),
+                         filename, len);
+
+    printf("  off %jx %s id %jx flen %ju seq %ju last %jx prnt %jx "
+           "flgs %jx %s\n",
+           (uintmax_t) off,
+           (nffs_hash_id_is_file(ndi.ndi_id) ? "File" :
+            (nffs_hash_id_is_dir(ndi.ndi_id) ? "Dir" : "???")),
+           (uintmax_t) ndi.ndi_id,
+           (uintmax_t) ndi.ndi_filename_len,
+           (uintmax_t) ndi.ndi_seq,
+           (uintmax_t) ndi.ndi_lastblock_id,
+           (uintmax_t) ndi.ndi_parent_id,
+           (uintmax_t) ndi.ndi_flags,
+           filename);
+    return sizeof(ndi) + ndi.ndi_filename_len;
+}
+
+int
+print_nffs_flash_block(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_block ndb;
+    int rc;
+
+    rc = hal_flash_read(area->na_flash_id, area->na_offset + off,
+                        &ndb, sizeof(ndb));
+    assert(rc == 0);
+
+    printf("  off %jx Block id %jx len %ju seq %ju prev %jx own ino %jx\n",
+           (uintmax_t) off,
+           (uintmax_t) ndb.ndb_id,
+           (uintmax_t) ndb.ndb_data_len,
+           (uintmax_t) ndb.ndb_seq,
+           (uintmax_t) ndb.ndb_prev_id,
+           (uintmax_t) ndb.ndb_inode_id);
+    return sizeof(ndb) + ndb.ndb_data_len;
+}
+
+int
+print_nffs_flash_object(struct nffs_area *area, uint32_t off)
+{
+    struct nffs_disk_object ndo;
+
+    hal_flash_read(area->na_flash_id, area->na_offset + off,
+                        &ndo.ndo_un_obj, sizeof(ndo.ndo_un_obj));
+
+    if (nffs_hash_id_is_inode(ndo.ndo_disk_inode.ndi_id)) {
+        return print_nffs_flash_inode(area, off);
+
+    } else if (nffs_hash_id_is_block(ndo.ndo_disk_block.ndb_id)) {
+        return print_nffs_flash_block(area, off);
+
+    } else if (ndo.ndo_disk_block.ndb_id == 0xffffffff) {
+        return area->na_length;
+
+    } else {
+        return 1;
+    }
+}
+
+void
+print_nffs_flash_areas(int verbose)
+{
+    struct nffs_area area;
+    struct nffs_disk_area darea;
+    int off;
+    int i;
+
+    for (i = 0; nffs_current_area_descs[i].nad_length != 0; i++) {
+        if (i > NFFS_MAX_AREAS) {
+            return;
+        }
+        area.na_offset = nffs_current_area_descs[i].nad_offset;
+        area.na_length = nffs_current_area_descs[i].nad_length;
+        area.na_flash_id = nffs_current_area_descs[i].nad_flash_id;
+        hal_flash_read(area.na_flash_id, area.na_offset, &darea, sizeof(darea));
+        area.na_id = darea.nda_id;
+        area.na_cur = nffs_areas[i].na_cur;
+        if (!nffs_area_magic_is_set(&darea)) {
+            printf("Area header corrupt!\n");
+        }
+        printf("area %d: id %ju %jx-%jx cur %jx len %ju flashid %jx "
+               "gc-seq %jd %s%s\n",
+               i,
+               (uintmax_t)area.na_id,
+               (uintmax_t)area.na_offset,
+               (uintmax_t)area.na_offset + area.na_length,
+               (uintmax_t)area.na_cur,
+               (uintmax_t)area.na_length,
+               (uintmax_t)area.na_flash_id,
+               (uintmax_t)darea.nda_gc_seq,
+               nffs_scratch_area_idx == i ? "(scratch)" : "",
+               !nffs_area_magic_is_set(&darea) ? "corrupt" : "");
+        if (verbose < 2) {
+            off = sizeof (struct nffs_disk_area);
+            while (off < area.na_length) {
+                off += print_nffs_flash_object(&area, off);
+            }
+        }
+    }
+}
+
+static int
+nffs_hash_fn(uint32_t id)
+{
+    return id % NFFS_HASH_SIZE;
+}
+
+void
+print_hashlist(struct nffs_hash_entry *he)
+{
+    struct nffs_hash_list *list;
+    int idx = nffs_hash_fn(he->nhe_id);
+    list = nffs_hash + idx;
+
+    SLIST_FOREACH(he, list, nhe_next) {
+        printf("hash_entry %s %p: id 0x%jx flash_loc 0x%jx next %p\n",
+                   nffs_hash_id_is_inode(he->nhe_id) ? "inode" : "block",
+                   he,
+                   (uintmax_t)he->nhe_id,
+                   (uintmax_t)he->nhe_flash_loc,
+                   he->nhe_next.sle_next);
+   }
+}
+
+void
+print_hash(void)
+{
+    int i;
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+    struct nffs_inode ni;
+    struct nffs_disk_inode di;
+    struct nffs_block nb;
+    struct nffs_disk_block db;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    NFFS_HASH_FOREACH(he, i, next) {
+        if (nffs_hash_id_is_inode(he->nhe_id)) {
+            printf("hash_entry inode %d %p: id 0x%jx flash_loc 0x%jx "
+                   "next %p\n",
+                   i,
+                   he,
+                   (uintmax_t)he->nhe_id,
+                   (uintmax_t)he->nhe_flash_loc,
+                   he->nhe_next.sle_next);
+            if (he->nhe_id == NFFS_ID_ROOT_DIR) {
+                continue;
+            }
+            nffs_flash_loc_expand(he->nhe_flash_loc,
+                                  &area_idx, &area_offset);
+            rc = nffs_inode_read_disk(area_idx, area_offset, &di);
+            if (rc) {
+                printf("%d: fail inode read id 0x%jx rc %d\n",
+                       i, (uintmax_t)he->nhe_id, rc);
+            }
+            printf("    Disk inode: id %jx seq %ju parent %jx last %jx "
+                   "flgs %jx\n",
+                   (uintmax_t)di.ndi_id,
+                   (uintmax_t)di.ndi_seq,
+                   (uintmax_t)di.ndi_parent_id,
+                   (uintmax_t)di.ndi_lastblock_id,
+                   (uintmax_t)di.ndi_flags);
+            ni.ni_inode_entry = (struct nffs_inode_entry *)he;
+            ni.ni_seq = di.ndi_seq; 
+            ni.ni_parent = nffs_hash_find_inode(di.ndi_parent_id);
+            printf("    RAM inode: entry %p seq %ju parent %p "
+                   "filename %s\n",
+                   ni.ni_inode_entry,
+                   (uintmax_t)ni.ni_seq,
+                   ni.ni_parent,
+                   ni.ni_filename);
+
+        } else if (nffs_hash_id_is_block(he->nhe_id)) {
+            printf("hash_entry block %d %p: id 0x%jx flash_loc 0x%jx "
+                   "next %p\n",
+                   i,
+                   he,
+                   (uintmax_t)he->nhe_id,
+                   (uintmax_t)he->nhe_flash_loc,
+                   he->nhe_next.sle_next);
+            rc = nffs_block_from_hash_entry(&nb, he);
+            if (rc) {
+                printf("%d: fail block read id 0x%jx rc %d\n",
+                       i, (uintmax_t)he->nhe_id, rc);
+            }
+            printf("    block: id %jx seq %ju inode %jx prev %jx\n",
+                   (uintmax_t)nb.nb_hash_entry->nhe_id,
+                   (uintmax_t)nb.nb_seq, 
+                   (uintmax_t)nb.nb_inode_entry->nie_hash_entry.nhe_id, 
+                   (uintmax_t)nb.nb_prev->nhe_id);
+            nffs_flash_loc_expand(nb.nb_hash_entry->nhe_flash_loc,
+                                  &area_idx, &area_offset);
+            rc = nffs_block_read_disk(area_idx, area_offset, &db);
+            if (rc) {
+                printf("%d: fail disk block read id 0x%jx rc %d\n",
+                       i, (uintmax_t)nb.nb_hash_entry->nhe_id, rc);
+            }
+            printf("    disk block: id %jx seq %ju inode %jx prev %jx "
+                   "len %ju\n",
+                   (uintmax_t)db.ndb_id,
+                   (uintmax_t)db.ndb_seq,
+                   (uintmax_t)db.ndb_inode_id,
+                   (uintmax_t)db.ndb_prev_id,
+                   (uintmax_t)db.ndb_data_len);
+        } else {
+            printf("hash_entry UNKNONN %d %p: id 0x%jx flash_loc 0x%jx "
+                   "next %p\n",
+                   i,
+                   he,
+                   (uintmax_t)he->nhe_id,
+                   (uintmax_t)he->nhe_flash_loc,
+                   he->nhe_next.sle_next);
+        }
+    }
+
+}
+
+void
+nffs_print_object(struct nffs_disk_object *dobj)
+{
+    struct nffs_disk_inode *di = &dobj->ndo_disk_inode;
+    struct nffs_disk_block *db = &dobj->ndo_disk_block;
+
+    if (dobj->ndo_type == NFFS_OBJECT_TYPE_INODE) {
+        printf("    %s id %jx seq %ju prnt %jx last %jx\n",
+               nffs_hash_id_is_file(di->ndi_id) ? "File" :
+                nffs_hash_id_is_dir(di->ndi_id) ? "Dir" : "???",
+               (uintmax_t)di->ndi_id,
+               (uintmax_t)di->ndi_seq,
+               (uintmax_t)di->ndi_parent_id,
+               (uintmax_t)di->ndi_lastblock_id);
+    } else if (dobj->ndo_type != NFFS_OBJECT_TYPE_BLOCK) {
+        printf("    %s: id %jx seq %ju ino %jx prev %jx len %ju\n",
+               nffs_hash_id_is_block(db->ndb_id) ? "Block" : "Block?",
+               (uintmax_t)db->ndb_id,
+               (uintmax_t)db->ndb_seq,
+               (uintmax_t)db->ndb_inode_id,
+               (uintmax_t)db->ndb_prev_id,
+               (uintmax_t)db->ndb_data_len);
+    }
+}
+
+void
+print_nffs_hash_block(struct nffs_hash_entry *he, int verbose)
+{
+    struct nffs_block nb = { 0 };
+    struct nffs_disk_block db = { 0 };
+    uint32_t area_offset = 0;
+    uint8_t area_idx = 0;
+    int rc;
+
+    if (he == NULL) {
+        return;
+    }
+    if (!nffs_hash_entry_is_dummy(he)) {
+        nffs_flash_loc_expand(he->nhe_flash_loc,
+                              &area_idx, &area_offset);
+        rc = nffs_block_read_disk(area_idx, area_offset, &db);
+        if (rc) {
+            printf("%p: fail block read id 0x%jx rc %d\n",
+                   he, (uintmax_t)he->nhe_id, rc);
+
+        }
+        nb.nb_hash_entry = he;
+        nb.nb_seq = db.ndb_seq;
+        if (db.ndb_inode_id != NFFS_ID_NONE) {
+            nb.nb_inode_entry = nffs_hash_find_inode(db.ndb_inode_id);
+        } else {
+            nb.nb_inode_entry = (void*)db.ndb_inode_id;
+        }
+        if (db.ndb_prev_id != NFFS_ID_NONE) {
+            nb.nb_prev = nffs_hash_find_block(db.ndb_prev_id);
+        } else {
+            nb.nb_prev = (void*)db.ndb_prev_id;
+        }
+        nb.nb_data_len = db.ndb_data_len;
+    }
+    if (!verbose) {
+        printf("%s%s id %jx idx/off %ju/%jx seq %ju ino %jx prev %jx "
+               "len %ju\n",
+               nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+               nffs_hash_id_is_block(he->nhe_id) ? "Block" : "Unknown",
+               (uintmax_t)he->nhe_id,
+               (uintmax_t)area_idx,
+               (uintmax_t)area_offset,
+               (uintmax_t)nb.nb_seq,
+               (uintmax_t)(nb.nb_inode_entry ? nb.nb_inode_entry->nie_hash_entry.nhe_id : 0),
+               (uintmax_t)db.ndb_prev_id,
+               (uintmax_t)db.ndb_data_len);
+        return;
+    }
+    printf("%s%s id %jx loc %jx/%jx %jx ent %p\n",
+           nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+           nffs_hash_id_is_block(he->nhe_id) ? "Block:" : "Unknown:",
+           (uintmax_t)he->nhe_id,
+           (uintmax_t)area_idx,
+           (uintmax_t)area_offset,
+           (uintmax_t)he->nhe_flash_loc,
+           he);
+    if (nb.nb_inode_entry) {
+        printf("  Ram: ent %p seq %ju ino %p prev %p len %ju\n",
+               nb.nb_hash_entry,
+               (uintmax_t)nb.nb_seq,
+               nb.nb_inode_entry,
+               nb.nb_prev,
+               (uintmax_t)nb.nb_data_len);
+    }
+    if (db.ndb_id) {
+        printf("  Disk %s id %jx seq %ju ino %jx prev %jx len %ju\n",
+               nffs_hash_id_is_block(db.ndb_id) ? "Block:" : "???:",
+               (uintmax_t)db.ndb_id,
+               (uintmax_t)db.ndb_seq,
+               (uintmax_t)db.ndb_inode_id,
+               (uintmax_t)db.ndb_prev_id,
+               (uintmax_t)db.ndb_data_len);
+    }
+}
+
+void
+print_nffs_hash_inode(struct nffs_hash_entry *he, int verbose)
+{
+    struct nffs_inode ni = { 0 };
+    struct nffs_disk_inode di = { 0 };
+    struct nffs_inode_entry *nie = (struct nffs_inode_entry*)he;
+    int cached_name_len;
+    uint32_t area_offset = 0;
+    uint8_t area_idx = 0;
+    int rc = 0;
+
+    if (he == NULL) {
+        return;
+    }
+    if (!nffs_hash_entry_is_dummy(he)) {
+        nffs_flash_loc_expand(he->nhe_flash_loc,
+                              &area_idx, &area_offset);
+        rc = nffs_inode_read_disk(area_idx, area_offset, &di);
+        if (rc) {
+            printf("Entry %p: fail inode read id 0x%jx rc %d\n",
+                   he, (uintmax_t)he->nhe_id, rc);
+        }
+        ni.ni_inode_entry = (struct nffs_inode_entry *)he;
+        ni.ni_seq = di.ndi_seq; 
+        if (di.ndi_parent_id != NFFS_ID_NONE) {
+            ni.ni_parent = nffs_hash_find_inode(di.ndi_parent_id);
+        } else {
+            ni.ni_parent = NULL;
+        }
+        if (ni.ni_filename_len > NFFS_SHORT_FILENAME_LEN) {
+            cached_name_len = NFFS_SHORT_FILENAME_LEN;
+        } else {
+            cached_name_len = ni.ni_filename_len;
+        }
+        if (cached_name_len != 0) {
+            rc = nffs_flash_read(area_idx, area_offset + sizeof di,
+                         ni.ni_filename, cached_name_len);
+            if (rc != 0) {
+                printf("entry %p: fail filename read id 0x%jx rc %d\n",
+                       he, (uintmax_t)he->nhe_id, rc);
+                return;
+            }
+        }
+    }
+    if (!verbose) {
+        printf("%s%s id %jx idx/off %jx/%jx seq %ju prnt %jx last %jx "
+               "flags %jx",
+               nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+               nffs_hash_id_is_file(he->nhe_id) ? "File" :
+                he->nhe_id == NFFS_ID_ROOT_DIR ? "**ROOT Dir" : 
+                nffs_hash_id_is_dir(he->nhe_id) ? "Dir" : "Inode",
+
+               (uintmax_t)he->nhe_id,
+               (uintmax_t)area_idx,
+               (uintmax_t)area_offset,
+               (uintmax_t)ni.ni_seq,
+               (uintmax_t)di.ndi_parent_id,
+               (uintmax_t)di.ndi_lastblock_id,
+               (uintmax_t)nie->nie_flags);
+        if (ni.ni_inode_entry) {
+            printf(" ref %ju\n", (uintmax_t)ni.ni_inode_entry->nie_refcnt);
+        } else {
+            printf("\n");
+        }
+        return;
+    }
+    printf("%s%s id %jx loc %jx/%jx %jx entry %p\n",
+           nffs_hash_entry_is_dummy(he) ? "Dummy " : "",
+           nffs_hash_id_is_file(he->nhe_id) ? "File:" :
+            he->nhe_id == NFFS_ID_ROOT_DIR ? "**ROOT Dir:" : 
+            nffs_hash_id_is_dir(he->nhe_id) ? "Dir:" : "Inode:",
+           (uintmax_t)he->nhe_id,
+           (uintmax_t)area_idx,
+           (uintmax_t)area_offset,
+           (uintmax_t)he->nhe_flash_loc,
+           he);
+    if (ni.ni_inode_entry) {
+        printf("  ram: ent %p seq %ju prnt %p lst %p ref %ju flgs %jx nm %s\n",
+               ni.ni_inode_entry,
+               (uintmax_t)ni.ni_seq,
+               ni.ni_parent,
+               ni.ni_inode_entry->nie_last_block_entry,
+               (uintmax_t)ni.ni_inode_entry->nie_refcnt,
+               (uintmax_t)ni.ni_inode_entry->nie_flags,
+               ni.ni_filename);
+    }
+    if (rc == 0) {
+        printf("  Disk %s: id %jx seq %ju prnt %jx lst %jx flgs %jx\n",
+               nffs_hash_id_is_file(di.ndi_id) ? "File" :
+                nffs_hash_id_is_dir(di.ndi_id) ? "Dir" : "???",
+               (uintmax_t)di.ndi_id,
+               (uintmax_t)di.ndi_seq,
+               (uintmax_t)di.ndi_parent_id,
+               (uintmax_t)di.ndi_lastblock_id,
+               (uintmax_t)di.ndi_flags);
+    }
+}
+
+void
+print_hash_entries(int verbose)
+{
+    int i;
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+
+    printf("\nnffs_hash_entries:\n");
+    for (i = 0; i < NFFS_HASH_SIZE; i++) {
+        he = SLIST_FIRST(nffs_hash + i);
+        while (he != NULL) {
+            next = SLIST_NEXT(he, nhe_next);
+            if (nffs_hash_id_is_inode(he->nhe_id)) {
+                print_nffs_hash_inode(he, verbose);
+            } else if (nffs_hash_id_is_block(he->nhe_id)) {
+                print_nffs_hash_block(he, verbose);
+            } else {
+                printf("UNKNOWN type hash entry %d: id 0x%jx loc 0x%jx\n",
+                       i,
+                       (uintmax_t)he->nhe_id,
+                       (uintmax_t)he->nhe_flash_loc);
+            }
+            he = next;
+        }
+    }
+}
+
+void
+print_nffs_hashlist(int verbose)
+{
+    struct nffs_hash_entry *he;
+    struct nffs_hash_entry *next;
+    int i;
+
+    NFFS_HASH_FOREACH(he, i, next) {
+        if (nffs_hash_id_is_inode(he->nhe_id)) {
+            print_nffs_hash_inode(he, verbose);
+        } else if (nffs_hash_id_is_block(he->nhe_id)) {
+            print_nffs_hash_block(he, verbose);
+        } else {
+            printf("UNKNOWN type hash entry %d: id 0x%jx loc 0x%jx\n",
+                   i,
+                   (uintmax_t)he->nhe_id,
+                   (uintmax_t)he->nhe_flash_loc);
+        }
+    }
+}
+
+void
+printfs()
+{
+    if (nffs_misc_ready()) {
+        printf("NFFS directory:\n");
+        process_inode_entry(nffs_root_dir, print_verbose);
+
+        printf("\nNFFS hash list:\n");
+        print_nffs_hashlist(print_verbose);
+    }
+    printf("\nNFFS flash areas:\n");
+    print_nffs_flash_areas(print_verbose);
+}
diff --git a/test/src/nffs_test_priv.h b/test/src/nffs_test_priv.h
new file mode 100644
index 0000000..e685937
--- /dev/null
+++ b/test/src/nffs_test_priv.h
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef H_NFFS_TEST_PRIV_
+#define H_NFFS_TEST_PRIV_
+
+#ifdef __cplusplus
+#extern "C" {
+#endif
+
+struct nffs_test_block_desc {
+    const char *data;
+    int data_len;
+};
+
+struct nffs_test_file_desc {
+    const char *filename;
+    int is_dir;
+    const char *contents;
+    int contents_len;
+    struct nffs_test_file_desc *children;
+};
+
+int nffs_test(void);
+
+extern const struct nffs_test_file_desc *nffs_test_system_01;
+extern const struct nffs_test_file_desc *nffs_test_system_01_rm_1014_mk10;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/test/src/nffs_test_system_01.c b/test/src/nffs_test_system_01.c
new file mode 100644
index 0000000..f25e745
--- /dev/null
+++ b/test/src/nffs_test_system_01.c
@@ -0,0 +1,6537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+/** Generated by makefs.rb */
+
+#include <stddef.h>
+#include "nffs_test_priv.h"
+
+const struct nffs_test_file_desc *nffs_test_system_01 =
+    (struct nffs_test_file_desc[]) {
+{
+.filename = "",
+.is_dir = 1,
+.children = (struct nffs_test_file_desc[]) {
+ {
+ .filename = "lvl1dir-0000",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0001",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0002",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0003",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0004",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+{ .filename = NULL, } }
+},
+};
+
+const struct nffs_test_file_desc *nffs_test_system_01_rm_1014_mk10 =
+    (struct nffs_test_file_desc[]) {
+{
+.filename = "",
+.is_dir = 1,
+.children = (struct nffs_test_file_desc[]) {
+ {
+ .filename = "lvl1dir-0000",
+ .is_dir = 1,
+ },
+ {
+ .filename = "lvl1dir-0001",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0002",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+ {
+ .filename = "lvl1dir-0003",
+ .is_dir = 1,
+ .children = (struct nffs_test_file_desc[]) {
+  {
+  .filename = "lvl2dir-0000",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0001",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0002",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0003",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+  {
+  .filename = "lvl2dir-0004",
+  .is_dir = 1,
+  .children = (struct nffs_test_file_desc[]) {
+   {
+   .filename = "lvl3dir-0000",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0001",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0002",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0003",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+   {
+   .filename = "lvl3dir-0004",
+   .is_dir = 1,
+   .children = (struct nffs_test_file_desc[]) {
+    {
+    .filename = "lvl4file-0000",
+    .contents = "0",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0001",
+    .contents = "1",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0002",
+    .contents = "2",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0003",
+    .contents = "3",
+    .contents_len = 1,
+    },
+    {
+    .filename = "lvl4file-0004",
+    .contents = "4",
+    .contents_len = 1,
+    },
+   { .filename = NULL, } }
+   },
+  { .filename = NULL, } }
+  },
+ { .filename = NULL, } }
+ },
+{ .filename = NULL, } }
+},
+};
+
diff --git a/test/src/nffs_test_utils.c b/test/src/nffs_test_utils.c
new file mode 100644
index 0000000..fea07b8
--- /dev/null
+++ b/test/src/nffs_test_utils.c
@@ -0,0 +1,748 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "hal/hal_flash.h"
+#include "testutil/testutil.h"
+#include "fs/fs.h"
+#include "nffs/nffs.h"
+#include "nffs_test.h"
+#include "nffs_test_priv.h"
+#include "nffs_priv.h"
+
+#if 0
+#ifdef ARCH_sim
+const struct nffs_area_desc nffs_sim_area_descs[] = {
+        { 0x00000000, 16 * 1024 },
+        { 0x00004000, 16 * 1024 },
+        { 0x00008000, 16 * 1024 },
+        { 0x0000c000, 16 * 1024 },
+        { 0x00010000, 64 * 1024 },
+        { 0x00020000, 128 * 1024 },
+        { 0x00040000, 128 * 1024 },
+        { 0x00060000, 128 * 1024 },
+        { 0x00080000, 128 * 1024 },
+        { 0x000a0000, 128 * 1024 },
+        { 0x000c0000, 128 * 1024 },
+        { 0x000e0000, 128 * 1024 },
+        { 0, 0 },
+};
+#endif
+#endif
+
+void
+nffs_test_util_assert_ent_name(struct fs_dirent *dirent,
+                               const char *expected_name)
+{
+    /* It should not be necessary to initialize this array, but the libgcc
+     * version of strcmp triggers a "Conditional jump or move depends on
+     * uninitialised value(s)" valgrind warning.
+     */
+    char name[NFFS_FILENAME_MAX_LEN + 1] = { 0 };
+    uint8_t name_len;
+    int rc;
+
+    rc = fs_dirent_name(dirent, sizeof name, name, &name_len);
+    TEST_ASSERT(rc == 0);
+    if (rc == 0) {
+        TEST_ASSERT(strcmp(name, expected_name) == 0);
+    }
+}
+
+void
+nffs_test_util_assert_file_len(struct fs_file *file, uint32_t expected)
+{
+    uint32_t len;
+    int rc;
+
+    rc = fs_filelen(file, &len);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(len == expected);
+}
+
+void
+nffs_test_util_assert_cache_is_sane(const char *filename)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_cache_block *cache_block;
+    struct fs_file *fs_file;
+    struct nffs_file *file;
+    uint32_t cache_start;
+    uint32_t cache_end;
+    uint32_t block_end;
+    int rc;
+
+    rc = fs_open(filename, FS_ACCESS_READ, &fs_file);
+    TEST_ASSERT(rc == 0);
+
+    file = (struct nffs_file *)fs_file;
+    rc = nffs_cache_inode_ensure(&cache_inode, file->nf_inode_entry);
+    TEST_ASSERT(rc == 0);
+
+    nffs_cache_inode_range(cache_inode, &cache_start, &cache_end);
+
+    if (TAILQ_EMPTY(&cache_inode->nci_block_list)) {
+        TEST_ASSERT(cache_start == 0 && cache_end == 0);
+    } else {
+        block_end = 0;  /* Pacify gcc. */
+        TAILQ_FOREACH(cache_block, &cache_inode->nci_block_list, ncb_link) {
+            if (cache_block == TAILQ_FIRST(&cache_inode->nci_block_list)) {
+                TEST_ASSERT(cache_block->ncb_file_offset == cache_start);
+            } else {
+                /* Ensure no gap between this block and its predecessor. */
+                TEST_ASSERT(cache_block->ncb_file_offset == block_end);
+            }
+
+            block_end = cache_block->ncb_file_offset +
+                        cache_block->ncb_block.nb_data_len;
+            if (cache_block == TAILQ_LAST(&cache_inode->nci_block_list,
+                                          nffs_cache_block_list)) {
+
+                TEST_ASSERT(block_end == cache_end);
+            }
+        }
+    }
+
+    rc = fs_close(fs_file);
+    TEST_ASSERT(rc == 0);
+}
+
+void
+nffs_test_util_assert_contents(const char *filename, const char *contents,
+                               int contents_len)
+{
+    struct fs_file *file;
+    uint32_t bytes_read;
+    void *buf;
+    int rc;
+
+    rc = fs_open(filename, FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == 0);
+
+    buf = malloc(contents_len + 1);
+    TEST_ASSERT(buf != NULL);
+
+    rc = fs_read(file, contents_len + 1, buf, &bytes_read);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(bytes_read == contents_len);
+    TEST_ASSERT(memcmp(buf, contents, contents_len) == 0);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    free(buf);
+
+    nffs_test_util_assert_cache_is_sane(filename);
+}
+
+int
+nffs_test_util_block_count(const char *filename)
+{
+    struct nffs_hash_entry *entry;
+    struct nffs_block block;
+    struct nffs_file *file;
+    struct fs_file *fs_file;
+    int count;
+    int rc;
+
+    rc = fs_open(filename, FS_ACCESS_READ, &fs_file);
+    TEST_ASSERT(rc == 0);
+
+    file = (struct nffs_file *)fs_file;
+    count = 0;
+    entry = file->nf_inode_entry->nie_last_block_entry;
+    while (entry != NULL) {
+        count++;
+        rc = nffs_block_from_hash_entry(&block, entry);
+        TEST_ASSERT(rc == 0);
+        TEST_ASSERT(block.nb_prev != entry);
+        entry = block.nb_prev;
+    }
+
+    rc = fs_close(fs_file);
+    TEST_ASSERT(rc == 0);
+
+    return count;
+}
+
+void
+nffs_test_util_assert_block_count(const char *filename, int expected_count)
+{
+    int actual_count;
+
+    actual_count = nffs_test_util_block_count(filename);
+    TEST_ASSERT(actual_count == expected_count);
+}
+
+void
+nffs_test_util_assert_cache_range(const char *filename,
+                                 uint32_t expected_cache_start,
+                                 uint32_t expected_cache_end)
+{
+    struct nffs_cache_inode *cache_inode;
+    struct nffs_file *file;
+    struct fs_file *fs_file;
+    uint32_t cache_start;
+    uint32_t cache_end;
+    int rc;
+
+    rc = fs_open(filename, FS_ACCESS_READ, &fs_file);
+    TEST_ASSERT(rc == 0);
+
+    file = (struct nffs_file *)fs_file;
+    rc = nffs_cache_inode_ensure(&cache_inode, file->nf_inode_entry);
+    TEST_ASSERT(rc == 0);
+
+    nffs_cache_inode_range(cache_inode, &cache_start, &cache_end);
+    TEST_ASSERT(cache_start == expected_cache_start);
+    TEST_ASSERT(cache_end == expected_cache_end);
+
+    rc = fs_close(fs_file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_cache_is_sane(filename);
+}
+
+void
+nffs_test_util_create_file_blocks(const char *filename,
+                                 const struct nffs_test_block_desc *blocks,
+                                 int num_blocks)
+{
+    struct fs_file *file;
+    uint32_t total_len;
+    uint32_t offset;
+    char *buf;
+    int num_writes;
+    int rc;
+    int i;
+
+    rc = fs_open(filename, FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE, &file);
+    TEST_ASSERT(rc == 0);
+
+    total_len = 0;
+    if (num_blocks <= 0) {
+        num_writes = 1;
+    } else {
+        num_writes = num_blocks;
+    }
+    for (i = 0; i < num_writes; i++) {
+        rc = fs_write(file, blocks[i].data, blocks[i].data_len);
+        TEST_ASSERT(rc == 0);
+
+        total_len += blocks[i].data_len;
+    }
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    buf = malloc(total_len);
+    TEST_ASSERT(buf != NULL);
+
+    offset = 0;
+    for (i = 0; i < num_writes; i++) {
+        memcpy(buf + offset, blocks[i].data, blocks[i].data_len);
+        offset += blocks[i].data_len;
+    }
+    TEST_ASSERT(offset == total_len);
+
+    nffs_test_util_assert_contents(filename, buf, total_len);
+    if (num_blocks > 0) {
+        nffs_test_util_assert_block_count(filename, num_blocks);
+    }
+
+    free(buf);
+}
+
+void
+nffs_test_util_create_file(const char *filename, const char *contents,
+                           int contents_len)
+{
+    struct nffs_test_block_desc block;
+
+    block.data = contents;
+    block.data_len = contents_len;
+
+    nffs_test_util_create_file_blocks(filename, &block, 0);
+}
+
+void
+nffs_test_util_append_file(const char *filename, const char *contents,
+                           int contents_len)
+{
+    struct fs_file *file;
+    int rc;
+
+    rc = fs_open(filename, FS_ACCESS_WRITE | FS_ACCESS_APPEND, &file);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_write(file, contents, contents_len);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+}
+
+void
+nffs_test_copy_area(const struct nffs_area_desc *from,
+                    const struct nffs_area_desc *to)
+{
+    void *buf;
+    int rc;
+
+    TEST_ASSERT(from->nad_length == to->nad_length);
+
+    buf = malloc(from->nad_length);
+    TEST_ASSERT(buf != NULL);
+
+    rc = hal_flash_read(from->nad_flash_id, from->nad_offset, buf,
+                        from->nad_length);
+    TEST_ASSERT(rc == 0);
+
+    rc = hal_flash_erase(from->nad_flash_id, to->nad_offset, to->nad_length);
+    TEST_ASSERT(rc == 0);
+
+    rc = hal_flash_write(to->nad_flash_id, to->nad_offset, buf, to->nad_length);
+    TEST_ASSERT(rc == 0);
+
+    free(buf);
+}
+
+void
+nffs_test_util_create_subtree(const char *parent_path,
+                             const struct nffs_test_file_desc *elem)
+{
+    char *path;
+    int rc;
+    int i;
+
+    if (parent_path == NULL) {
+        path = malloc(1);
+        TEST_ASSERT(path != NULL);
+        path[0] = '\0';
+    } else {
+        path = malloc(strlen(parent_path) + 1 + strlen(elem->filename) + 1);
+        TEST_ASSERT(path != NULL);
+
+        sprintf(path, "%s/%s", parent_path, elem->filename);
+    }
+
+    if (elem->is_dir) {
+        if (parent_path != NULL) {
+            rc = fs_mkdir(path);
+            TEST_ASSERT(rc == 0);
+        }
+
+        if (elem->children != NULL) {
+            for (i = 0; elem->children[i].filename != NULL; i++) {
+                nffs_test_util_create_subtree(path, elem->children + i);
+            }
+        }
+    } else {
+        nffs_test_util_create_file(path, elem->contents, elem->contents_len);
+    }
+
+    free(path);
+}
+
+void
+nffs_test_util_create_tree(const struct nffs_test_file_desc *root_dir)
+{
+    nffs_test_util_create_subtree(NULL, root_dir);
+}
+
+#define NFFS_TEST_TOUCHED_ARR_SZ     (16 * 64)
+/*#define NFFS_TEST_TOUCHED_ARR_SZ     (16 * 1024)*/
+struct nffs_hash_entry
+    *nffs_test_touched_entries[NFFS_TEST_TOUCHED_ARR_SZ];
+int nffs_test_num_touched_entries;
+
+/*
+ * Recursively descend directory structure
+ */
+void
+nffs_test_assert_file(const struct nffs_test_file_desc *file,
+                     struct nffs_inode_entry *inode_entry,
+                     const char *path)
+{
+    const struct nffs_test_file_desc *child_file;
+    struct nffs_inode inode;
+    struct nffs_inode_entry *child_inode_entry;
+    char *child_path;
+    int child_filename_len;
+    int path_len;
+    int rc;
+
+    /*
+     * track of hash entries that have been examined
+     */
+    TEST_ASSERT(nffs_test_num_touched_entries < NFFS_TEST_TOUCHED_ARR_SZ);
+    nffs_test_touched_entries[nffs_test_num_touched_entries] =
+        &inode_entry->nie_hash_entry;
+    nffs_test_num_touched_entries++;
+
+    path_len = strlen(path);
+
+    rc = nffs_inode_from_entry(&inode, inode_entry);
+    TEST_ASSERT(rc == 0);
+
+    /*
+     * recursively examine each child of directory
+     */
+    if (nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) {
+        for (child_file = file->children;
+             child_file != NULL && child_file->filename != NULL;
+             child_file++) {
+
+            /*
+             * Construct full pathname for file
+             * Not null terminated
+             */
+            child_filename_len = strlen(child_file->filename);
+            child_path = malloc(path_len + 1 + child_filename_len + 1);
+            TEST_ASSERT(child_path != NULL);
+            memcpy(child_path, path, path_len);
+            child_path[path_len] = '/';
+            memcpy(child_path + path_len + 1, child_file->filename,
+                   child_filename_len);
+            child_path[path_len + 1 + child_filename_len] = '\0';
+
+            /*
+             * Verify child inode can be found using full pathname
+             */
+            rc = nffs_path_find_inode_entry(child_path, &child_inode_entry);
+            if (rc != 0) {
+                TEST_ASSERT(rc == 0);
+            }
+
+            nffs_test_assert_file(child_file, child_inode_entry, child_path);
+
+            free(child_path);
+        }
+    } else {
+        nffs_test_util_assert_contents(path, file->contents,
+                                       file->contents_len);
+    }
+}
+
+void
+nffs_test_assert_branch_touched(struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_inode_entry *child;
+    int i;
+
+    if (inode_entry == nffs_lost_found_dir) {
+        return;
+    }
+
+    for (i = 0; i < nffs_test_num_touched_entries; i++) {
+        if (nffs_test_touched_entries[i] == &inode_entry->nie_hash_entry) {
+            break;
+        }
+    }
+    TEST_ASSERT(i < nffs_test_num_touched_entries);
+    nffs_test_touched_entries[i] = NULL;
+
+    if (nffs_hash_id_is_dir(inode_entry->nie_hash_entry.nhe_id)) {
+        SLIST_FOREACH(child, &inode_entry->nie_child_list, nie_sibling_next) {
+            nffs_test_assert_branch_touched(child);
+        }
+    }
+}
+
+void
+nffs_test_assert_child_inode_present(struct nffs_inode_entry *child)
+{
+    const struct nffs_inode_entry *inode_entry;
+    const struct nffs_inode_entry *parent;
+    struct nffs_inode inode;
+    int rc;
+
+    /*
+     * Sucessfully read inode data from flash
+     */
+    rc = nffs_inode_from_entry(&inode, child);
+    TEST_ASSERT(rc == 0);
+
+    /*
+     * Validate parent
+     */
+    parent = inode.ni_parent;
+    TEST_ASSERT(parent != NULL);
+    TEST_ASSERT(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id));
+
+    /*
+     * Make sure inode is in parents child list
+     */
+    SLIST_FOREACH(inode_entry, &parent->nie_child_list, nie_sibling_next) {
+        if (inode_entry == child) {
+            return;
+        }
+    }
+
+    TEST_ASSERT(0);
+}
+
+void
+nffs_test_assert_block_present(struct nffs_hash_entry *block_entry)
+{
+    const struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *cur;
+    struct nffs_block block;
+    int rc;
+
+    /*
+     * Successfully read block data from flash
+     */
+    rc = nffs_block_from_hash_entry(&block, block_entry);
+    TEST_ASSERT(rc == 0);
+
+    /*
+     * Validate owning inode
+     */
+    inode_entry = block.nb_inode_entry;
+    TEST_ASSERT(inode_entry != NULL);
+    TEST_ASSERT(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
+
+    /*
+     * Validate that block is in owning inode's block chain
+     */
+    cur = inode_entry->nie_last_block_entry;
+    while (cur != NULL) {
+        if (cur == block_entry) {
+            return;
+        }
+
+        rc = nffs_block_from_hash_entry(&block, cur);
+        TEST_ASSERT(rc == 0);
+        cur = block.nb_prev;
+    }
+
+    TEST_ASSERT(0);
+}
+
+/*
+ * Recursively verify that the children of each directory are sorted
+ * on the directory children linked list by filename length
+ */
+void
+nffs_test_assert_children_sorted(struct nffs_inode_entry *inode_entry)
+{
+    struct nffs_inode_entry *child_entry;
+    struct nffs_inode_entry *prev_entry;
+    struct nffs_inode child_inode;
+    struct nffs_inode prev_inode;
+    int cmp;
+    int rc;
+
+    prev_entry = NULL;
+    SLIST_FOREACH(child_entry, &inode_entry->nie_child_list,
+                  nie_sibling_next) {
+        rc = nffs_inode_from_entry(&child_inode, child_entry);
+        TEST_ASSERT(rc == 0);
+
+        if (prev_entry != NULL) {
+            rc = nffs_inode_from_entry(&prev_inode, prev_entry);
+            TEST_ASSERT(rc == 0);
+
+            rc = nffs_inode_filename_cmp_flash(&prev_inode, &child_inode,
+                                               &cmp);
+            TEST_ASSERT(rc == 0);
+            TEST_ASSERT(cmp < 0);
+        }
+
+        if (nffs_hash_id_is_dir(child_entry->nie_hash_entry.nhe_id)) {
+            nffs_test_assert_children_sorted(child_entry);
+        }
+
+        prev_entry = child_entry;
+    }
+}
+
+void
+nffs_test_assert_system_once(const struct nffs_test_file_desc *root_dir)
+{
+    struct nffs_inode_entry *inode_entry;
+    struct nffs_hash_entry *entry;
+    struct nffs_hash_entry *next;
+    int i;
+
+    nffs_test_num_touched_entries = 0;
+    nffs_test_assert_file(root_dir, nffs_root_dir, "");
+    nffs_test_assert_branch_touched(nffs_root_dir);
+
+    /* Ensure no orphaned inodes or blocks. */
+    NFFS_HASH_FOREACH(entry, i, next) {
+        TEST_ASSERT(entry->nhe_flash_loc != NFFS_FLASH_LOC_NONE);
+        if (nffs_hash_id_is_inode(entry->nhe_id)) {
+            inode_entry = (void *)entry;
+            TEST_ASSERT(inode_entry->nie_refcnt == 1);
+            if (entry->nhe_id == NFFS_ID_ROOT_DIR) {
+                TEST_ASSERT(inode_entry == nffs_root_dir);
+            } else {
+                nffs_test_assert_child_inode_present(inode_entry);
+            }
+        } else {
+            nffs_test_assert_block_present(entry);
+        }
+    }
+
+    /* Ensure proper sorting. */
+    nffs_test_assert_children_sorted(nffs_root_dir);
+}
+
+void
+nffs_test_assert_system(const struct nffs_test_file_desc *root_dir,
+                        const struct nffs_area_desc *area_descs)
+{
+    int rc;
+
+    /* Ensure files are as specified, and that there are no other files or
+     * orphaned inodes / blocks.
+     */
+    nffs_test_assert_system_once(root_dir);
+
+    /* Force a garbage collection cycle. */
+    rc = nffs_gc(NULL);
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure file system is still as expected. */
+    nffs_test_assert_system_once(root_dir);
+
+    /* Clear cached data and restore from flash (i.e, simulate a reboot). */
+    rc = nffs_misc_reset();
+    TEST_ASSERT(rc == 0);
+    rc = nffs_detect(area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure file system is still as expected. */
+    nffs_test_assert_system_once(root_dir);
+}
+
+void
+nffs_test_assert_area_seqs(int seq1, int count1, int seq2, int count2)
+{
+    struct nffs_disk_area disk_area;
+    int cur1;
+    int cur2;
+    int rc;
+    int i;
+
+    cur1 = 0;
+    cur2 = 0;
+
+    for (i = 0; i < nffs_num_areas; i++) {
+        rc = nffs_flash_read(i, 0, &disk_area, sizeof disk_area);
+        TEST_ASSERT(rc == 0);
+        TEST_ASSERT(nffs_area_magic_is_set(&disk_area));
+        TEST_ASSERT(disk_area.nda_gc_seq == nffs_areas[i].na_gc_seq);
+        if (i == nffs_scratch_area_idx) {
+            TEST_ASSERT(disk_area.nda_id == NFFS_AREA_ID_NONE);
+        }
+
+        if (nffs_areas[i].na_gc_seq == seq1) {
+            cur1++;
+        } else if (nffs_areas[i].na_gc_seq == seq2) {
+            cur2++;
+        } else {
+            TEST_ASSERT(0);
+        }
+    }
+
+    TEST_ASSERT(cur1 == count1 && cur2 == count2);
+}
+
+#if 0
+void
+nffs_test_mkdir(void)
+{
+    struct fs_file *file;
+    int rc;
+
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c/d");
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_mkdir("asdf");
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    rc = fs_mkdir("/a");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c/d");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/a/b/c/d/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "a",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "b",
+                    .is_dir = 1,
+                    .children = (struct nffs_test_file_desc[]) { {
+                        .filename = "c",
+                        .is_dir = 1,
+                        .children = (struct nffs_test_file_desc[]) { {
+                            .filename = "d",
+                            .is_dir = 1,
+                            .children = (struct nffs_test_file_desc[]) { {
+                                .filename = "myfile.txt",
+                                .contents = NULL,
+                                .contents_len = 0,
+                            }, {
+                                .filename = NULL,
+                            } },
+                        }, {
+                            .filename = NULL,
+                        } },
+                    }, {
+                        .filename = NULL,
+                    } },
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
+#endif
diff --git a/test/src/nffs_test_utils.h b/test/src/nffs_test_utils.h
new file mode 100644
index 0000000..c211a2d
--- /dev/null
+++ b/test/src/nffs_test_utils.h
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#ifndef H_NFFS_TEST_UTILS_
+#define H_NFFS_TEST_UTILS_
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "hal/hal_flash.h"
+#include "testutil/testutil.h"
+#include "fs/fs.h"
+#include "nffs/nffs.h"
+#include "nffs_test.h"
+#include "nffs_test_priv.h"
+#include "nffs_priv.h"
+
+#ifdef __cplusplus
+#extern "C" {
+#endif
+
+extern struct nffs_hash_entry *nffs_test_touched_entries;
+int nffs_test_num_touched_entries;
+
+extern int flash_native_memset(uint32_t offset, uint8_t c, uint32_t len);
+
+void nffs_test_util_assert_ent_name(struct fs_dirent *dirent,
+                                    const char *expected_name);
+void nffs_test_util_assert_file_len(struct fs_file *file, uint32_t expected);
+void nffs_test_util_assert_cache_is_sane(const char *filename);
+void nffs_test_util_assert_contents(const char *filename,
+                                    const char *contents, int contents_len);
+int nffs_test_util_block_count(const char *filename);
+void nffs_test_util_assert_block_count(const char *filename,
+                                       int expected_count);
+void nffs_test_util_assert_cache_range(const char *filename,
+                                       uint32_t expected_cache_start,
+                                       uint32_t expected_cache_end);
+void nffs_test_util_create_file_blocks(const char *filename,
+                                   const struct nffs_test_block_desc *blocks,
+                                   int num_blocks);
+void nffs_test_util_create_file(const char *filename, const char *contents,
+                                int contents_len);
+void nffs_test_util_append_file(const char *filename, const char *contents,
+                                int contents_len);
+void nffs_test_copy_area(const struct nffs_area_desc *from,
+                         const struct nffs_area_desc *to);
+void nffs_test_util_create_subtree(const char *parent_path,
+                                   const struct nffs_test_file_desc *elem);
+void nffs_test_util_create_tree(const struct nffs_test_file_desc *root_dir);
+
+/*
+ * Recursively descend directory structure
+ */
+void nffs_test_assert_file(const struct nffs_test_file_desc *file,
+                           struct nffs_inode_entry *inode_entry,
+                           const char *path);
+void nffs_test_assert_branch_touched(struct nffs_inode_entry *inode_entry);
+void nffs_test_assert_child_inode_present(struct nffs_inode_entry *child);
+void nffs_test_assert_block_present(struct nffs_hash_entry *block_entry);
+/*
+ * Recursively verify that the children of each directory are sorted
+ * on the directory children linked list by filename length
+ */
+void nffs_test_assert_children_sorted(struct nffs_inode_entry *inode_entry);
+void nffs_test_assert_system_once(const struct nffs_test_file_desc *root_dir);
+void nffs_test_assert_system(const struct nffs_test_file_desc *root_dir,
+                             const struct nffs_area_desc *area_descs);
+void nffs_test_assert_area_seqs(int seq1, int count1, int seq2, int count2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_NFFS_TEST_UTILS_ */
diff --git a/test/src/testcases/append_test.c b/test/src/testcases/append_test.c
new file mode 100644
index 0000000..e82b6b9
--- /dev/null
+++ b/test/src/testcases/append_test.c
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+void process_inode_entry(struct nffs_inode_entry *inode_entry, int indent);
+
+TEST_CASE(nffs_test_append)
+{
+    struct fs_file *file;
+    uint32_t len;
+    char c;
+    int rc;
+    int i;
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE | FS_ACCESS_APPEND, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 0);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "abcdefgh", 8);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 8);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "abcdefgh", 8);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE | FS_ACCESS_APPEND, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 8);
+
+    /* File position should always be at the end of a file after an append.
+     * Seek to the middle prior to writing to test this.
+     */
+    rc = fs_seek(file, 2);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 2);
+
+    rc = fs_write(file, "ijklmnop", 8);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 16);
+    rc = fs_write(file, "qrstuvwx", 8);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 24);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt",
+                                  "abcdefghijklmnopqrstuvwx", 24);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT_FATAL(rc == 0);
+    rc = fs_open("/mydir/gaga.txt", FS_ACCESS_WRITE | FS_ACCESS_APPEND, &file);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    /*** Repeated appends to a large file. */
+    for (i = 0; i < 1000; i++) {
+        rc = fs_filelen(file, &len);
+        TEST_ASSERT_FATAL(rc == 0);
+        TEST_ASSERT(len == i);
+
+        c = '0' + i % 10;
+        rc = fs_write(file, &c, 1);
+        TEST_ASSERT_FATAL(rc == 0);
+    }
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/mydir/gaga.txt",
+        "01234567890123456789012345678901234567890123456789" /* 1 */
+        "01234567890123456789012345678901234567890123456789" /* 2 */
+        "01234567890123456789012345678901234567890123456789" /* 3 */
+        "01234567890123456789012345678901234567890123456789" /* 4 */
+        "01234567890123456789012345678901234567890123456789" /* 5 */
+        "01234567890123456789012345678901234567890123456789" /* 6 */
+        "01234567890123456789012345678901234567890123456789" /* 7 */
+        "01234567890123456789012345678901234567890123456789" /* 8 */
+        "01234567890123456789012345678901234567890123456789" /* 9 */
+        "01234567890123456789012345678901234567890123456789" /* 10 */
+        "01234567890123456789012345678901234567890123456789" /* 11 */
+        "01234567890123456789012345678901234567890123456789" /* 12 */
+        "01234567890123456789012345678901234567890123456789" /* 13 */
+        "01234567890123456789012345678901234567890123456789" /* 14 */
+        "01234567890123456789012345678901234567890123456789" /* 15 */
+        "01234567890123456789012345678901234567890123456789" /* 16 */
+        "01234567890123456789012345678901234567890123456789" /* 17 */
+        "01234567890123456789012345678901234567890123456789" /* 18 */
+        "01234567890123456789012345678901234567890123456789" /* 19 */
+        "01234567890123456789012345678901234567890123456789" /* 20 */
+        ,
+        1000);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "abcdefghijklmnopqrstuvwx",
+                .contents_len = 24,
+            }, {
+                .filename = "mydir",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "gaga.txt",
+                    .contents =
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    "01234567890123456789012345678901234567890123456789"
+    ,
+                    .contents_len = 1000,
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/cache_large_file_test.c b/test/src/testcases/cache_large_file_test.c
new file mode 100644
index 0000000..44e1232
--- /dev/null
+++ b/test/src/testcases/cache_large_file_test.c
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_cache_large_file)
+{
+    static char data[NFFS_BLOCK_MAX_DATA_SZ_MAX * 5];
+    struct fs_file *file;
+    uint8_t b;
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/myfile.txt", data, sizeof data);
+    nffs_cache_clear();
+
+    /* Opening a file should not cause any blocks to get cached. */
+    rc = fs_open("/myfile.txt", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt", 0, 0);
+
+    /* Cache first block. */
+    rc = fs_seek(file, nffs_block_max_data_sz * 0);
+    TEST_ASSERT(rc == 0);
+    rc = fs_read(file, 1, &b, NULL);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt",
+                                     nffs_block_max_data_sz * 0,
+                                     nffs_block_max_data_sz * 1);
+
+    /* Cache second block. */
+    rc = fs_seek(file, nffs_block_max_data_sz * 1);
+    TEST_ASSERT(rc == 0);
+    rc = fs_read(file, 1, &b, NULL);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt",
+                                     nffs_block_max_data_sz * 0,
+                                     nffs_block_max_data_sz * 2);
+
+
+    /* Cache fourth block; prior cache should get erased. */
+    rc = fs_seek(file, nffs_block_max_data_sz * 3);
+    TEST_ASSERT(rc == 0);
+    rc = fs_read(file, 1, &b, NULL);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt",
+                                     nffs_block_max_data_sz * 3,
+                                     nffs_block_max_data_sz * 4);
+
+    /* Cache second and third blocks. */
+    rc = fs_seek(file, nffs_block_max_data_sz * 1);
+    TEST_ASSERT(rc == 0);
+    rc = fs_read(file, 1, &b, NULL);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt",
+                                     nffs_block_max_data_sz * 1,
+                                     nffs_block_max_data_sz * 4);
+
+    /* Cache fifth block. */
+    rc = fs_seek(file, nffs_block_max_data_sz * 4);
+    TEST_ASSERT(rc == 0);
+    rc = fs_read(file, 1, &b, NULL);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_cache_range("/myfile.txt",
+                                     nffs_block_max_data_sz * 1,
+                                     nffs_block_max_data_sz * 5);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+}
diff --git a/test/src/testcases/corrupt_block_test.c b/test/src/testcases/corrupt_block_test.c
new file mode 100644
index 0000000..1f4bc3b
--- /dev/null
+++ b/test/src/testcases/corrupt_block_test.c
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_corrupt_block)
+{
+    struct nffs_block block;
+    struct fs_file *fs_file;
+    struct nffs_file *file;
+    uint32_t flash_offset;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    uint8_t off;    /* offset to corrupt */
+    int rc;
+    struct nffs_disk_block ndb;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/mydir/a", "aaaa", 4);
+    nffs_test_util_create_file("/mydir/b", "bbbb", 4);
+    nffs_test_util_create_file("/mydir/c", "cccc", 4);
+
+    /* Add a second block to the 'b' file. */
+    nffs_test_util_append_file("/mydir/b", "1234", 4);
+
+    /* Corrupt the 'b' file; overwrite the second block's magic number. */
+    rc = fs_open("/mydir/b", FS_ACCESS_READ, &fs_file);
+    TEST_ASSERT(rc == 0);
+    file = (struct nffs_file *)fs_file;
+
+    rc = nffs_block_from_hash_entry(&block,
+                                   file->nf_inode_entry->nie_last_block_entry);
+    TEST_ASSERT(rc == 0);
+
+    nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &area_idx,
+                         &area_offset);
+    flash_offset = nffs_areas[area_idx].na_offset + area_offset;
+
+    /*
+     * Overwriting the reserved16 field should invalidate the CRC
+     */
+    off = (char*)&ndb.reserved16 - (char*)&ndb;
+    rc = flash_native_memset(flash_offset + off, 0x43, 1);
+
+    TEST_ASSERT(rc == 0);
+
+    /* Write a fourth file. This file should get restored even though the
+     * previous object has an invalid magic number.
+     */
+    nffs_test_util_create_file("/mydir/d", "dddd", 4);
+
+    rc = nffs_misc_reset();
+    TEST_ASSERT(rc == 0);
+    rc = nffs_detect(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /* The entire second block should be removed; the file should only contain
+     * the first block.
+     */
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "mydir",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "a",
+                    .contents = "aaaa",
+                    .contents_len = 4,
+#if 0
+                /*
+                 * In the newer implementation without the find_file_ends
+                 * corrupted inodes are deleted rather than retained with
+                 * partial contents
+                 */
+                }, {
+                    .filename = "b",
+                    .contents = "bbbb",
+                    .contents_len = 4,
+#endif
+                }, {
+                    .filename = "c",
+                    .contents = "cccc",
+                    .contents_len = 4,
+                }, {
+                    .filename = "d",
+                    .contents = "dddd",
+                    .contents_len = 4,
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/corrupt_scratch_test.c b/test/src/testcases/corrupt_scratch_test.c
new file mode 100644
index 0000000..9f5d6b5
--- /dev/null
+++ b/test/src/testcases/corrupt_scratch_test.c
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_corrupt_scratch)
+{
+    int non_scratch_id;
+    int scratch_id;
+    int rc;
+
+    static const struct nffs_area_desc area_descs_two[] = {
+        { 0x00020000, 128 * 1024 },
+        { 0x00040000, 128 * 1024 },
+        { 0, 0 },
+    };
+    nffs_current_area_descs = (struct nffs_area_desc*)area_descs_two;
+
+    /*** Setup. */
+    rc = nffs_format(area_descs_two);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/myfile.txt", "contents", 8);
+
+    /* Copy the current contents of the non-scratch area to the scratch area.
+     * This will make the scratch area look like it only partially
+     * participated in a garbage collection cycle.
+     */
+    scratch_id = nffs_scratch_area_idx;
+    non_scratch_id = scratch_id ^ 1;
+    nffs_test_copy_area(area_descs_two + non_scratch_id,
+                       area_descs_two + nffs_scratch_area_idx);
+
+    /* Add some more data to the non-scratch area. */
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure the file system is successfully detected and valid, despite
+     * corruption.
+     */
+
+    rc = nffs_misc_reset();
+    TEST_ASSERT(rc == 0);
+
+    rc = nffs_detect(area_descs_two);
+    TEST_ASSERT(rc == 0);
+
+    TEST_ASSERT(nffs_scratch_area_idx == scratch_id);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "mydir",
+                .is_dir = 1,
+            }, {
+                .filename = "myfile.txt",
+                .contents = "contents",
+                .contents_len = 8,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, area_descs_two);
+}
diff --git a/test/src/testcases/gc_on_oom_test.c b/test/src/testcases/gc_on_oom_test.c
new file mode 100644
index 0000000..4d1b733
--- /dev/null
+++ b/test/src/testcases/gc_on_oom_test.c
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_gc_on_oom)
+{
+    int rc;
+
+    /*** Setup. */
+    /* Ensure all areas are the same size. */
+    static const struct nffs_area_desc area_descs_two[] = {
+            { 0x00000000, 16 * 1024 },
+            { 0x00004000, 16 * 1024 },
+            { 0x00008000, 16 * 1024 },
+            { 0, 0 },
+    };
+
+    rc = nffs_format(area_descs_two);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    /* Leak block entries until only four are left. */
+    /* XXX: This is ridiculous.  Need to fix nffs configuration so that the
+     * caller passes a config object rather than writing to a global variable.
+     */
+    while (nffs_block_entry_pool.mp_num_free != 4) {
+        nffs_block_entry_alloc();
+    }
+
+    /*** Write 4 data blocks. */
+    struct nffs_test_block_desc blocks[4] = { {
+        .data = "1",
+        .data_len = 1,
+    }, {
+        .data = "2",
+        .data_len = 1,
+    }, {
+        .data = "3",
+        .data_len = 1,
+    }, {
+        .data = "4",
+        .data_len = 1,
+    } };
+
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 4);
+
+    TEST_ASSERT_FATAL(nffs_block_entry_pool.mp_num_free == 0);
+
+    /* Attempt another one-byte write.  This should trigger a garbage
+     * collection cycle, resulting in the four blocks being collated.  The
+     * fifth write consumes an additional block, resulting in 2 out of 4 blocks
+     * in use.
+     */
+    nffs_test_util_append_file("/myfile.txt", "5", 1);
+
+    TEST_ASSERT_FATAL(nffs_block_entry_pool.mp_num_free == 2);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "12345",
+                .contents_len = 5,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, area_descs_two);
+}
diff --git a/test/src/testcases/gc_test.c b/test/src/testcases/gc_test.c
new file mode 100644
index 0000000..49a77a6
--- /dev/null
+++ b/test/src/testcases/gc_test.c
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_gc)
+{
+    int rc;
+
+    static const struct nffs_area_desc area_descs_two[] = {
+        { 0x00020000, 128 * 1024 },
+        { 0x00040000, 128 * 1024 },
+        { 0, 0 },
+    };
+
+    struct nffs_test_block_desc blocks[8] = { {
+        .data = "1",
+        .data_len = 1,
+    }, {
+        .data = "2",
+        .data_len = 1,
+    }, {
+        .data = "3",
+        .data_len = 1,
+    }, {
+        .data = "4",
+        .data_len = 1,
+    }, {
+        .data = "5",
+        .data_len = 1,
+    }, {
+        .data = "6",
+        .data_len = 1,
+    }, {
+        .data = "7",
+        .data_len = 1,
+    }, {
+        .data = "8",
+        .data_len = 1,
+    } };
+
+
+    rc = nffs_format(area_descs_two);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 8);
+
+    nffs_gc(NULL);
+
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+}
diff --git a/test/src/testcases/incomplete_block_test.c b/test/src/testcases/incomplete_block_test.c
new file mode 100644
index 0000000..33fa9c3
--- /dev/null
+++ b/test/src/testcases/incomplete_block_test.c
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+/*
+ * This test no longer works with the current implementation. The
+ * expectation is that intermediate blocks can be removed and the old
+ * method of finding the last current block after restore will allow the
+ * file to be salvaged. Instead, the file should be removed and all data
+ * declared invalid.
+ */
+TEST_CASE(nffs_test_incomplete_block)
+{
+    struct nffs_block block;
+    struct fs_file *fs_file;
+    struct nffs_file *file;
+    uint32_t flash_offset;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/mydir/a", "aaaa", 4);
+    nffs_test_util_create_file("/mydir/b", "bbbb", 4);
+    nffs_test_util_create_file("/mydir/c", "cccc", 4);
+
+    /* Add a second block to the 'b' file. */
+    nffs_test_util_append_file("/mydir/b", "1234", 4);
+
+    /* Corrupt the 'b' file; make it look like the second block only got half
+     * written.
+     */
+    rc = fs_open("/mydir/b", FS_ACCESS_READ, &fs_file);
+    TEST_ASSERT(rc == 0);
+    file = (struct nffs_file *)fs_file;
+
+    rc = nffs_block_from_hash_entry(&block,
+                                   file->nf_inode_entry->nie_last_block_entry);
+    TEST_ASSERT(rc == 0);
+
+    nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &area_idx,
+                         &area_offset);
+    flash_offset = nffs_areas[area_idx].na_offset + area_offset;
+    /*
+     * Overwrite block data - the CRC check should pick this up
+     */
+    rc = flash_native_memset(
+            flash_offset + sizeof (struct nffs_disk_block) + 2, 0xff, 2);
+    TEST_ASSERT(rc == 0);
+
+    rc = nffs_misc_reset();
+    TEST_ASSERT(rc == 0);
+    rc = nffs_detect(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /* OLD: The entire second block should be removed; the file should only
+     * contain the first block.
+     * Unless we can salvage the block, the entire file should probably be
+     * removed. This is a contrived example which generates bad data on the
+     * what happens to be the last block, but corruption can actually occur
+     * in any block. Sweep should be updated to search look for blocks that
+     * don't have a correct prev_id and then decide whether to delete the
+     * owning inode. XXX
+     */
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "mydir",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "a",
+                    .contents = "aaaa",
+                    .contents_len = 4,
+#if 0
+/* keep this out until sweep updated to capture bad blocks XXX */
+                }, {
+                    .filename = "b",
+                    .contents = "bbbb",
+                    .contents_len = 4,
+#endif
+                }, {
+                    .filename = "c",
+                    .contents = "cccc",
+                    .contents_len = 4,
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/large_system_test.c b/test/src/testcases/large_system_test.c
new file mode 100644
index 0000000..fc0fad1
--- /dev/null
+++ b/test/src/testcases/large_system_test.c
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_large_system)
+{
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_create_tree(nffs_test_system_01);
+
+    nffs_test_assert_system(nffs_test_system_01, nffs_current_area_descs);
+
+    rc = fs_unlink("/lvl1dir-0000");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_unlink("/lvl1dir-0004");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/lvl1dir-0000");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_assert_system(nffs_test_system_01_rm_1014_mk10, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/large_unlink_test.c b/test/src/testcases/large_unlink_test.c
new file mode 100644
index 0000000..4805ff4
--- /dev/null
+++ b/test/src/testcases/large_unlink_test.c
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_large_unlink)
+{
+    /* It should not be necessary to initialize this array, but the libgcc
+     * version of strcmp triggers a "Conditional jump or move depends on
+     * uninitialised value(s)" valgrind warning.
+     */
+    char filename[256] = { 0 };
+    int rc;
+    int i;
+    int j;
+    int k;
+
+    static char file_contents[1024 * 4];
+
+    /*** Setup. */
+    nffs_config.nc_num_inodes = 1024;
+    nffs_config.nc_num_blocks = 1024;
+
+    rc = nffs_init();
+    TEST_ASSERT(rc == 0);
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    for (i = 0; i < 5; i++) {
+        snprintf(filename, sizeof filename, "/dir0_%d", i);
+        rc = fs_mkdir(filename);
+        TEST_ASSERT(rc == 0);
+
+        for (j = 0; j < 5; j++) {
+            snprintf(filename, sizeof filename, "/dir0_%d/dir1_%d", i, j);
+            rc = fs_mkdir(filename);
+            TEST_ASSERT(rc == 0);
+
+            for (k = 0; k < 5; k++) {
+                snprintf(filename, sizeof filename,
+                         "/dir0_%d/dir1_%d/file2_%d", i, j, k);
+                nffs_test_util_create_file(filename, file_contents,
+                                          sizeof file_contents);
+            }
+        }
+
+        for (j = 0; j < 15; j++) {
+            snprintf(filename, sizeof filename, "/dir0_%d/file1_%d", i, j);
+            nffs_test_util_create_file(filename, file_contents,
+                                      sizeof file_contents);
+        }
+    }
+
+    for (i = 0; i < 5; i++) {
+        snprintf(filename, sizeof filename, "/dir0_%d", i);
+        rc = fs_unlink(filename);
+        TEST_ASSERT(rc == 0);
+    }
+
+    /* The entire file system should be empty. */
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/large_write_test.c b/test/src/testcases/large_write_test.c
new file mode 100644
index 0000000..3bf3a02
--- /dev/null
+++ b/test/src/testcases/large_write_test.c
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_large_write)
+{
+    static char data[NFFS_BLOCK_MAX_DATA_SZ_MAX * 5];
+    int rc;
+    int i;
+
+    static const struct nffs_area_desc area_descs_two[] = {
+        { 0x00020000, 128 * 1024 },
+        { 0x00040000, 128 * 1024 },
+        { 0, 0 },
+    };
+
+    /*** Setup. */
+    rc = nffs_format(area_descs_two);
+    TEST_ASSERT(rc == 0);
+
+    for (i = 0; i < sizeof data; i++) {
+        data[i] = i;
+    }
+
+    nffs_test_util_create_file("/myfile.txt", data, sizeof data);
+
+    /* Ensure large write was split across the appropriate number of data
+     * blocks.
+     */
+    TEST_ASSERT(nffs_test_util_block_count("/myfile.txt") ==
+           sizeof data / NFFS_BLOCK_MAX_DATA_SZ_MAX);
+
+    /* Garbage collect and then ensure the large file is still properly divided
+     * according to max data block size.
+     */
+    nffs_gc(NULL);
+    TEST_ASSERT(nffs_test_util_block_count("/myfile.txt") ==
+           sizeof data / NFFS_BLOCK_MAX_DATA_SZ_MAX);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = data,
+                .contents_len = sizeof data,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, area_descs_two);
+}
diff --git a/test/src/testcases/long_filename_test.c b/test/src/testcases/long_filename_test.c
new file mode 100644
index 0000000..0afc99a
--- /dev/null
+++ b/test/src/testcases/long_filename_test.c
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_long_filename)
+{
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/12345678901234567890.txt", "contents", 8);
+
+    rc = fs_mkdir("/longdir12345678901234567890");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_rename("/12345678901234567890.txt",
+                    "/longdir12345678901234567890/12345678901234567890.txt");
+    TEST_ASSERT(rc == 0);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "longdir12345678901234567890",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "/12345678901234567890.txt",
+                    .contents = "contents",
+                    .contents_len = 8,
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/lost_found_test.c b/test/src/testcases/lost_found_test.c
new file mode 100644
index 0000000..5accb74
--- /dev/null
+++ b/test/src/testcases/lost_found_test.c
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_lost_found)
+{
+    char buf[32];
+    struct nffs_inode_entry *inode_entry;
+    uint32_t flash_offset;
+    uint32_t area_offset;
+    uint8_t area_idx;
+    int rc;
+    struct nffs_disk_inode ndi;
+    uint8_t off;    /* calculated offset for memset */
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+    rc = fs_mkdir("/mydir/dir1");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/mydir/file1", "aaaa", 4);
+    nffs_test_util_create_file("/mydir/dir1/file2", "bbbb", 4);
+
+    /* Corrupt the mydir inode. */
+    rc = nffs_path_find_inode_entry("/mydir", &inode_entry);
+    TEST_ASSERT(rc == 0);
+
+    snprintf(buf, sizeof buf, "%lu",
+             (unsigned long)inode_entry->nie_hash_entry.nhe_id);
+
+    nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
+                         &area_idx, &area_offset);
+    flash_offset = nffs_areas[area_idx].na_offset + area_offset;
+    /*
+     * Overwrite the sequence number - should be detected as CRC corruption
+     */
+    off = (char*)&ndi.ndi_seq - (char*)&ndi;
+    rc = flash_native_memset(flash_offset + off, 0xaa, 1);
+    TEST_ASSERT(rc == 0);
+
+    /* Clear cached data and restore from flash (i.e, simulate a reboot). */
+    rc = nffs_misc_reset();
+    TEST_ASSERT(rc == 0);
+    rc = nffs_detect(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /* All contents should now be in the lost+found dir. */
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "lost+found",
+                .is_dir = 1,
+#if 0
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = buf,
+                    .is_dir = 1,
+                    .children = (struct nffs_test_file_desc[]) { {
+                        .filename = "file1",
+                        .contents = "aaaa",
+                        .contents_len = 4,
+                    }, {
+                        .filename = "dir1",
+                        .is_dir = 1,
+                        .children = (struct nffs_test_file_desc[]) { {
+                            .filename = "file2",
+                            .contents = "bbbb",
+                            .contents_len = 4,
+                        }, {
+                            .filename = NULL,
+                        } },
+                    }, {
+                        .filename = NULL,
+                    } },
+                }, {
+                    .filename = NULL,
+                } },
+#endif
+            }, {
+                .filename = NULL,
+            } }
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/many_children_test.c b/test/src/testcases/many_children_test.c
new file mode 100644
index 0000000..7cf2b8c
--- /dev/null
+++ b/test/src/testcases/many_children_test.c
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_many_children)
+{
+    int rc;
+
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/zasdf", NULL, 0);
+    nffs_test_util_create_file("/FfD", NULL, 0);
+    nffs_test_util_create_file("/4Zvv", NULL, 0);
+    nffs_test_util_create_file("/*(*2fs", NULL, 0);
+    nffs_test_util_create_file("/pzzd", NULL, 0);
+    nffs_test_util_create_file("/zasdf0", NULL, 0);
+    nffs_test_util_create_file("/23132.bin", NULL, 0);
+    nffs_test_util_create_file("/asldkfjaldskfadsfsdf.txt", NULL, 0);
+    nffs_test_util_create_file("/sdgaf", NULL, 0);
+    nffs_test_util_create_file("/939302**", NULL, 0);
+    rc = fs_mkdir("/dir");
+    nffs_test_util_create_file("/dir/itw82", NULL, 0);
+    nffs_test_util_create_file("/dir/124", NULL, 0);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) {
+                { "zasdf" },
+                { "FfD" },
+                { "4Zvv" },
+                { "*(*2fs" },
+                { "pzzd" },
+                { "zasdf0" },
+                { "23132.bin" },
+                { "asldkfjaldskfadsfsdf.txt" },
+                { "sdgaf" },
+                { "939302**" },
+                {
+                    .filename = "dir",
+                    .is_dir = 1,
+                    .children = (struct nffs_test_file_desc[]) {
+                        { "itw82" },
+                        { "124" },
+                        { NULL },
+                    },
+                },
+                { NULL },
+            }
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/mkdir_test.c b/test/src/testcases/mkdir_test.c
new file mode 100644
index 0000000..f69a161
--- /dev/null
+++ b/test/src/testcases/mkdir_test.c
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_mkdir)
+{
+    struct fs_file *file;
+    int rc;
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c/d");
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_mkdir("asdf");
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    rc = fs_mkdir("/a");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/a/b/c/d");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/a/b/c/d/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "a",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "b",
+                    .is_dir = 1,
+                    .children = (struct nffs_test_file_desc[]) { {
+                        .filename = "c",
+                        .is_dir = 1,
+                        .children = (struct nffs_test_file_desc[]) { {
+                            .filename = "d",
+                            .is_dir = 1,
+                            .children = (struct nffs_test_file_desc[]) { {
+                                .filename = "myfile.txt",
+                                .contents = NULL,
+                                .contents_len = 0,
+                            }, {
+                                .filename = NULL,
+                            } },
+                        }, {
+                            .filename = NULL,
+                        } },
+                    }, {
+                        .filename = NULL,
+                    } },
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/open_test.c b/test/src/testcases/open_test.c
new file mode 100644
index 0000000..7edbe6c
--- /dev/null
+++ b/test/src/testcases/open_test.c
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_open)
+{
+    struct fs_file *file;
+    struct fs_dir *dir;
+    int rc;
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /*** Fail to open an invalid path (not rooted). */
+    rc = fs_open("file", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    /*** Fail to open a directory (root directory). */
+    rc = fs_open("/", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    /*** Fail to open a nonexistent file for reading. */
+    rc = fs_open("/1234", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    /*** Fail to open a child of a nonexistent directory. */
+    rc = fs_open("/dir/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == FS_ENOENT);
+    rc = fs_opendir("/dir", &dir);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_mkdir("/dir");
+    TEST_ASSERT(rc == 0);
+
+    /*** Fail to open a directory. */
+    rc = fs_open("/dir", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    /*** Successfully open an existing file for reading. */
+    nffs_test_util_create_file("/dir/file.txt", "1234567890", 10);
+    rc = fs_open("/dir/file.txt", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == 0);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    /*** Successfully open an nonexistent file for writing. */
+    rc = fs_open("/dir/file2.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    /*** Ensure the file can be reopened. */
+    rc = fs_open("/dir/file.txt", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == 0);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+}
diff --git a/test/src/testcases/overwrite_many_test.c b/test/src/testcases/overwrite_many_test.c
new file mode 100644
index 0000000..e1e52b3
--- /dev/null
+++ b/test/src/testcases/overwrite_many_test.c
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_overwrite_many)
+{
+    struct nffs_test_block_desc *blocks = (struct nffs_test_block_desc[]) { {
+        .data = "abcdefgh",
+        .data_len = 8,
+    }, {
+        .data = "ijklmnop",
+        .data_len = 8,
+    }, {
+        .data = "qrstuvwx",
+        .data_len = 8,
+    } };
+
+    struct fs_file *file;
+    int rc;
+
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /*** Overwrite middle of first block. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 3);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 3);
+
+    rc = fs_write(file, "12", 2);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 5);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "abc12fghijklmnopqrstuvwx", 24);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    /*** Overwrite end of first block, start of second. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234", 4);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 10);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "abcdef1234klmnopqrstuvwx", 24);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "abcdef1234klmnopqrstuvwx",
+                .contents_len = 24,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/overwrite_one_test.c b/test/src/testcases/overwrite_one_test.c
new file mode 100644
index 0000000..bf25036
--- /dev/null
+++ b/test/src/testcases/overwrite_one_test.c
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_overwrite_one)
+{
+    struct fs_file *file;
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_append_file("/myfile.txt", "abcdefgh", 8);
+
+    /*** Overwrite within one block (middle). */
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 3);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 3);
+
+    rc = fs_write(file, "12", 2);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 5);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "abc12fgh", 8);
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+
+    /*** Overwrite within one block (start). */
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "xy", 2);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 2);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "xyc12fgh", 8);
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+
+    /*** Overwrite within one block (end). */
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "<>", 2);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 8);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "xyc12f<>", 8);
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+
+    /*** Overwrite one block middle, extend. */
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 4);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 4);
+
+    rc = fs_write(file, "abcdefgh", 8);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 12);
+    TEST_ASSERT(fs_getpos(file) == 12);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "xyc1abcdefgh", 12);
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+
+    /*** Overwrite one block start, extend. */
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 12);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "abcdefghijklmnop", 16);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 16);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "abcdefghijklmnop", 16);
+    nffs_test_util_assert_block_count("/myfile.txt", 1);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "abcdefghijklmnop",
+                .contents_len = 16,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/overwrite_three_test.c b/test/src/testcases/overwrite_three_test.c
new file mode 100644
index 0000000..3e11824
--- /dev/null
+++ b/test/src/testcases/overwrite_three_test.c
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_overwrite_three)
+{
+    struct nffs_test_block_desc *blocks = (struct nffs_test_block_desc[]) { {
+        .data = "abcdefgh",
+        .data_len = 8,
+    }, {
+        .data = "ijklmnop",
+        .data_len = 8,
+    }, {
+        .data = "qrstuvwx",
+        .data_len = 8,
+    } };
+
+    struct fs_file *file;
+    int rc;
+
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /*** Overwrite three blocks (middle). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234567890!@", 12);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 18);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "abcdef1234567890!@stuvwx", 24);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    /*** Overwrite three blocks (start). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "1234567890!@#$%^&*()", 20);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 20);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "1234567890!@#$%^&*()uvwx", 24);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    /*** Overwrite three blocks (end). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234567890!@#$%^&*", 18);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 24);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "abcdef1234567890!@#$%^&*", 24);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    /*** Overwrite three blocks middle, extend. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234567890!@#$%^&*()", 20);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 26);
+    TEST_ASSERT(fs_getpos(file) == 26);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "abcdef1234567890!@#$%^&*()", 26);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    /*** Overwrite three blocks start, extend. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 3);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 24);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "1234567890!@#$%^&*()abcdefghij", 30);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 30);
+    TEST_ASSERT(fs_getpos(file) == 30);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt",
+                                   "1234567890!@#$%^&*()abcdefghij", 30);
+    nffs_test_util_assert_block_count("/myfile.txt", 3);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "1234567890!@#$%^&*()abcdefghij",
+                .contents_len = 30,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/overwrite_two_test.c b/test/src/testcases/overwrite_two_test.c
new file mode 100644
index 0000000..259cc3d
--- /dev/null
+++ b/test/src/testcases/overwrite_two_test.c
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_overwrite_two)
+{
+    struct nffs_test_block_desc *blocks = (struct nffs_test_block_desc[]) { {
+        .data = "abcdefgh",
+        .data_len = 8,
+    }, {
+        .data = "ijklmnop",
+        .data_len = 8,
+    } };
+
+    struct fs_file *file;
+    int rc;
+
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    /*** Overwrite two blocks (middle). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 2);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 7);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 7);
+
+    rc = fs_write(file, "123", 3);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 10);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt", "abcdefg123klmnop", 16);
+    nffs_test_util_assert_block_count("/myfile.txt", 2);
+
+    /*** Overwrite two blocks (start). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 2);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "ABCDEFGHIJ", 10);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 10);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt", "ABCDEFGHIJklmnop", 16);
+    nffs_test_util_assert_block_count("/myfile.txt", 2);
+
+    /*** Overwrite two blocks (end). */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 2);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234567890", 10);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 16);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt", "abcdef1234567890", 16);
+    nffs_test_util_assert_block_count("/myfile.txt", 2);
+
+    /*** Overwrite two blocks middle, extend. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 2);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_seek(file, 6);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 6);
+
+    rc = fs_write(file, "1234567890!@#$", 14);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 20);
+    TEST_ASSERT(fs_getpos(file) == 20);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt", "abcdef1234567890!@#$", 20);
+    nffs_test_util_assert_block_count("/myfile.txt", 2);
+
+    /*** Overwrite two blocks start, extend. */
+    nffs_test_util_create_file_blocks("/myfile.txt", blocks, 2);
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 16);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "1234567890!@#$%^&*()", 20);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 20);
+    TEST_ASSERT(fs_getpos(file) == 20);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents( "/myfile.txt", "1234567890!@#$%^&*()", 20);
+    nffs_test_util_assert_block_count("/myfile.txt", 2);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "1234567890!@#$%^&*()",
+                .contents_len = 20,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/read_test.c b/test/src/testcases/read_test.c
new file mode 100644
index 0000000..28738e9
--- /dev/null
+++ b/test/src/testcases/read_test.c
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_read)
+{
+    struct fs_file *file;
+    uint8_t buf[16];
+    uint32_t bytes_read;
+    int rc;
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_create_file("/myfile.txt", "1234567890", 10);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 10);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_read(file, 4, buf, &bytes_read);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(bytes_read == 4);
+    TEST_ASSERT(memcmp(buf, "1234", 4) == 0);
+    TEST_ASSERT(fs_getpos(file) == 4);
+
+    rc = fs_read(file, sizeof buf - 4, buf + 4, &bytes_read);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(bytes_read == 6);
+    TEST_ASSERT(memcmp(buf, "1234567890", 10) == 0);
+    TEST_ASSERT(fs_getpos(file) == 10);
+
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+}
diff --git a/test/src/testcases/readdir_test.c b/test/src/testcases/readdir_test.c
new file mode 100644
index 0000000..2faabe0
--- /dev/null
+++ b/test/src/testcases/readdir_test.c
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_readdir)
+{
+    struct fs_dirent *dirent;
+    struct fs_dir *dir;
+    int rc;
+
+    /*** Setup. */
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT_FATAL(rc == 0);
+
+    nffs_test_util_create_file("/mydir/b", "bbbb", 4);
+    nffs_test_util_create_file("/mydir/a", "aaaa", 4);
+    rc = fs_mkdir("/mydir/c");
+    TEST_ASSERT_FATAL(rc == 0);
+
+    /* Nonexistent directory. */
+    rc = fs_opendir("/asdf", &dir);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    /* Fail to opendir a file. */
+    rc = fs_opendir("/mydir/a", &dir);
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    /* Real directory (with trailing slash). */
+    rc = fs_opendir("/mydir/", &dir);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_ent_name(dirent, "a");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 0);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_ent_name(dirent, "b");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 0);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_ent_name(dirent, "c");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 1);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_closedir(dir);
+    TEST_ASSERT(rc == 0);
+
+    /* Root directory. */
+    rc = fs_opendir("/", &dir);
+    TEST_ASSERT(rc == 0);
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_ent_name(dirent, "lost+found");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 1);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_ent_name(dirent, "mydir");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 1);
+
+    rc = fs_closedir(dir);
+    TEST_ASSERT(rc == 0);
+
+    /* Delete entries while iterating. */
+    rc = fs_opendir("/mydir", &dir);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_ent_name(dirent, "a");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 0);
+
+    rc = fs_unlink("/mydir/b");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_unlink("/mydir/c");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_unlink("/mydir");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_ent_name(dirent, "c");
+    TEST_ASSERT(fs_dirent_is_dir(dirent) == 1);
+
+    rc = fs_readdir(dir, &dirent);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_closedir(dir);
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure directory is gone. */
+    rc = fs_opendir("/mydir", &dir);
+    TEST_ASSERT(rc == FS_ENOENT);
+}
diff --git a/test/src/testcases/rename_test.c b/test/src/testcases/rename_test.c
new file mode 100644
index 0000000..e925615
--- /dev/null
+++ b/test/src/testcases/rename_test.c
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_rename)
+{
+    struct fs_file *file;
+    const char contents[] = "contents";
+    int rc;
+
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_rename("/nonexistent.txt", "/newname.txt");
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    /*** Rename file. */
+    nffs_test_util_create_file("/myfile.txt", contents, sizeof contents);
+
+    rc = fs_rename("/myfile.txt", "badname");
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    rc = fs_rename("/myfile.txt", "/myfile2.txt");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_READ, &file);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    nffs_test_util_assert_contents("/myfile2.txt", contents, sizeof contents);
+
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_mkdir("/mydir/leafdir");
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_rename("/myfile2.txt", "/mydir/myfile2.txt");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/mydir/myfile2.txt", contents,
+                                  sizeof contents);
+
+    /*** Rename directory. */
+    rc = fs_rename("/mydir", "badname");
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    /* Don't allow a directory to be moved into a descendent directory. */
+    rc = fs_rename("/mydir", "/mydir/leafdir/a");
+    TEST_ASSERT(rc == FS_EINVAL);
+
+    rc = fs_rename("/mydir", "/mydir2");
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/mydir2/myfile2.txt", contents,
+                                  sizeof contents);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "mydir2",
+                .is_dir = 1,
+                .children = (struct nffs_test_file_desc[]) { {
+                    .filename = "leafdir",
+                    .is_dir = 1,
+                }, {
+                    .filename = "myfile2.txt",
+                    .contents = "contents",
+                    .contents_len = 9,
+                }, {
+                    .filename = NULL,
+                } },
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/split_file_test.c b/test/src/testcases/split_file_test.c
new file mode 100644
index 0000000..137d961
--- /dev/null
+++ b/test/src/testcases/split_file_test.c
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_split_file)
+{
+    static char data[24 * 1024];
+    int rc;
+    int i;
+
+    /*** Setup. */
+    static const struct nffs_area_desc area_descs_two[] = {
+            { 0x00000000, 16 * 1024 },
+            { 0x00004000, 16 * 1024 },
+            { 0x00008000, 16 * 1024 },
+            { 0, 0 },
+    };
+
+    rc = nffs_format(area_descs_two);
+    TEST_ASSERT(rc == 0);
+
+    for (i = 0; i < sizeof data; i++) {
+        data[i] = i;
+    }
+
+    for (i = 0; i < 256; i++) {
+        nffs_test_util_create_file("/myfile.txt", data, sizeof data);
+        rc = fs_unlink("/myfile.txt");
+        TEST_ASSERT(rc == 0);
+    }
+
+    nffs_test_util_create_file("/myfile.txt", data, sizeof data);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = data,
+                .contents_len = sizeof data,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, area_descs_two);
+}
diff --git a/test/src/testcases/truncate_test.c b/test/src/testcases/truncate_test.c
new file mode 100644
index 0000000..d2b6bf2
--- /dev/null
+++ b/test/src/testcases/truncate_test.c
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_truncate)
+{
+    struct fs_file *file;
+    int rc;
+
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 0);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "abcdefgh", 8);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 8);
+    TEST_ASSERT(fs_getpos(file) == 8);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "abcdefgh", 8);
+
+    rc = fs_open("/myfile.txt", FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE, &file);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 0);
+    TEST_ASSERT(fs_getpos(file) == 0);
+
+    rc = fs_write(file, "1234", 4);
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_assert_file_len(file, 4);
+    TEST_ASSERT(fs_getpos(file) == 4);
+    rc = fs_close(file);
+    TEST_ASSERT(rc == 0);
+
+    nffs_test_util_assert_contents("/myfile.txt", "1234", 4);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+            .children = (struct nffs_test_file_desc[]) { {
+                .filename = "myfile.txt",
+                .contents = "1234",
+                .contents_len = 4,
+            }, {
+                .filename = NULL,
+            } },
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+}
diff --git a/test/src/testcases/unlink_test.c b/test/src/testcases/unlink_test.c
new file mode 100644
index 0000000..61bdf25
--- /dev/null
+++ b/test/src/testcases/unlink_test.c
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_unlink)
+{
+    struct fs_file *file0;
+    struct fs_file *file2;
+    uint8_t buf[64];
+    struct nffs_file *nfs_file;
+    uint32_t bytes_read;
+    struct fs_file *file1;
+    int initial_num_blocks;
+    int initial_num_inodes;
+    int rc;
+
+    rc = nffs_format(nffs_current_area_descs);
+    TEST_ASSERT_FATAL(rc == 0);
+
+    initial_num_blocks = nffs_block_entry_pool.mp_num_free;
+    initial_num_inodes = nffs_inode_entry_pool.mp_num_free;
+
+    nffs_test_util_create_file("/file0.txt", "0", 1);
+
+    rc = fs_open("/file0.txt", FS_ACCESS_READ | FS_ACCESS_WRITE, &file0);
+    TEST_ASSERT(rc == 0);
+    nfs_file = (struct nffs_file *)file0;
+    TEST_ASSERT(nfs_file->nf_inode_entry->nie_refcnt == 2);
+
+    rc = fs_unlink("/file0.txt");
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(nfs_file->nf_inode_entry->nie_refcnt == 1);
+
+    rc = fs_open("/file0.txt", FS_ACCESS_READ, &file2);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_write(file0, "00", 2);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_seek(file0, 0);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_read(file0, sizeof buf, buf, &bytes_read);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(bytes_read == 2);
+    TEST_ASSERT(memcmp(buf, "00", 2) == 0);
+
+    rc = fs_close(file0);
+    TEST_ASSERT(rc == 0);
+
+
+    rc = fs_open("/file0.txt", FS_ACCESS_READ, &file0);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    /* Ensure the file was fully removed from RAM. */
+    TEST_ASSERT(nffs_inode_entry_pool.mp_num_free == initial_num_inodes);
+    TEST_ASSERT(nffs_block_entry_pool.mp_num_free == initial_num_blocks);
+
+    /*** Nested unlink. */
+    rc = fs_mkdir("/mydir");
+    TEST_ASSERT(rc == 0);
+    nffs_test_util_create_file("/mydir/file1.txt", "1", 2);
+
+    rc = fs_open("/mydir/file1.txt", FS_ACCESS_READ | FS_ACCESS_WRITE, &file1);
+    TEST_ASSERT(rc == 0);
+    nfs_file = (struct nffs_file *)file1;
+    TEST_ASSERT(nfs_file->nf_inode_entry->nie_refcnt == 2);
+
+    rc = fs_unlink("/mydir");
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(nfs_file->nf_inode_entry->nie_refcnt == 1);
+
+    rc = fs_open("/mydir/file1.txt", FS_ACCESS_READ, &file2);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    rc = fs_write(file1, "11", 2);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_seek(file1, 0);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_read(file1, sizeof buf, buf, &bytes_read);
+    TEST_ASSERT(rc == 0);
+    TEST_ASSERT(bytes_read == 2);
+    TEST_ASSERT(memcmp(buf, "11", 2) == 0);
+
+    rc = fs_close(file1);
+    TEST_ASSERT(rc == 0);
+
+    rc = fs_open("/mydir/file1.txt", FS_ACCESS_READ, &file1);
+    TEST_ASSERT(rc == FS_ENOENT);
+
+    struct nffs_test_file_desc *expected_system =
+        (struct nffs_test_file_desc[]) { {
+            .filename = "",
+            .is_dir = 1,
+    } };
+
+    nffs_test_assert_system(expected_system, nffs_current_area_descs);
+
+    /* Ensure the files and directories were fully removed from RAM. */
+    TEST_ASSERT(nffs_inode_entry_pool.mp_num_free == initial_num_inodes);
+    TEST_ASSERT(nffs_block_entry_pool.mp_num_free == initial_num_blocks);
+}
diff --git a/test/src/testcases/wear_level_test.c b/test/src/testcases/wear_level_test.c
new file mode 100644
index 0000000..929f5af
--- /dev/null
+++ b/test/src/testcases/wear_level_test.c
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "nffs_test_utils.h"
+
+TEST_CASE(nffs_test_wear_level)
+{
+    int rc;
+    int i;
+    int j;
+
+    static const struct nffs_area_desc area_descs_uniform[] = {
+        { 0x00000000, 2 * 1024 },
+        { 0x00020000, 2 * 1024 },
+        { 0x00040000, 2 * 1024 },
+        { 0x00060000, 2 * 1024 },
+        { 0x00080000, 2 * 1024 },
+        { 0, 0 },
+    };
+
+    /*** Setup. */
+    rc = nffs_format(area_descs_uniform);
+    TEST_ASSERT(rc == 0);
+
+    /* Ensure areas rotate properly. */
+    for (i = 0; i < 255; i++) {
+        for (j = 0; j < nffs_num_areas; j++) {
+            nffs_test_assert_area_seqs(i, nffs_num_areas - j, i + 1, j);
+            nffs_gc(NULL);
+        }
+    }
+
+    /* Ensure proper rollover of sequence numbers. */
+    for (j = 0; j < nffs_num_areas; j++) {
+        nffs_test_assert_area_seqs(255, nffs_num_areas - j, 0, j);
+        nffs_gc(NULL);
+    }
+    for (j = 0; j < nffs_num_areas; j++) {
+        nffs_test_assert_area_seqs(0, nffs_num_areas - j, 1, j);
+        nffs_gc(NULL);
+    }
+}