http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/d98ddc1c/fs/nffs/test/src/arch/sim/nffs_test.c ---------------------------------------------------------------------- diff --git a/fs/nffs/test/src/arch/sim/nffs_test.c b/fs/nffs/test/src/arch/sim/nffs_test.c new file mode 100644 index 0000000..87afcfa --- /dev/null +++ b/fs/nffs/test/src/arch/sim/nffs_test.c @@ -0,0 +1,3251 @@ +/** + * 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 "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" + +int flash_native_memset(uint32_t offset, uint8_t c, uint32_t len); + +static const struct nffs_area_desc nffs_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 }, +}; + +static void +nffs_test_util_assert_ent_name(struct fs_dirent *dirent, + const char *expected_name) +{ + char name[NFFS_FILENAME_MAX_LEN + 1]; + 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); + } +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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; +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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 * 1024) +static struct nffs_hash_entry + *nffs_test_touched_entries[NFFS_TEST_TOUCHED_ARR_SZ]; +static int nffs_test_num_touched_entries; + +/* + * Recursively descend directory structure + */ +static 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); + } +} + +static 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); + } + } +} + +static 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); +} + +static 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 + */ +static 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; + } +} + +static 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); +} + +static 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); +} + +static 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); +} + +static void +nffs_test_mkdir(void) +{ + struct fs_file *file; + int rc; + + + rc = nffs_format(nffs_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_area_descs); +} + +TEST_CASE(nffs_test_unlink) +{ + struct fs_file *file0; + struct fs_file *file1; + struct fs_file *file2; + struct nffs_file *nfs_file; + uint8_t buf[64]; + uint32_t bytes_read; + int initial_num_blocks; + int initial_num_inodes; + int rc; + + rc = nffs_format(nffs_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_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); +} + +TEST_CASE(nffs_test_rename) +{ + struct fs_file *file; + const char contents[] = "contents"; + int rc; + + + rc = nffs_format(nffs_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_area_descs); +} + +TEST_CASE(nffs_test_truncate) +{ + struct fs_file *file; + int rc; + + + rc = nffs_format(nffs_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_area_descs); +} + +TEST_CASE(nffs_test_append) +{ + struct fs_file *file; + uint32_t len; + char c; + int rc; + int i; + + rc = nffs_format(nffs_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" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789", + 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_area_descs); +} + +TEST_CASE(nffs_test_read) +{ + struct fs_file *file; + uint8_t buf[16]; + uint32_t bytes_read; + int rc; + + rc = nffs_format(nffs_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); +} + +TEST_CASE(nffs_test_open) +{ + struct fs_file *file; + struct fs_dir *dir; + int rc; + + rc = nffs_format(nffs_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); +} + +TEST_CASE(nffs_test_overwrite_one) +{ + struct fs_file *file; + int rc; + + /*** Setup. */ + rc = nffs_format(nffs_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_area_descs); +} + +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_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_area_descs); +} + +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_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_area_descs); +} + +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_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_area_descs); +} + +TEST_CASE(nffs_test_long_filename) +{ + int rc; + + + /*** Setup. */ + rc = nffs_format(nffs_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_area_descs); +} + +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); +} + +TEST_CASE(nffs_test_many_children) +{ + int rc; + + + /*** Setup. */ + rc = nffs_format(nffs_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_area_descs); +} + +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); +} + +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); + } +} + +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 }, + }; + + + /*** 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); +} + +/* + * 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_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_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_area_descs); +} + +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_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_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_area_descs); +} + +TEST_CASE(nffs_test_large_unlink) +{ + static char file_contents[1024 * 4]; + char filename[256]; + int rc; + int i; + int j; + int k; + + + /*** Setup. */ + nffs_config.nc_num_inodes = 1024; + nffs_config.nc_num_blocks = 1024; + + rc = nffs_init(); + TEST_ASSERT(rc == 0); + + rc = nffs_format(nffs_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_area_descs); +} + +TEST_CASE(nffs_test_large_system) +{ + int rc; + + + /*** Setup. */ + rc = nffs_format(nffs_area_descs); + TEST_ASSERT(rc == 0); + nffs_test_util_create_tree(nffs_test_system_01); + + nffs_test_assert_system(nffs_test_system_01, nffs_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_area_descs); +} + +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_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_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_area_descs); +} + +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_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); +} + +TEST_CASE(nffs_test_readdir) +{ + struct fs_dirent *dirent; + struct fs_dir *dir; + int rc; + + /*** Setup. */ + rc = nffs_format(nffs_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); +} + +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); +} + +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); +} + +TEST_SUITE(nffs_suite_cache) +{ + int rc; + + memset(&nffs_config, 0, sizeof nffs_config); + nffs_config.nc_num_cache_inodes = 4; + nffs_config.nc_num_cache_blocks = 64; + + rc = nffs_init(); + TEST_ASSERT(rc == 0); + + nffs_test_cache_large_file(); +} + +static void +nffs_test_gen(void) +{ + 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_SUITE(gen_1_1) +{ + nffs_config.nc_num_cache_inodes = 1; + nffs_config.nc_num_cache_blocks = 1; + nffs_test_gen(); +} + +TEST_SUITE(gen_4_32) +{ + nffs_config.nc_num_cache_inodes = 4; + nffs_config.nc_num_cache_blocks = 32; + nffs_test_gen(); +} + +TEST_SUITE(gen_32_1024) +{ + nffs_config.nc_num_cache_inodes = 32; + nffs_config.nc_num_cache_blocks = 1024; + nffs_test_gen(); +} + +int +nffs_test_all(void) +{ + nffs_config.nc_num_inodes = 1024 * 8; + nffs_config.nc_num_blocks = 1024 * 20; + + gen_1_1(); + gen_4_32(); + gen_32_1024(); + nffs_suite_cache(); + + return tu_any_failed; +} + +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); +} + +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", +
<TRUNCATED>