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);
+ }
+}