blob: c6da0a85af73cbfbd03b9c11aad250ec74ce48c8 [file] [log] [blame]
/*
* Copyright (C) 2015-2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <limits.h>
#include <lk/macros.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <openssl/rand.h>
#include "../block_allocator.h"
#include "../block_cache.h"
#include "../block_map.h"
#include "../block_set.h"
#include "../crypt.h"
#include "../debug_stats.h"
#include "../file.h"
#include "../transaction.h"
#include <time.h>
long gettime(uint32_t clock_id, uint32_t flags, int64_t* time) {
int ret;
struct timespec ts;
assert(!clock_id);
assert(!flags);
ret = clock_gettime(CLOCK_MONOTONIC, &ts);
assert(!ret);
*time = ts.tv_sec * 1000000000LL + ts.tv_nsec;
return 0;
}
#if 0 /* test tree order 3 */
/* not useful, b+tree for free set grows faster than the space that is added to it */
#define BLOCK_SIZE (64)
#define BLOCK_COUNT (256)
#elif 0 /* test tree order 4 */
#define BLOCK_SIZE (80)
#define BLOCK_COUNT (256)
#elif 0 /* test tree order 5 */
#define BLOCK_SIZE (96)
#define BLOCK_COUNT (256)
#elif 0 /* test tree order 6 */
#define BLOCK_SIZE (112)
#define BLOCK_COUNT (256)
#elif 0 /* test tree order 7 */
#define BLOCK_SIZE (128)
#define BLOCK_COUNT (256)
#elif 0 /* test tree order 8 */
#define BLOCK_SIZE (144)
#define BLOCK_COUNT (256)
#elif 0 /* test single rpmb block with 64-bit indexes */
#define BLOCK_SIZE (256)
#define BLOCK_COUNT (256)
#elif 1
#define BLOCK_SIZE (2048)
#define BLOCK_COUNT (256)
#elif 0
/* test single rpmb block with simulated 16-bit indexes, 128kb device */
#define BLOCK_SIZE (256 * 4)
#define BLOCK_COUNT (512)
#elif 0
/* test single rpmb block with simulated 16-bit indexes, 4MB device */
#define BLOCK_SIZE (256 * 4)
#define BLOCK_COUNT (16384)
#else
#define BLOCK_SIZE (256 * 4)
#define BLOCK_COUNT (0x10000)
#endif
struct block {
char data[BLOCK_SIZE];
char data_copy[BLOCK_SIZE];
struct mac mac;
bool loaded;
bool dirty;
bool dirty_ref;
struct block* parent;
struct block_mac* block_mac_in_parent;
const char* used_by_str;
data_block_t used_by_block;
};
static struct block blocks[BLOCK_COUNT];
static const struct key key;
static bool print_test_verbose = false;
static bool print_block_tree_test_verbose = false;
static void block_test_start_read(struct block_device* dev,
data_block_t block) {
assert(dev->block_size <= BLOCK_SIZE);
assert(block < countof(blocks));
block_cache_complete_read(dev, block, blocks[block].data, dev->block_size,
false);
}
static void block_test_start_write(struct block_device* dev,
data_block_t block,
const void* data,
size_t data_size) {
assert(block < countof(blocks));
assert(data_size <= sizeof(blocks[block].data));
memcpy(blocks[block].data, data, data_size);
block_cache_complete_write(dev, block, false);
}
#if FULL_ASSERT
static void block_clear_used_by(void) {
size_t block;
for (block = 0; block < countof(blocks); block++) {
blocks[block].used_by_str = NULL;
blocks[block].used_by_block = 0;
}
}
static void block_set_used_by(data_block_t block,
const char* used_by_str,
data_block_t used_by_block) {
assert(block < countof(blocks));
if (!blocks[block].used_by_str) {
blocks[block].used_by_str = used_by_str;
blocks[block].used_by_block = used_by_block;
}
assert(blocks[block].used_by_str == used_by_str);
assert(blocks[block].used_by_block == used_by_block);
}
static void mark_block_tree_in_use(struct transaction* tr,
struct block_tree* block_tree,
bool mark_data_used,
const char* used_by_str,
data_block_t used_by_block) {
struct block_tree_path path;
unsigned int i;
block_tree_walk(tr, block_tree, 0, true, &path);
if (path.count) {
/* mark root in use in case it is empty */
block_set_used_by(block_mac_to_block(tr, &path.entry[0].block_mac),
used_by_str, used_by_block);
}
while (block_tree_path_get_key(&path)) {
for (i = 0; i < path.count; i++) {
block_set_used_by(block_mac_to_block(tr, &path.entry[i].block_mac),
used_by_str, used_by_block);
}
if (mark_data_used) {
block_set_used_by(block_tree_path_get_data(&path), used_by_str,
used_by_block);
}
block_tree_path_next(&path);
}
}
static void mark_files_in_use(struct transaction* tr) {
void file_block_map_init(struct transaction * tr,
struct block_map * block_map,
const struct block_mac* file);
struct block_tree_path path;
struct block_map block_map;
block_tree_walk(tr, &tr->fs->files, 0, true, &path);
while (block_tree_path_get_key(&path)) {
struct block_mac block_mac = block_tree_path_get_data_block_mac(&path);
file_block_map_init(tr, &block_map, &block_mac);
mark_block_tree_in_use(tr, &block_map.tree, true, "file",
block_mac_to_block(tr, &block_mac));
block_tree_path_next(&path);
}
}
static void check_fs_prepare(struct transaction* tr) {
data_block_t block;
block_clear_used_by();
for (block = tr->fs->dev->block_count; block < countof(blocks); block++) {
block_set_used_by(block, "out-of-range", 0);
}
block_set_used_by(tr->fs->super_block[0], "superblock", 0);
block_set_used_by(tr->fs->super_block[1], "superblock", 1);
block = 1;
while (true) {
block = block_set_find_next_block(tr, &tr->fs->free, block, true);
if (!block) {
break;
}
block_set_used_by(block, "free", 0);
block++;
}
mark_block_tree_in_use(tr, &tr->fs->free.block_tree, false,
"free_tree_node", 0);
mark_block_tree_in_use(tr, &tr->fs->files, true, "files", 0);
mark_files_in_use(tr);
}
static bool check_fs_finish(struct transaction* tr) {
bool valid = true;
data_block_t block;
for (block = 0; block < countof(blocks); block++) {
if (!blocks[block].used_by_str) {
printf("block %lld, lost\n", block);
valid = false;
}
}
if (!valid) {
printf("free:\n");
block_set_print(tr, &tr->fs->free);
files_print(tr);
}
return valid;
}
static bool check_fs_allocated(struct transaction* tr,
data_block_t* allocated,
size_t allocated_count) {
size_t i;
check_fs_prepare(tr);
for (i = 0; i < allocated_count; i++) {
block_set_used_by(allocated[i], "allocated", 0);
}
return check_fs_finish(tr);
}
static bool check_fs(struct transaction* tr) {
return check_fs_allocated(tr, NULL, 0);
}
#endif
static void empty_test(struct transaction* tr) {
struct block_range range;
range.start = 4;
range.end = tr->fs->dev->block_count;
assert(block_set_range_in_set(tr, &tr->fs->free, range));
if (print_test_verbose) {
printf("%s: initial free state:\n", __func__);
block_set_print(tr, &tr->fs->free);
}
}
typedef uint16_t (*keyfunc_t)(unsigned int index,
unsigned int rindex,
unsigned int maxindex);
static uint16_t inc_inc_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
return rindex ?: index;
}
static uint16_t inc_dec_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
return index;
}
static uint16_t dec_inc_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
return maxindex + 1 - index;
}
static uint16_t dec_dec_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
return maxindex + 1 - (rindex ?: index);
}
static uint16_t same_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
return 1;
}
static uint16_t rand_key(unsigned int index,
unsigned int rindex,
unsigned int maxindex) {
uint16_t key;
RAND_bytes((uint8_t*)&key, sizeof(key));
return key ?: 1; /* 0 key is not currently supported */
}
keyfunc_t keyfuncs[] = {
inc_inc_key, inc_dec_key, dec_inc_key, dec_dec_key, same_key, rand_key,
};
static void block_tree_test_etc(struct transaction* tr,
unsigned int order,
unsigned int count,
unsigned int commit_interval,
keyfunc_t keyfunc) {
unsigned int i;
unsigned int ri;
uint16_t key;
uint16_t tmpkey;
unsigned int commit_count = 0;
struct block_tree tree = BLOCK_TREE_INITIAL_VALUE(tree);
struct block_tree_path path;
const size_t key_size = sizeof(key);
const size_t header_size = sizeof(struct iv) + 8;
const size_t child_size = sizeof(struct block_mac);
size_t block_size =
header_size + key_size * (order - 1) + child_size * order;
if (block_size > tr->fs->dev->block_size) {
printf("block tree order %d does not fit in block. block size %zd > %zd, skip test\n",
order, block_size, tr->fs->dev->block_size);
return;
}
block_tree_init(&tree, block_size, key_size, child_size, child_size);
if (commit_interval) {
tree.copy_on_write = true;
tree.allow_copy_on_write = true;
}
assert(tree.key_count[0] == order - 1);
assert(tree.key_count[1] == order - 1);
for (i = 1; i <= count; key++, i++) {
key = keyfunc(i, 0, count);
if (print_block_tree_test_verbose) {
printf("block tree order %d, insert %d:\n", order, key);
}
block_tree_insert(tr, &tree, key, i);
if (tr->failed) {
return;
}
if (commit_interval && ++commit_count == commit_interval) {
commit_count = 0;
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
}
if (print_block_tree_test_verbose) {
printf("block tree order %d after %d inserts, last %d:\n", order, i,
key);
block_tree_print(tr, &tree);
}
}
for (ri = 1, i--; i >= 1; i--, ri++) {
tmpkey = keyfunc(i, ri, count);
assert(tmpkey);
block_tree_walk(tr, &tree, tmpkey, false, &path);
key = block_tree_path_get_key(&path);
if (!key) {
key = path.entry[path.count - 1].prev_key;
assert(key);
}
if (key != tmpkey) {
block_tree_walk(tr, &tree, key, false, &path);
}
assert(key = block_tree_path_get_key(&path));
assert(block_tree_path_get_data(&path));
if (print_block_tree_test_verbose) {
printf("block tree order %d, remove %d (%d):\n", order, key,
tmpkey);
}
block_tree_remove(tr, &tree, key, block_tree_path_get_data(&path));
if (tr->failed) {
return;
}
if (commit_interval && ++commit_count == commit_interval) {
commit_count = 0;
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
}
if (print_block_tree_test_verbose) {
printf("block tree order %d removed %d:\n", order, key);
block_tree_print(tr, &tree);
}
}
if (commit_interval) {
block_discard_dirty_by_block(tr->fs->dev,
block_mac_to_block(tr, &tree.root));
block_free(tr, block_mac_to_block(tr, &tree.root));
}
}
static void block_tree_keyfuncs_test(struct transaction* tr,
unsigned int order,
unsigned int count) {
unsigned int commit_interval;
unsigned int i;
for (commit_interval = 0; commit_interval < 2; commit_interval++) {
for (i = 0; i < countof(keyfuncs); i++) {
block_tree_test_etc(tr, order, count, commit_interval, keyfuncs[i]);
}
}
}
static void block_tree_test(struct transaction* tr) {
unsigned int order;
block_tree_keyfuncs_test(tr, 6, 5); /* test leaf node only */
block_tree_keyfuncs_test(tr, 6, 10);
for (order = 3; order <= 5; order++) {
block_tree_keyfuncs_test(tr, order, order - 1);
block_tree_keyfuncs_test(tr, order, order);
block_tree_keyfuncs_test(tr, order, order * 2);
block_tree_keyfuncs_test(tr, order, order * order);
block_tree_keyfuncs_test(tr, order, order * order * order);
}
}
static void block_set_test(struct transaction* tr) {
struct block_set sets[3];
unsigned int si, i;
for (si = 0; si < countof(sets); si++) {
block_set_init(tr->fs, &sets[si]);
}
for (i = 0; i < 10; i++) {
for (si = 0; si < countof(sets); si++) {
block_set_add_block(tr, &sets[si], 2 + i * 3 + si);
}
}
for (si = 0; si < countof(sets); si++) {
assert(!block_set_overlap(tr, &sets[si],
&sets[(si + 1) % countof(sets)]));
}
for (si = 1; si < countof(sets); si++) {
block_set_add_block(tr, &sets[0], 2 + 5 * 3 + si);
}
for (si = 1; si < countof(sets); si++) {
assert(block_set_overlap(tr, &sets[si], &sets[0]));
assert(block_set_overlap(tr, &sets[0], &sets[si]));
assert(si < 2 || !block_set_overlap(tr, &sets[si], &sets[si - 1]));
}
}
static void block_tree_allocate_all_test(struct transaction* tr) {
unsigned int i;
for (i = 0; i < countof(keyfuncs); i++) {
assert(!tr->failed);
block_tree_test_etc(tr, 3, ~0, 0, keyfuncs[i]);
assert(tr->failed);
transaction_complete(tr);
transaction_activate(tr);
}
}
static void block_map_test(struct transaction* tr) {
unsigned int i;
struct block_mac block_mac = BLOCK_MAC_INITIAL_VALUE(block_mac);
struct block_map block_map = BLOCK_MAP_INITIAL_VALUE(block_map);
block_map_init(tr, &block_map, &block_mac, 128);
for (i = 1; i <= 100; i++) {
block_mac_set_block(tr, &block_mac, block_allocate(tr));
block_map_set(tr, &block_map, i, &block_mac);
}
for (; i >= 2; i /= 2) {
block_map_truncate(tr, &block_map, i);
assert(!block_map_get(tr, &block_map, i, &block_mac));
assert(block_map_get(tr, &block_map, i - 1, &block_mac));
}
block_map_free(tr, &block_map);
}
static void free_frag_etc_test(struct transaction* tr,
int start,
int end,
int inc) {
int i;
for (i = start; i < end; i += inc) {
block_free(tr, i);
assert(!tr->failed);
}
}
static void allocate_2_transactions_test_etc(struct transaction* tr,
data_block_t blocks1[],
size_t blocks1_count,
data_block_t blocks2[],
size_t blocks2_count) {
unsigned int i;
struct transaction tr1;
struct transaction tr2;
size_t blocks_max_count = MAX(blocks1_count, blocks2_count);
transaction_init(&tr1, tr->fs, true);
transaction_init(&tr2, tr->fs, true);
for (i = 0; i < blocks_max_count; i++) {
if (i < blocks1_count) {
blocks1[i] = block_allocate(&tr1);
}
if (i < blocks2_count) {
blocks2[i] = block_allocate(&tr2);
}
}
assert(!tr1.failed);
assert(!tr2.failed);
transaction_complete(&tr1);
assert(!tr1.failed);
assert(!tr2.failed);
for (i = 0; i < blocks1_count; i++) {
assert(!block_set_block_in_set(tr, &tr->fs->free, blocks1[i]));
}
for (i = 0; i < blocks2_count; i++) {
assert(block_set_block_in_set(tr, &tr->fs->free, blocks2[i]));
}
transaction_complete(&tr2);
for (i = 0; i < blocks1_count; i++) {
assert(!block_set_block_in_set(tr, &tr->fs->free, blocks1[i]));
}
for (i = 0; i < blocks2_count; i++) {
assert(!block_set_block_in_set(tr, &tr->fs->free, blocks2[i]));
}
assert(!block_cache_debug_get_ref_block_count());
#if FULL_ASSERT
check_fs_prepare(tr);
for (i = 0; i < blocks1_count; i++) {
block_set_used_by(blocks1[i], "allocated", 0);
}
for (i = 0; i < blocks2_count; i++) {
block_set_used_by(blocks2[i], "allocated2", 0);
}
assert(check_fs_finish(tr));
#endif
transaction_free(&tr1);
transaction_free(&tr2);
}
static void free_test_etc(struct transaction* tr,
data_block_t blocks1[],
size_t blocks1_count,
data_block_t blocks2[],
size_t blocks2_count) {
unsigned int i;
struct transaction tr1;
struct transaction tr2;
size_t blocks_max_count = MAX(blocks1_count, blocks2_count);
transaction_init(&tr1, tr->fs, true);
transaction_init(&tr2, tr->fs, true);
for (i = 0; i < blocks_max_count; i++) {
if (i < blocks1_count) {
block_free(&tr1, blocks1[i]);
if (print_test_verbose) {
printf("tr1.freed after free %lld:\n", blocks1[i]);
block_set_print(&tr1, &tr1.freed);
}
}
if (i < blocks2_count) {
block_free(&tr2, blocks1[i]);
if (print_test_verbose) {
printf("tr2.freed after free %lld:\n", blocks2[i]);
block_set_print(&tr2, &tr2.freed);
}
}
}
assert(!tr1.failed);
assert(!tr2.failed);
transaction_complete(&tr1);
assert(!tr1.failed);
for (i = 0; i < blocks1_count; i++) {
assert(block_set_block_in_set(tr, &tr->fs->free, blocks1[i]));
}
if (blocks1 == blocks2) {
/* free conflict test */
assert(tr2.failed);
}
transaction_complete(&tr2);
if (blocks1 != blocks2) {
assert(!tr2.failed);
for (i = 0; i < blocks2_count; i++) {
assert(block_set_block_in_set(tr, &tr->fs->free, blocks2[i]));
}
}
assert(!block_cache_debug_get_ref_block_count());
transaction_free(&tr1);
transaction_free(&tr2);
}
enum {
test_free_start = BLOCK_SIZE > 112 ? 20 : 200,
test_free_split = test_free_start + (BLOCK_SIZE > 112 ? 60 : 20),
test_free_end = BLOCK_SIZE > 96 ? BLOCK_COUNT
: BLOCK_SIZE > 80 ? BLOCK_COUNT - 8
: BLOCK_COUNT - 16,
test_free_increment = BLOCK_SIZE > 64 ? 2 : 20,
allocated_size = BLOCK_SIZE > 128 ? 40 : 10,
allocated2_size = BLOCK_SIZE > 64 ? allocated_size : 4,
};
static data_block_t allocated[allocated_size];
static data_block_t allocated2[allocated2_size];
static void allocate_frag_test(struct transaction* tr) {
int i;
struct block_range range;
range.start = test_free_start;
range.end = test_free_end;
block_set_add_range(tr, &tr->allocated, range);
assert(!block_cache_debug_get_ref_block_count());
for (i = test_free_start; i + 1 < test_free_end; i += test_free_increment) {
if (print_test_verbose) {
printf("%s: remove block %d\n", __func__, i);
}
range.start = i + 1;
range.end = i + test_free_increment;
if (range.end > test_free_end) {
range.end = test_free_end;
}
block_set_remove_range(tr, &tr->allocated, range);
assert(!block_cache_debug_get_ref_block_count());
assert(!tr->failed);
}
if (print_test_verbose) {
printf("%s: tr.tmp_allocated:\n", __func__);
block_set_print(tr, &tr->tmp_allocated);
printf("%s: tr.allocated:\n", __func__);
block_set_print(tr, &tr->allocated);
}
assert(!tr->failed);
assert(!block_cache_debug_get_ref_block_count());
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
if (print_test_verbose) {
printf("%s: free state after transaction complete:\n", __func__);
block_set_print(tr, &tr->fs->free);
}
assert(!block_cache_debug_get_ref_block_count());
#if FULL_ASSERT
check_fs_prepare(tr);
for (i = test_free_start; i < test_free_end; i += test_free_increment) {
block_set_used_by(i, "test_free_fragmentation", 0);
}
assert(check_fs_finish(tr));
#endif
}
static void allocate_free_same_test(struct transaction* tr) {
unsigned int i;
printf("%s: start allocate then free same test\n", __func__);
for (i = 0; i < countof(allocated); i++) {
allocated[i] = block_allocate(tr);
assert(!tr->failed);
}
if (print_test_verbose) {
printf("%s: tr.tmp_allocated:\n", __func__);
block_set_print(tr, &tr->tmp_allocated);
printf("%s: tr.allocated:\n", __func__);
block_set_print(tr, &tr->allocated);
}
for (i = 0; i < countof(allocated); i++) {
block_free(tr, allocated[i]);
}
assert(!tr->failed);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
assert(block_set_check(tr, &tr->fs->free));
assert(!block_cache_debug_get_ref_block_count());
#if FULL_ASSERT
check_fs_prepare(tr);
for (i = test_free_start; i < test_free_end; i += test_free_increment) {
block_set_used_by(i, "test_free_fragmentation", 0);
}
assert(check_fs_finish(tr));
#endif
printf("%s: start allocate then free same test, done\n", __func__);
}
static void allocate_free_other_test(struct transaction* tr) {
unsigned int i;
printf("%s: start allocate then free some other test\n", __func__);
for (i = 0; i < countof(allocated); i++) {
allocated[i] = block_allocate(tr);
if (print_test_verbose) {
printf("tr.tmp_allocated after allocate %d, %lld:\n", i,
allocated[i]);
block_set_print(tr, &tr->tmp_allocated);
printf("tr.allocated after allocate %d, %lld:\n", i, allocated[i]);
block_set_print(tr, &tr->allocated);
}
assert(!tr->failed);
}
for (i = test_free_start; i < test_free_split; i += test_free_increment) {
block_free(tr, i);
if (print_test_verbose) {
printf("tr.freed after free %d:\n", i);
block_set_print(tr, &tr->freed);
}
assert(!tr->failed);
}
if (print_test_verbose) {
printf("fs.super->free:\n");
block_set_print(tr, &tr->fs->free);
}
assert(!tr->failed);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
assert(block_set_check(tr, &tr->fs->free));
for (i = 0; i < countof(allocated); i++) {
assert(!block_set_block_in_set(tr, &tr->fs->free, allocated[i]));
}
for (i = test_free_start; i < test_free_split; i += test_free_increment) {
assert(block_set_block_in_set(tr, &tr->fs->free, i));
}
assert(!block_cache_debug_get_ref_block_count());
#if FULL_ASSERT
check_fs_prepare(tr);
for (i = test_free_split; i < test_free_end; i += test_free_increment) {
block_set_used_by(i, "test_free_fragmentation", 0);
}
for (i = 0; i < countof(allocated); i++) {
block_set_used_by(allocated[i], "allocated", 0);
}
assert(check_fs_finish(tr));
#endif
printf("%s: start allocate then free some other test, done\n", __func__);
}
static void free_frag_rem_test(struct transaction* tr) {
int i;
printf("%s: start free rem test\n", __func__);
free_frag_etc_test(tr, test_free_split, test_free_end, test_free_increment);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
assert(block_set_check(tr, &tr->fs->free));
for (i = test_free_split; i < test_free_end; i += test_free_increment) {
assert(block_set_block_in_set(tr, &tr->fs->free, i));
}
assert(!block_cache_debug_get_ref_block_count());
full_assert(check_fs_allocated(tr, allocated, countof(allocated)));
printf("%s: start free rem test, done\n", __func__);
}
static void free_test(struct transaction* tr) {
unsigned int i;
free_test_etc(tr, allocated, countof(allocated), NULL, 0);
i = 0;
do {
// TODO: use this version, currently does not work since ranges aer not
// merged accross nodes
// i = block_set_find_next_block(&fs.free, i, false);
while (block_set_find_next_block(tr, &tr->fs->free, i, true) == i) {
i++;
}
int free = block_set_find_next_block(tr, &tr->fs->free, i, true);
printf("not free: [%d-%d]\n", i, free - 1);
i = free;
} while (i);
}
static void allocate_2_transactions_test(struct transaction* tr) {
printf("%s: start allocate 2 transactions test\n", __func__);
allocate_2_transactions_test_etc(tr, allocated, countof(allocated),
allocated2, countof(allocated2));
printf("%s: allocate 2 transactions test done\n", __func__);
}
static void free_2_transactions_same_test(struct transaction* tr) {
printf("%s: start free 2 transactions same test\n", __func__);
free_test_etc(tr, allocated, countof(allocated), allocated,
countof(allocated));
full_assert(check_fs_allocated(tr, allocated2, countof(allocated2)));
printf("%s: free 2 transactions same test done\n", __func__);
}
static void free_2_transactions_same_test_2(struct transaction* tr) {
printf("%s: start free 2 transactions same test 2\n", __func__);
free_test_etc(tr, allocated2, countof(allocated2), allocated2,
countof(allocated2));
full_assert(check_fs(tr));
printf("%s: free 2 transactions same test 2 done\n", __func__);
}
static void allocate_all_test(struct transaction* tr) {
while (block_allocate(tr)) {
assert(!tr->failed);
}
assert(tr->failed);
transaction_complete(tr);
transaction_activate(tr);
}
static void open_test_file_etc(struct transaction* tr,
struct file_handle* file,
const char* path,
enum file_create_mode create,
bool expect_failure) {
bool found;
found = file_open(tr, path, file, create);
if (print_test_verbose) {
printf("%s: lookup file %s, create %d, got %lld:\n", __func__, path,
create, block_mac_to_block(tr, &file->block_mac));
}
assert(found == !expect_failure);
assert(!found || block_mac_valid(tr, &file->block_mac));
}
static void open_test_file(struct transaction* tr,
struct file_handle* file,
const char* path,
enum file_create_mode create) {
open_test_file_etc(tr, file, path, create, false);
}
static void file_allocate_all_test(struct transaction* master_tr,
unsigned int tr_count,
int success_count,
int step_size,
const char* path,
enum file_create_mode create) {
unsigned int i;
unsigned int j;
unsigned int done;
unsigned int count;
struct file_handle file[tr_count];
data_block_t file_size[tr_count];
struct transaction tr[tr_count];
int written_count[tr_count];
size_t file_block_size = master_tr->fs->dev->block_size - sizeof(struct iv);
void* block_data_rw;
obj_ref_t ref = OBJ_REF_INITIAL_VALUE(ref);
for (i = 0; i < tr_count; i++) {
transaction_init(&tr[i], master_tr->fs, true);
}
for (count = INT_MAX, done = 0; count > 0; count -= step_size) {
for (i = 0; i < tr_count; i++) {
open_test_file(&tr[i], &file[i], path, create);
file_size[i] = file[i].size;
written_count[i] = 0;
}
for (j = 0, done = 0; done != tr_count && j < count; j++) {
for (i = 0, done = 0; i < tr_count; i++) {
if (tr[i].failed) {
done++;
assert(j);
continue;
}
block_data_rw =
file_get_block_write(&tr[i], &file[i], j, true, &ref);
if (!block_data_rw) {
done++;
continue;
}
assert(!tr[i].failed);
file_block_put_dirty(&tr[i], &file[i], j, block_data_rw, &ref);
written_count[i] = j + 1;
}
}
for (i = 0, done = 0; i < tr_count; i++) {
file_set_size(&tr[i], &file[i], written_count[i] * file_block_size);
if (count == INT_MAX) {
assert(tr[i].failed);
}
transaction_complete(&tr[i]);
if (!tr[i].failed) {
assert(file[i].size == written_count[i] * file_block_size);
done++;
} else if (!done) {
assert(file_size[i] == file[i].size);
}
file_close(&file[i]);
transaction_activate(&tr[i]);
}
if (count == INT_MAX) {
count = j;
}
if (success_count && done) {
success_count--;
}
if (!success_count) {
break;
}
if (done) {
file_delete(&tr[0], path);
transaction_complete(&tr[0]);
assert(!tr[0].failed);
transaction_activate(&tr[0]);
}
}
assert(!success_count);
for (i = 0; i < tr_count; i++) {
transaction_complete(&tr[i]);
transaction_free(&tr[i]);
}
}
static void file_create_all_test(struct transaction* tr) {
int i;
char path[4 + 8 + 1];
struct file_handle file;
bool found;
for (i = 0;; i++) {
snprintf(path, sizeof(path), "test%08x", i);
found = file_open(tr, path, &file, FILE_OPEN_CREATE_EXCLUSIVE);
if (!found) {
break;
}
file_close(&file);
assert(!tr->failed);
}
assert(tr->failed);
transaction_complete(tr);
transaction_activate(tr);
}
/* run tests on already open file */
static void file_test_open(struct transaction* tr,
struct file_handle* file,
int allocate,
int read,
int free,
int id) {
int i;
int* block_data_rw;
obj_ref_t ref = OBJ_REF_INITIAL_VALUE(ref);
const int* block_data_ro;
size_t file_block_size = tr->fs->dev->block_size - sizeof(struct iv);
if (allocate) {
for (i = 0; i < allocate; i++) {
block_data_rw = file_get_block_write(tr, file, i, true, &ref);
if (print_test_verbose) {
printf("%s: allocate file block %d, %lld:\n", __func__, i,
data_to_block_num(block_data_rw));
}
assert(block_data_rw);
/* TODO: store iv in file block map */
block_data_rw = (void*)block_data_rw + sizeof(struct iv);
// block_data_rw = block_get_cleared(block)+ sizeof(struct iv);
block_data_rw[0] = i;
block_data_rw[1] = ~i;
block_data_rw[2] = id;
block_data_rw[3] = ~id;
file_block_put_dirty(tr, file, i,
(void*)block_data_rw - sizeof(struct iv),
&ref);
}
if (file->size < i * file_block_size) {
file_set_size(tr, file, i * file_block_size);
}
assert(file->size >= i * file_block_size);
if (print_test_verbose) {
printf("%s: allocated %d file blocks\n", __func__, i);
file_print(tr, file);
}
}
if (read) {
for (i = 0;; i++) {
block_data_ro = file_get_block(tr, file, i, &ref);
if (!block_data_ro) {
break;
}
if (print_test_verbose) {
printf("%s: found file block %d, %lld:\n", __func__, i,
data_to_block_num(block_data_ro));
}
block_data_ro = (void*)block_data_ro + sizeof(struct iv);
assert(block_data_ro[0] == i);
assert(block_data_ro[1] == ~i);
assert(block_data_ro[2] == id);
assert(block_data_ro[3] == ~id);
file_block_put((void*)block_data_ro - sizeof(struct iv), &ref);
}
assert(i == read);
assert(file->size >= i * file_block_size);
}
if (free) {
file_set_size(tr, file, 0);
for (i = 0; i < free; i++) {
block_data_ro = file_get_block(tr, file, i, &ref);
if (block_data_ro) {
file_block_put(block_data_ro, &ref);
printf("%s: file block %d, %lld not deleted\n", __func__, i,
data_to_block_num(block_data_ro));
break;
}
}
if (print_test_verbose) {
printf("%s: deleted %d file blocks\n", __func__, i);
file_print(tr, file);
}
assert(i == free);
}
}
static void file_test_commit(struct transaction* tr, bool commit) {
if (commit) {
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
}
}
static void file_move_expect(struct transaction* tr,
const char* src_path,
enum file_create_mode src_create,
const char* dest_path,
enum file_create_mode dest_create,
bool expect) {
struct file_handle file;
bool moved;
assert(!tr->failed);
open_test_file(tr, &file, src_path, src_create);
moved = file_move(tr, &file, dest_path, dest_create);
assert(moved == expect);
assert(!tr->failed);
file_close(&file);
}
static void file_move_expect_fail(struct transaction* tr,
const char* src_path,
enum file_create_mode src_create,
const char* dest_path,
enum file_create_mode dest_create) {
file_move_expect(tr, src_path, src_create, dest_path, dest_create, false);
}
static void file_move_expect_success(struct transaction* tr,
const char* src_path,
enum file_create_mode src_create,
const char* dest_path,
enum file_create_mode dest_create) {
file_move_expect(tr, src_path, src_create, dest_path, dest_create, true);
}
static void file_test_etc(struct transaction* tr,
bool commit,
const char* path,
enum file_create_mode create,
const char* move_path,
enum file_create_mode move_create,
int allocate,
int read,
int free,
bool delete,
int id) {
bool deleted;
struct file_handle file;
open_test_file(tr, &file, path, create);
file_test_commit(tr, commit);
if (move_path) {
file_move(tr, &file, move_path, move_create);
file_test_commit(tr, commit);
path = move_path;
}
file_test_open(tr, &file, allocate, read, free, id);
file_test_commit(tr, commit);
if (delete) {
if (print_test_verbose) {
printf("%s: delete file %s, at %lld:\n", __func__, path,
block_mac_to_block(tr, &file.block_mac));
}
deleted = file_delete(tr, path);
file_test_commit(tr, commit);
assert(deleted);
}
file_close(&file);
}
static void file_test(struct transaction* tr,
const char* path,
enum file_create_mode create,
int allocate,
int read,
int free,
bool delete,
int id) {
file_test_etc(tr, false, path, create, NULL, FILE_OPEN_NO_CREATE, allocate,
read, free, delete, id);
}
static void file_test_split_tr(struct transaction* tr,
const char* path,
enum file_create_mode create,
int open_count,
int allocate_file_index,
int allocate_index,
int allocate,
int read,
int free,
bool delete,
int id) {
bool deleted;
int i;
struct file_handle file[open_count];
assert(allocate_file_index <= allocate_index);
assert(allocate_index < open_count);
for (i = 0; i < open_count; i++) {
open_test_file(tr, &file[i], path,
(i == 0) ? create : FILE_OPEN_NO_CREATE);
assert(file[i].size == 0 || i > allocate_index);
if (i == allocate_index) {
file_test_open(tr, &file[allocate_file_index], allocate, 0, 0, id);
}
}
if (create == FILE_OPEN_NO_CREATE) {
transaction_fail(tr);
transaction_activate(tr);
for (i = 0; i < open_count; i++) {
assert(file[i].size == 0);
}
file_test_open(tr, &file[allocate_file_index], allocate, 0, 0, id);
}
assert(!tr->failed);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
for (i = 0; i < open_count; i++) {
assert(file[i].size != 0);
}
for (i = 1; i < open_count; i++) {
file_test_open(tr, &file[i], 0, read, 0, id);
assert(!tr->failed);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
}
file_test_open(tr, &file[0], 0, read, free, id);
if (delete) {
if (print_test_verbose) {
printf("%s: delete file %s, at %lld:\n", __func__, path,
block_mac_to_block(tr, &file[i].block_mac));
}
deleted = file_delete(tr, path);
assert(deleted);
}
for (i = 0; i < open_count; i++) {
file_close(&file[i]);
}
}
static void file_read_after_delete_test(struct transaction* tr) {
const char* path = "test1s";
struct file_handle file;
struct file_handle file2;
obj_ref_t ref = OBJ_REF_INITIAL_VALUE(ref);
const void* block_data_ro;
void* block_data_rw;
struct transaction tr2;
transaction_init(&tr2, tr->fs, true);
/* create test file */
open_test_file(tr, &file, path, FILE_OPEN_CREATE_EXCLUSIVE);
block_data_rw = file_get_block_write(tr, &file, 0, false, &ref);
file_block_put_dirty(tr, &file, 0, block_data_rw, &ref);
transaction_complete(tr);
assert(!tr->failed);
/* open in second transaction */
open_test_file(&tr2, &file2, path, FILE_OPEN_NO_CREATE);
/* delete and try read in same transaction */
transaction_activate(tr);
file_delete(tr, path);
block_data_ro = file_get_block(tr, &file, 0, &ref);
assert(!block_data_ro);
assert(tr->failed);
/* read in second transaction */
block_data_ro = file_get_block(&tr2, &file2, 0, &ref);
assert(block_data_ro);
file_block_put(block_data_ro, &ref);
/* read file then delete file */
transaction_activate(tr);
block_data_ro = file_get_block(tr, &file, 0, &ref);
assert(block_data_ro);
file_block_put(block_data_ro, &ref);
file_delete(tr, path);
transaction_complete(tr);
assert(!tr->failed);
/* try to read in both transactions */
transaction_activate(tr);
block_data_ro = file_get_block(tr, &file, 0, &ref);
assert(!block_data_ro);
assert(tr->failed);
block_data_ro = file_get_block(&tr2, &file2, 0, &ref);
assert(!block_data_ro);
assert(tr2.failed);
file_close(&file);
file_close(&file2);
transaction_activate(tr);
transaction_free(&tr2);
}
static const int file_test_block_count = BLOCK_SIZE > 64 ? 40 : 10;
static const int file_test_many_file_count = BLOCK_SIZE > 80 ? 40 : 10;
static void file_create1_small_test(struct transaction* tr) {
file_test(tr, "test1s", FILE_OPEN_CREATE_EXCLUSIVE, 0, 0, 0, false, 1);
}
static void file_write1_small_test(struct transaction* tr) {
file_test(tr, "test1s", FILE_OPEN_NO_CREATE, 1, 0, 0, false, 1);
}
static void file_delete1_small_test(struct transaction* tr) {
file_test(tr, "test1s", FILE_OPEN_NO_CREATE, 0, 1, 1, true, 1);
}
static void file_create_write_delete1_small_test(struct transaction* tr) {
file_test(tr, "test1s", FILE_OPEN_CREATE_EXCLUSIVE, 1, 1, 1, true, 1);
}
static void file_splittr1_small_test(struct transaction* tr) {
file_test_split_tr(tr, "test1s", FILE_OPEN_NO_CREATE, 1, 0, 0, 1, 1, 0,
false, 1);
}
static void file_splittr1o4_small_test(struct transaction* tr) {
#if 0
/*
* Disabled test: Current file code does not allow having the same
* file open more than once per transaction.
*/
file_test_split_tr(tr, "test1s", FILE_OPEN_NO_CREATE, 4, 1, 2, 1, 1, 0, false, 1);
#else
struct file_handle file[2];
open_test_file_etc(tr, &file[0], "test1s", FILE_OPEN_NO_CREATE, false);
open_test_file_etc(tr, &file[1], "test1s", FILE_OPEN_NO_CREATE, true);
file_close(&file[0]);
file_splittr1_small_test(tr);
#endif
}
static void file_splittr1c_small_test(struct transaction* tr) {
file_test_split_tr(tr, "test1s", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, 1, 1,
1, true, 1);
}
static void file_splittr1o4c_small_test(struct transaction* tr) {
#if 0
/*
* Disabled test: Current file code does not allow having the same
* file open more than once per transaction.
*/
file_test_split_tr(tr, "test1s", FILE_OPEN_CREATE_EXCLUSIVE, 4, 1, 2, 1, 1, 1, true, 1);
#endif
}
static void file_splittr1o4cl_small_test(struct transaction* tr) {
#if 0
/*
* Disabled test: Current file code does not allow having the same
* file open more than once per transaction.
*/
file_test_split_tr(tr, "test1s", FILE_OPEN_CREATE_EXCLUSIVE, 4, 2, 3, 1, 1, 1, true, 1);
#endif
}
static void file_create1_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_CREATE_EXCLUSIVE, 0, 0, 0, false, 1);
}
static void file_write1h_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, file_test_block_count / 2, 0, 0,
false, 1);
}
static void file_write1_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, file_test_block_count, 0, 0,
false, 1);
}
static void file_delete1_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, true, 1);
}
static void file_delete_create_write1_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, true, 1);
file_test(tr, "test1", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 1);
}
static void file_delete1_no_free_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, 0, 0, true, 1);
}
static void file_move12_test(struct transaction* tr) {
file_test_etc(tr, false, "test1", FILE_OPEN_NO_CREATE, "test2",
FILE_OPEN_CREATE_EXCLUSIVE, 0, file_test_block_count, 0,
false, 1);
}
static void file_move21_test(struct transaction* tr) {
file_test_etc(tr, true, "test2", FILE_OPEN_NO_CREATE, "test1",
FILE_OPEN_CREATE_EXCLUSIVE, 0, file_test_block_count, 0,
false, 1);
}
static void file_create2_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 2);
file_test(tr, "test2", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 3);
}
static void file_move_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 2);
file_test_etc(tr, false, "test1", FILE_OPEN_NO_CREATE, "test2",
FILE_OPEN_CREATE_EXCLUSIVE, 0, file_test_block_count, 0,
false, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, true, 2);
file_test(tr, "test1", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 2);
file_test(tr, "test2", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0,
0, false, 3);
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, file_test_block_count, false,
false, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count, false,
false, 3);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test3",
FILE_OPEN_NO_CREATE);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test2",
FILE_OPEN_CREATE_EXCLUSIVE);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test1",
FILE_OPEN_CREATE_EXCLUSIVE);
file_move_expect_success(tr, "test1", FILE_OPEN_NO_CREATE, "test1",
FILE_OPEN_NO_CREATE);
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test3",
FILE_OPEN_NO_CREATE);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test2",
FILE_OPEN_CREATE_EXCLUSIVE);
file_move_expect_fail(tr, "test1", FILE_OPEN_NO_CREATE, "test1",
FILE_OPEN_CREATE_EXCLUSIVE);
file_move_expect_success(tr, "test1", FILE_OPEN_NO_CREATE, "test1",
FILE_OPEN_NO_CREATE);
file_test_etc(tr, false, "test1", FILE_OPEN_NO_CREATE, "test2",
FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, false, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count, false,
false, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, true, 2);
}
static void file_delete2_test(struct transaction* tr) {
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, file_test_block_count, false,
false, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count, false,
false, 3);
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, file_test_block_count, 2);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, file_test_block_count,
file_test_block_count, file_test_block_count, 3);
}
static void file_create2_read_after_commit_test(struct transaction* tr) {
int i;
struct file_handle file[2];
open_test_file(tr, &file[0], "test1", FILE_OPEN_CREATE_EXCLUSIVE);
open_test_file(tr, &file[1], "test2", FILE_OPEN_CREATE_EXCLUSIVE);
for (i = 0; i < 2; i++) {
file_test_open(tr, &file[i], file_test_block_count, 0, 0, 2 + i);
}
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
for (i = 0; i < 2; i++) {
file_test_open(tr, &file[i], 0, file_test_block_count, 0, 2 + i);
file_close(&file[i]);
}
}
static void file_create3_conflict_test(struct transaction* tr) {
struct transaction tr1;
struct transaction tr2;
struct transaction tr3;
transaction_init(&tr1, tr->fs, true);
transaction_init(&tr2, tr->fs, true);
transaction_init(&tr3, tr->fs, true);
file_test(&tr1, "test1", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 4);
file_test(&tr2, "test1", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 5);
file_test(&tr3, "test2", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 6);
assert(!tr1.failed);
assert(!tr2.failed);
assert(!tr3.failed);
transaction_complete(&tr1);
transaction_complete(&tr2);
transaction_complete(&tr3);
assert(!tr1.failed);
assert(tr2.failed);
assert(!tr3.failed);
file_test(tr, "test1", FILE_OPEN_NO_CREATE, 0, 1, 1, true, 4);
file_test(tr, "test2", FILE_OPEN_NO_CREATE, 0, 1, 1, true, 6);
transaction_free(&tr1);
transaction_free(&tr2);
transaction_free(&tr3);
}
static void file_create_delete_2_transaction_test(struct transaction* tr) {
struct transaction tr1;
struct transaction tr2;
transaction_init(&tr1, tr->fs, true);
transaction_init(&tr2, tr->fs, true);
file_test(&tr1, "test1", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 7);
file_test(&tr2, "test2", FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 8);
assert(!tr1.failed);
assert(!tr2.failed);
transaction_complete(&tr1);
transaction_complete(&tr2);
assert(!tr1.failed);
assert(!tr2.failed);
transaction_activate(&tr1);
transaction_activate(&tr2);
file_test(&tr1, "test1", FILE_OPEN_NO_CREATE, 0, 1, 1, true, 7);
file_test(&tr2, "test2", FILE_OPEN_NO_CREATE, 0, 1, 1, true, 8);
assert(!tr1.failed);
assert(!tr2.failed);
transaction_complete(&tr1);
transaction_complete(&tr2);
assert(!tr1.failed);
assert(!tr2.failed);
transaction_free(&tr1);
transaction_free(&tr2);
}
static void file_create_many_test(struct transaction* tr) {
char path[10];
int i;
for (i = 0; i < file_test_many_file_count; i++) {
snprintf(path, sizeof(path), "test%d", i);
file_test(tr, path, FILE_OPEN_CREATE_EXCLUSIVE, 1, 0, 0, false, 7 + i);
}
}
static void file_delete_many_test(struct transaction* tr) {
char path[10];
int i;
files_print(tr);
for (i = 0; i < file_test_many_file_count; i++) {
snprintf(path, sizeof(path), "test%d", i);
file_test(tr, path, false, 0, 1, 1, true, 7 + i);
}
}
struct file_iterate_many_state {
struct file_iterate_state iter;
uint64_t found;
bool stop;
char last_path[10];
};
static bool file_iterate_many_iter(struct file_iterate_state* iter,
struct transaction* tr,
const struct block_mac* block_mac,
bool added,
bool removed) {
struct file_iterate_many_state* miter =
containerof(iter, struct file_iterate_many_state, iter);
const struct file_info* file_info;
obj_ref_t ref = OBJ_REF_INITIAL_VALUE(ref);
int i;
int ret;
uint64_t mask;
file_info = file_get_info(tr, block_mac, &ref);
ret = sscanf(file_info->path, "test%d", &i);
assert(strlen(file_info->path) < sizeof(miter->last_path));
strcpy(miter->last_path, file_info->path);
file_info_put(file_info, &ref);
assert(ret == 1);
mask = (1ULL << i);
assert(!(miter->found & mask));
miter->found |= mask;
return miter->stop;
}
static void file_iterate_many_test(struct transaction* tr) {
struct file_iterate_many_state state = {
.iter.file = file_iterate_many_iter,
.found = 0,
.stop = false,
};
uint64_t last_found = 0;
bool ret;
/* iterate over all files in one pass */
ret = file_iterate(tr, NULL, false, &state.iter);
assert(state.found = (1ull << file_test_many_file_count) - 1);
assert(ret);
ret = file_iterate(tr, NULL, true, &state.iter);
assert(ret);
/* lookup one file at a time */
state.found = 0;
state.stop = true;
ret = file_iterate(tr, NULL, false, &state.iter);
assert(ret);
while (state.found != last_found) {
last_found = state.found;
ret = file_iterate(tr, state.last_path, false, &state.iter);
assert(ret);
}
assert(state.found = (1ull << file_test_many_file_count) - 1);
ret = file_iterate(tr, NULL, true, &state.iter);
assert(ret);
}
static void file_allocate_all1_test(struct transaction* tr) {
file_allocate_all_test(tr, 1, 0, 1, "test1", FILE_OPEN_CREATE);
}
static void file_allocate_all_2tr_1_test(struct transaction* tr) {
file_allocate_all_test(tr, 2, 0, 1, "test1", FILE_OPEN_CREATE);
}
static void file_allocate_all_8tr_1_test(struct transaction* tr) {
file_allocate_all_test(tr, 8, 0, 1, "test1", FILE_OPEN_CREATE);
}
static void file_allocate_all_complete1_test(struct transaction* tr) {
file_allocate_all_test(tr, 1, 1, 1, "test1", FILE_OPEN_CREATE);
}
static void file_allocate_all_complete_multi1_test(struct transaction* tr) {
file_allocate_all_test(tr, 2, 8, 10, "test1", FILE_OPEN_CREATE);
}
static void file_allocate_leave_10_test(struct transaction* tr) {
file_allocate_all_test(tr, 1, 1, 10, "test1", FILE_OPEN_CREATE);
}
static void future_fs_version_test(struct transaction* tr) {
obj_ref_t super_ref = OBJ_REF_INITIAL_VALUE(super_ref);
struct fs* fs = tr->fs;
const struct key* key = fs->key;
struct block_device* dev = fs->dev;
struct block_device* super_dev = fs->super_dev;
const void* super_ro;
uint32_t* super_rw;
data_block_t block;
int ret;
transaction_complete(tr);
block = tr->fs->super_block[fs->super_block_version & 1];
super_ro = block_get_super(fs, block, &super_ref);
assert(super_ro);
super_rw = block_dirty(tr, super_ro, false);
assert(super_rw);
super_rw[7]++;
block_put_dirty_no_mac(super_rw, &super_ref);
block_cache_clean_transaction(tr);
transaction_free(tr);
ret = fs_init(fs, key, dev, super_dev, false);
assert(ret == -1);
ret = fs_init(fs, key, dev, super_dev, true);
assert(ret == -1);
fs->dev = dev;
fs->super_dev = super_dev;
transaction_init(tr, fs, false);
super_ro = block_get_super(fs, block, &super_ref);
assert(super_ro);
super_rw = block_dirty(tr, super_ro, false);
assert(super_rw);
super_rw[7]--;
block_put_dirty_no_mac(super_rw, &super_ref);
block_cache_clean_transaction(tr);
transaction_free(tr);
ret = fs_init(fs, key, dev, super_dev, false);
assert(ret == 0);
transaction_init(tr, fs, true);
}
#if 0
static void file_allocate_leave_10_test2(struct transaction *tr)
{
int i;
for (i = 1; i < 10; i++) {
file_allocate_all_test(tr, 1, 0, 1, "test1", FILE_OPEN_NO_CREATE);
file_allocate_all_test(tr, 1, 1, i, "test2", FILE_OPEN_CREATE);
file_delete(tr, "test2");
transaction_complete(tr);
assert(!tr->failed);
transaction_activate(tr);
}
}
#endif
#define TEST(a, ...) \
{ .name = #a, .func = (a), ##__VA_ARGS__ }
struct {
const char* name;
bool no_free_check;
void (*func)(struct transaction* tr);
} tests[] = {
TEST(empty_test),
TEST(empty_test),
TEST(block_tree_test),
TEST(block_set_test),
TEST(block_map_test),
TEST(allocate_frag_test, .no_free_check = true),
TEST(allocate_free_same_test, .no_free_check = true),
TEST(allocate_free_other_test, .no_free_check = true),
TEST(free_frag_rem_test, .no_free_check = true),
TEST(free_test),
TEST(allocate_2_transactions_test, .no_free_check = true),
TEST(free_2_transactions_same_test, .no_free_check = true),
TEST(free_2_transactions_same_test_2),
TEST(allocate_all_test),
TEST(block_tree_allocate_all_test),
TEST(file_create1_small_test),
TEST(file_write1_small_test),
TEST(file_delete1_small_test),
TEST(file_read_after_delete_test),
TEST(file_create1_small_test),
TEST(file_splittr1_small_test),
TEST(file_delete1_small_test),
TEST(file_create1_small_test),
TEST(file_splittr1o4_small_test),
TEST(file_delete1_small_test),
TEST(file_create_write_delete1_small_test),
TEST(file_splittr1c_small_test),
TEST(file_splittr1o4c_small_test),
TEST(file_splittr1o4cl_small_test),
TEST(file_create1_test),
TEST(file_write1h_test),
TEST(file_write1_test),
TEST(file_delete1_test),
TEST(file_create2_test),
TEST(file_delete2_test),
TEST(file_create2_read_after_commit_test),
TEST(file_delete2_test),
TEST(file_move_test),
TEST(file_create1_test),
TEST(file_write1_test),
TEST(file_move12_test),
TEST(file_move21_test),
TEST(file_delete1_test),
TEST(file_create3_conflict_test),
TEST(file_create_delete_2_transaction_test),
TEST(file_create_many_test),
TEST(file_create1_small_test),
TEST(file_write1_small_test),
TEST(file_write1_small_test),
TEST(file_delete1_small_test),
TEST(file_iterate_many_test),
TEST(file_delete_many_test),
TEST(file_create1_test),
TEST(file_allocate_all1_test),
TEST(file_write1_test),
TEST(file_delete1_no_free_test),
TEST(file_allocate_all1_test),
TEST(file_create_all_test),
TEST(file_create1_test),
TEST(file_write1_test),
TEST(file_delete_create_write1_test),
TEST(file_write1_test),
TEST(file_delete1_test),
TEST(file_allocate_all_2tr_1_test),
TEST(file_allocate_all_8tr_1_test),
TEST(file_create1_test),
TEST(file_allocate_all_complete1_test),
TEST(file_delete1_no_free_test),
TEST(file_allocate_all_complete1_test),
TEST(file_delete1_no_free_test),
TEST(file_create1_test),
TEST(file_allocate_all_complete_multi1_test),
TEST(file_delete1_no_free_test),
TEST(file_allocate_all_complete_multi1_test),
TEST(file_delete1_no_free_test),
TEST(file_create1_test),
TEST(file_allocate_leave_10_test),
TEST(file_create1_small_test),
TEST(file_write1_small_test),
TEST(file_delete1_small_test),
TEST(file_create1_small_test),
TEST(file_splittr1_small_test),
TEST(file_delete1_small_test),
TEST(file_create1_small_test),
TEST(file_splittr1o4_small_test),
TEST(file_delete1_small_test),
TEST(file_allocate_all1_test),
// TEST(file_write1_test),
// TEST(file_allocate_leave_10_test2),
TEST(file_delete1_no_free_test),
TEST(future_fs_version_test),
};
int main(int argc, const char* argv[]) {
// struct block_set_node *node;
struct block_device dev = {
.start_read = block_test_start_read,
.start_write = block_test_start_write,
.block_count = BLOCK_COUNT,
.block_size = 256,
.block_num_size = 8,
.mac_size = 16,
.tamper_detecting = true,
.io_ops = LIST_INITIAL_VALUE(dev.io_ops),
};
struct block_device dev256 = {
.start_read = block_test_start_read,
.start_write = block_test_start_write,
.block_count = 0x10000,
.block_size = 256,
.block_num_size = 2,
.mac_size = 2,
.tamper_detecting = true,
.io_ops = LIST_INITIAL_VALUE(dev256.io_ops),
};
struct fs fs = {
.dev = &dev,
.transactions = LIST_INITIAL_VALUE(fs.transactions),
.allocated = LIST_INITIAL_VALUE(fs.allocated),
.files =
{
.copy_on_write = true,
//.allow_copy_on_write = true,
},
};
struct transaction tr = {};
unsigned int i;
bool test_remount = true;
if (argc > 1) {
print_lookup = true;
}
assert(test_free_start < test_free_split);
assert(test_free_split < test_free_end);
stats_timer_reset();
block_tree_check_config(&dev);
block_tree_check_config(&dev256);
block_tree_check_config_done();
crypt_init();
block_cache_init();
fs_init(&fs, &key, &dev, &dev, true);
fs.reserved_count = 18; /* HACK: override default reserved space */
transaction_init(&tr, &fs, false);
for (i = 0; i < countof(tests); i++) {
transaction_activate(&tr);
printf("%s: start test: %s\n", __func__, tests[i].name);
tests[i].func(&tr);
transaction_complete(&tr);
assert(!block_cache_debug_get_ref_block_count());
if (!tests[i].no_free_check) {
full_assert(check_fs(&tr));
}
if (0) { // per test stats
stats_timer_print();
stats_timer_reset();
}
printf("%s: test done: %s\n", __func__, tests[i].name);
assert(!tr.failed);
if (test_remount) {
transaction_free(&tr);
fs_init(&fs, &key, &dev, &dev, false);
fs.reserved_count = 18; /* HACK: override default reserved space */
transaction_init(&tr, &fs, false);
}
}
full_assert(check_fs(&tr));
files_print(&tr);
block_set_print(&tr, &tr.fs->free);
stats_timer_print();
transaction_free(&tr);
crypt_shutdown();
printf("%s: done\n", __func__);
return 0;
}