blob: 62e0e913cb151b8309c6cd92cf60b93ccf5d4e51 [file] [log] [blame]
/*
* 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);
}