| /* |
| * 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. |
| */ |
| |
| /* |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <fs.h> |
| #include <nffs/queue.h> |
| #include <nffs/nffs.h> |
| #include <nffs/os.h> |
| #include <flash.h> |
| #include <ztest_assert.h> |
| #include "nffs_test_utils.h" |
| |
| /* |
| * This should fit the largest area used in test (128K). |
| */ |
| #define AREA_BUF_MAX_SIZE (128 * 1024) |
| static u8_t area_buf[AREA_BUF_MAX_SIZE]; |
| |
| #define NFFS_TEST_BUF_SIZE (24 * 1024) |
| u8_t nffs_test_buf[NFFS_TEST_BUF_SIZE]; |
| |
| void nffs_test_util_overwrite_data(u8_t *data, u32_t data_len, |
| u32_t addr) |
| { |
| struct device *dev; |
| struct flash_pages_info info; |
| off_t off; |
| |
| dev = device_get_binding(CONFIG_FS_NFFS_FLASH_DEV_NAME); |
| flash_get_page_info_by_offs(dev, addr, &info); |
| |
| nffs_os_flash_read(0, info.start_offset, area_buf, info.size); |
| |
| /* |
| * To make this simpler, assume we always overwrite within sector |
| * boundary (which is the case here). |
| */ |
| off = addr - info.start_offset; |
| memcpy(&area_buf[off], data, data_len); |
| |
| nffs_os_flash_erase(0, info.start_offset, info.size); |
| nffs_os_flash_write(0, info.start_offset, area_buf, info.size); |
| } |
| |
| void nffs_test_util_assert_ent_name(struct fs_dirent *fs_dirent, |
| const char *expected_name) |
| { |
| zassert_equal(strcmp(fs_dirent->name, expected_name), 0, NULL); |
| } |
| |
| void nffs_test_util_assert_file_len(struct nffs_file *file, u32_t expected) |
| { |
| u32_t len; |
| int rc; |
| |
| rc = nffs_inode_data_len(file->nf_inode_entry, &len); |
| zassert_equal(rc, 0, NULL); |
| zassert_equal(len, expected, NULL); |
| } |
| |
| void nffs_test_util_assert_cache_is_sane(const char *filename) |
| { |
| struct nffs_cache_inode *cache_inode; |
| struct nffs_cache_block *cache_block; |
| struct nffs_file *file; |
| fs_file_t fs_file; |
| u32_t cache_start; |
| u32_t cache_end; |
| u32_t block_end; |
| int rc; |
| |
| rc = fs_open(&fs_file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| file = fs_file.fp; |
| rc = nffs_cache_inode_ensure(&cache_inode, file->nf_inode_entry); |
| zassert_equal(rc, 0, NULL); |
| |
| nffs_cache_inode_range(cache_inode, &cache_start, &cache_end); |
| |
| if (TAILQ_EMPTY(&cache_inode->nci_block_list)) { |
| zassert_equal(cache_start, 0, NULL); |
| zassert_equal(cache_end, 0, NULL); |
| } 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)) { |
| zassert_equal(cache_block->ncb_file_offset, |
| cache_start, NULL); |
| } else { |
| /* Ensure no gap between this block and its |
| * predecessor. |
| */ |
| zassert_equal(cache_block->ncb_file_offset, |
| block_end, NULL); |
| } |
| |
| 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)) { |
| zassert_equal(block_end, cache_end, NULL); |
| } |
| } |
| } |
| |
| rc = fs_close(&fs_file); |
| zassert_equal(rc, 0, NULL); |
| } |
| |
| void |
| nffs_test_util_assert_contents(const char *filename, const char *contents, |
| int contents_len) |
| { |
| fs_file_t file; |
| u32_t bytes_read; |
| void *buf; |
| int rc; |
| |
| rc = fs_open(&file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| zassert_true(contents_len <= AREA_BUF_MAX_SIZE, "contents too large"); |
| buf = area_buf; |
| |
| bytes_read = fs_read(&file, buf, contents_len); |
| zassert_equal(bytes_read, contents_len, NULL); |
| zassert_equal(memcmp(buf, contents, contents_len), 0, NULL); |
| |
| rc = fs_close(&file); |
| zassert_equal(rc, 0, NULL); |
| |
| 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; |
| fs_file_t fs_file; |
| int count; |
| int rc; |
| |
| rc = fs_open(&fs_file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| file = fs_file.fp; |
| count = 0; |
| entry = file->nf_inode_entry->nie_last_block_entry; |
| while (entry != NULL) { |
| count++; |
| rc = nffs_block_from_hash_entry(&block, entry); |
| zassert_equal(rc, 0, NULL); |
| zassert_not_equal(block.nb_prev, entry, NULL); |
| entry = block.nb_prev; |
| } |
| |
| rc = fs_close(&fs_file); |
| zassert_equal(rc, 0, NULL); |
| |
| 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); |
| zassert_equal(actual_count, expected_count, NULL); |
| } |
| |
| void nffs_test_util_assert_cache_range(const char *filename, |
| u32_t expected_cache_start, |
| u32_t expected_cache_end) |
| { |
| struct nffs_cache_inode *cache_inode; |
| struct nffs_file *file; |
| fs_file_t fs_file; |
| u32_t cache_start; |
| u32_t cache_end; |
| int rc; |
| |
| rc = fs_open(&fs_file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| file = fs_file.fp; |
| rc = nffs_cache_inode_ensure(&cache_inode, file->nf_inode_entry); |
| zassert_equal(rc, 0, NULL); |
| |
| nffs_cache_inode_range(cache_inode, &cache_start, &cache_end); |
| zassert_equal(cache_start, expected_cache_start, NULL); |
| zassert_equal(cache_end, expected_cache_end, NULL); |
| |
| rc = fs_close(&fs_file); |
| zassert_equal(rc, 0, NULL); |
| |
| 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) |
| { |
| fs_file_t file; |
| u32_t total_len; |
| u32_t offset; |
| char *buf; |
| int num_writes; |
| int rc; |
| int i; |
| |
| /* We do not have 'truncate' flag in fs_open, so unlink here instead */ |
| fs_unlink(filename); |
| |
| rc = fs_open(&file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| 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); |
| zassert_equal(rc, blocks[i].data_len, NULL); |
| |
| total_len += blocks[i].data_len; |
| } |
| |
| rc = fs_close(&file); |
| zassert_equal(rc, 0, NULL); |
| |
| zassert_true(total_len <= AREA_BUF_MAX_SIZE, "contents too large"); |
| buf = area_buf; |
| |
| offset = 0; |
| for (i = 0; i < num_writes; i++) { |
| memcpy(buf + offset, blocks[i].data, blocks[i].data_len); |
| offset += blocks[i].data_len; |
| } |
| zassert_equal(offset, total_len, NULL); |
| |
| nffs_test_util_assert_contents(filename, buf, total_len); |
| if (num_blocks > 0) { |
| nffs_test_util_assert_block_count(filename, num_blocks); |
| } |
| } |
| |
| 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) |
| { |
| fs_file_t file; |
| int rc; |
| |
| rc = fs_open(&file, filename); |
| zassert_equal(rc, 0, NULL); |
| |
| rc = fs_seek(&file, 0, FS_SEEK_END); |
| zassert_equal(rc, 0, NULL); |
| |
| rc = fs_write(&file, contents, contents_len); |
| zassert_equal(rc, contents_len, NULL); |
| |
| rc = fs_close(&file); |
| zassert_equal(rc, 0, NULL); |
| } |
| |
| void nffs_test_copy_area(const struct nffs_area_desc *from, |
| const struct nffs_area_desc *to) |
| { |
| int rc; |
| void *buf; |
| |
| zassert_equal(from->nad_length, to->nad_length, NULL); |
| |
| zassert_true(from->nad_length <= AREA_BUF_MAX_SIZE, "area too large"); |
| buf = area_buf; |
| |
| rc = nffs_os_flash_read(from->nad_flash_id, from->nad_offset, buf, |
| from->nad_length); |
| zassert_equal(rc, 0, NULL); |
| |
| rc = nffs_os_flash_erase(from->nad_flash_id, to->nad_offset, |
| to->nad_length); |
| zassert_equal(rc, 0, NULL); |
| |
| rc = nffs_os_flash_write(to->nad_flash_id, to->nad_offset, buf, |
| to->nad_length); |
| zassert_equal(rc, 0, NULL); |
| } |
| |
| 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 = k_malloc(1); |
| zassert_not_null(path, NULL); |
| path[0] = '\0'; |
| } else { |
| path = k_malloc(strlen(parent_path) + strlen(elem->filename) + 2); |
| zassert_not_null(path, NULL); |
| |
| sprintf(path, "%s/%s", parent_path, elem->filename); |
| } |
| |
| if (elem->is_dir) { |
| if (parent_path != NULL) { |
| rc = fs_mkdir(path); |
| zassert_equal(rc, 0, NULL); |
| } |
| |
| 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); |
| } |
| |
| k_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 |
| */ |
| zassert_true(nffs_test_num_touched_entries < NFFS_TEST_TOUCHED_ARR_SZ, |
| NULL); |
| 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); |
| zassert_equal(rc, 0, NULL); |
| |
| /* 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 = k_malloc(path_len + child_filename_len + 2); |
| zassert_not_null(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); |
| zassert_equal(rc, 0, NULL); |
| |
| nffs_test_assert_file(child_file, child_inode_entry, |
| child_path); |
| |
| k_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; |
| } |
| } |
| zassert_true(i < nffs_test_num_touched_entries, NULL); |
| 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; |
| |
| /* |
| * Successfully read inode data from flash |
| */ |
| rc = nffs_inode_from_entry(&inode, child); |
| zassert_equal(rc, 0, NULL); |
| |
| /* |
| * Validate parent |
| */ |
| parent = inode.ni_parent; |
| zassert_not_null(parent, NULL); |
| zassert_true(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id), NULL); |
| |
| /* |
| * Make sure inode is in parents child list |
| */ |
| SLIST_FOREACH(inode_entry, &parent->nie_child_list, nie_sibling_next) { |
| if (inode_entry == child) { |
| return; |
| } |
| } |
| |
| zassert_true(0, NULL); |
| } |
| |
| 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); |
| zassert_equal(rc, 0, NULL); |
| |
| /* |
| * Validate owning inode |
| */ |
| inode_entry = block.nb_inode_entry; |
| zassert_not_null(inode_entry, NULL); |
| zassert_true(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id), |
| NULL); |
| |
| /* |
| * 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); |
| zassert_equal(rc, 0, NULL); |
| cur = block.nb_prev; |
| } |
| |
| zassert_true(0, NULL); |
| } |
| |
| /* |
| * 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); |
| zassert_equal(rc, 0, NULL); |
| |
| if (prev_entry != NULL) { |
| rc = nffs_inode_from_entry(&prev_inode, prev_entry); |
| zassert_equal(rc, 0, NULL); |
| |
| rc = nffs_inode_filename_cmp_flash(&prev_inode, |
| &child_inode, &cmp); |
| zassert_equal(rc, 0, NULL); |
| zassert_true(cmp < 0, NULL); |
| } |
| |
| 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) { |
| zassert_true(entry->nhe_flash_loc != NFFS_FLASH_LOC_NONE, NULL); |
| if (nffs_hash_id_is_inode(entry->nhe_id)) { |
| inode_entry = (void *)entry; |
| zassert_equal(inode_entry->nie_refcnt, 1, NULL); |
| if (entry->nhe_id == NFFS_ID_ROOT_DIR) { |
| zassert_true(inode_entry == nffs_root_dir, |
| NULL); |
| } 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); |
| zassert_equal(rc, 0, NULL); |
| |
| /* 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(); |
| zassert_equal(rc, 0, NULL); |
| rc = nffs_restore_full(area_descs); |
| zassert_equal(rc, 0, NULL); |
| |
| /* 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)); |
| zassert_equal(rc, 0, NULL); |
| zassert_true(nffs_area_magic_is_set(&disk_area), NULL); |
| zassert_equal(disk_area.nda_gc_seq, nffs_areas[i].na_gc_seq, |
| NULL); |
| if (i == nffs_scratch_area_idx) { |
| zassert_equal(disk_area.nda_id, NFFS_AREA_ID_NONE, |
| NULL); |
| } |
| |
| if (nffs_areas[i].na_gc_seq == seq1) { |
| cur1++; |
| } else if (nffs_areas[i].na_gc_seq == seq2) { |
| cur2++; |
| } else { |
| zassert_true(0, NULL); |
| } |
| } |
| |
| zassert_equal(cur1, count1, NULL); |
| zassert_equal(cur2, count2, NULL); |
| } |