/* * 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 #include #include #include #include #include #include #include #include #include #include "block_allocator.h" #include "block_cache.h" #include "block_map.h" #include "block_set.h" #include "checkpoint.h" #include "crypt.h" #include "debug_stats.h" #include "error_reporting_mock.h" #include "file.h" #include "transaction.h" #include 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; } #define FILE_SYSTEM_TEST "block_test" #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; const char* checkpoint_used_by_str; data_block_t checkpoint_used_by_block; }; static struct block blocks[BLOCK_COUNT]; static struct block blocks_backup[BLOCK_COUNT]; static const struct key key; static bool allow_repaired = false; static bool print_test_verbose = false; static bool print_block_tree_test_verbose = false; data_block_t block_test_fail_write_blocks; static inline void transaction_complete(struct transaction* tr) { return transaction_complete_etc(tr, false); } static inline void transaction_complete_update_checkpoint( struct transaction* tr) { return transaction_complete_etc(tr, true); } static void block_test_clear_reinit_etc(struct transaction* tr, uint32_t flags, bool swap, bool clear, size_t start) { struct fs* fs = tr->fs; const struct key* key = fs->key; struct block_device* dev = tr->fs->dev; struct block_device* super_dev = tr->fs->super_dev; int i; struct block tmp; int ret; transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); if (swap) { for (i = start; i < BLOCK_COUNT; ++i) { tmp = blocks[i]; blocks[i] = blocks_backup[i]; blocks_backup[i] = tmp; } } if (clear) { memset(&blocks[start], 0, (BLOCK_COUNT - start) * sizeof(struct block)); } ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, flags); assert(ret == 0); fs->reserved_count = 18; /* HACK: override default reserved space */ transaction_init(tr, fs, true); } static void block_test_swap_clear_reinit(struct transaction* tr, uint32_t flags) { block_test_clear_reinit_etc(tr, flags, true, true, 2); } static void block_test_swap_reinit(struct transaction* tr, uint32_t flags) { block_test_clear_reinit_etc(tr, flags, true, false, 2); } static void block_test_reinit(struct transaction* tr, uint32_t flags) { block_test_clear_reinit_etc(tr, flags, false, false, 2); } static void block_test_clear_superblock_reinit(struct transaction* tr, uint32_t flags) { block_test_clear_reinit_etc(tr, flags, false, true, 0); } 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, BLOCK_READ_SUCCESS); } static void block_test_start_write(struct block_device* dev, data_block_t block, const void* data, size_t data_size, bool sync) { assert(block < countof(blocks)); assert(data_size <= sizeof(blocks[block].data)); memcpy(blocks[block].data, data, data_size); block_cache_complete_write(dev, block, block < block_test_fail_write_blocks ? BLOCK_WRITE_FAILED : BLOCK_WRITE_SUCCESS); } #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; blocks[block].checkpoint_used_by_str = NULL; blocks[block].checkpoint_used_by_block = 0; } } static void block_set_used_by_etc(data_block_t block, const char* used_by_str, data_block_t used_by_block, bool checkpoint, bool force) { assert(block < countof(blocks)); if (checkpoint) { if (force || !blocks[block].checkpoint_used_by_str) { blocks[block].checkpoint_used_by_str = used_by_str; blocks[block].checkpoint_used_by_block = used_by_block; } assert(blocks[block].checkpoint_used_by_str == used_by_str); assert(blocks[block].checkpoint_used_by_block == used_by_block); } else { if (force || !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 bool block_set_replace_used_by(data_block_t block, const char* old_used_by_str, const char* new_used_by_str, data_block_t new_used_by_block, bool checkpoint) { if (!blocks[block].used_by_str || strcmp(blocks[block].used_by_str, old_used_by_str) != 0) { return false; } block_set_used_by_etc(block, new_used_by_str, new_used_by_block, checkpoint, true); return true; } static void block_set_used_by(data_block_t block, const char* used_by_str, data_block_t used_by_block) { block_set_used_by_etc(block, used_by_str, used_by_block, false, false); } 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, bool checkpoint) { 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_etc(block_mac_to_block(tr, &path.entry[0].block_mac), used_by_str, used_by_block, checkpoint, false); } while (block_tree_path_get_key(&path)) { for (i = 0; i < path.count; i++) { block_set_used_by_etc( block_mac_to_block(tr, &path.entry[i].block_mac), used_by_str, used_by_block, checkpoint, false); } if (mark_data_used) { block_set_used_by_etc(block_tree_path_get_data(&path), used_by_str, used_by_block, checkpoint, false); } 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), false); block_tree_path_next(&path); } } static void check_fs_prepare(struct transaction* tr) { data_block_t block; struct block_tree checkpoint_files = BLOCK_TREE_INITIAL_VALUE(checkpoint_files); size_t block_mac_size = tr->fs->block_num_size + tr->fs->mac_size; block_tree_init(&checkpoint_files, tr->fs->dev->block_size, tr->fs->block_num_size, block_mac_size, block_mac_size); 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, false); mark_block_tree_in_use(tr, &tr->fs->files, true, "files", 0, false); mark_files_in_use(tr); if (block_mac_valid(tr, &tr->fs->checkpoint)) { assert(checkpoint_read(tr, &tr->fs->checkpoint, &checkpoint_files, NULL)); block_set_used_by_etc(block_mac_to_block(tr, &tr->fs->checkpoint), "checkpoint", 0, true, false); mark_block_tree_in_use(tr, &checkpoint_files, true, "checkpoint_files", 0, true); mark_block_tree_in_use(tr, &tr->fs->checkpoint_free.block_tree, false, "checkpoint_free", 0, true); } } 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 && !blocks[block].checkpoint_used_by_str) { printf("block %" PRIu64 ", lost\n", block); valid = false; } } if (!valid) { printf("free:\n"); block_set_print(tr, &tr->fs->free); files_print(tr); printf("checkpoint free:\n"); block_set_print(tr, &tr->fs->checkpoint_free); } return valid; } static size_t get_fs_checkpoint_count(struct transaction* tr) { data_block_t block; size_t checkpoint_count = 0; check_fs_prepare(tr); for (block = 0; block < countof(blocks); block++) { if (blocks[block].checkpoint_used_by_str && strncmp("checkpoint", blocks[block].checkpoint_used_by_str, strlen("checkpoint")) == 0) { checkpoint_count++; } } assert(check_fs_finish(tr)); return checkpoint_count; } 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, UINT_MAX, 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 %" PRIu64 ":\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 %" PRIu64 ":\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); } /* clang-format off */ 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, }; /* clang-format on */ 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, %" PRIu64 ":\n", i, allocated[i]); block_set_print(tr, &tr->tmp_allocated); printf("tr.allocated after allocate %d, %" PRIu64 ":\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 super_block_write_failure_test(struct transaction* tr) { data_block_t block1 = block_allocate(tr); /* trigger a superblock write failure */ block_test_fail_write_blocks = 2; transaction_complete(tr); block_test_fail_write_blocks = 0; assert(tr->failed); transaction_activate(tr); assert(block_allocate(tr) == block1); transaction_complete(tr); transaction_activate(tr); block_free(tr, block1); } /* Test that block_put_dirty_discard actually drops the reference */ static void block_put_dirty_discard_test(struct transaction* tr) { struct obj_ref super_ref = OBJ_REF_INITIAL_VALUE(super_ref); struct fs* fs = tr->fs; const void* super_ro; uint32_t* super_rw; data_block_t block; 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); /* * As part of dropping the dirty block we need to clear it from the block * cache with block_cache_entry_discard_dirty, which requires that the block * not have any active references. Verify that block_put_dirty_discard drops * super_ref before trying to drop the block itself. */ block_put_dirty_discard(super_rw, &super_ref); } static void open_test_file_etc(struct transaction* tr, struct storage_file_handle* file, const char* path, enum file_create_mode create, enum file_op_result expected_result) { enum file_op_result result; result = file_open(tr, path, file, create, allow_repaired); if (print_test_verbose) { printf("%s: lookup file %s, create %d, got %" PRIu64 ":\n", __func__, path, create, block_mac_to_block(tr, &file->block_mac)); } assert(result == expected_result); assert(result != FILE_OP_SUCCESS || block_mac_valid(tr, &file->block_mac)); } static void open_test_file(struct transaction* tr, struct storage_file_handle* file, const char* path, enum file_create_mode create) { open_test_file_etc(tr, file, path, create, FILE_OP_SUCCESS); } 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 storage_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; struct obj_ref 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, false); 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 storage_file_handle file; enum file_op_result result; for (i = 0;; i++) { snprintf(path, sizeof(path), "test%08x", i); result = file_open(tr, path, &file, FILE_OPEN_CREATE_EXCLUSIVE, false); if (result != FILE_OP_SUCCESS) { 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 storage_file_handle* file, int allocate, int read, int free, int id) { int i; int* block_data_rw; struct obj_ref 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, %" PRIu64 ":\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, %" PRIu64 ":\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, %" PRIu64 " 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, enum file_op_result expected_result) { struct storage_file_handle file; enum file_op_result res; assert(!tr->failed); open_test_file(tr, &file, src_path, src_create); res = file_move(tr, &file, dest_path, dest_create, false); assert(res == expected_result); assert(!tr->failed); file_close(&file); } 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, FILE_OP_SUCCESS); } 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) { enum file_op_result delete_res; struct storage_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, allow_repaired); 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 %" PRIu64 ":\n", __func__, path, block_mac_to_block(tr, &file.block_mac)); } delete_res = file_delete(tr, path, allow_repaired); file_test_commit(tr, commit); assert(delete_res == FILE_OP_SUCCESS); } 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) { enum file_op_result delete_res; int i; struct storage_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 %" PRIu64 ":\n", __func__, path, block_mac_to_block(tr, &file[i].block_mac)); } delete_res = file_delete(tr, path, false); assert(delete_res == FILE_OP_SUCCESS); } 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 storage_file_handle file; struct storage_file_handle file2; struct obj_ref 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, false); 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, false); 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 storage_file_handle file[2]; open_test_file_etc(tr, &file[0], "test1s", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); open_test_file_etc(tr, &file[1], "test1s", FILE_OPEN_NO_CREATE, FILE_OP_ERR_ALREADY_OPEN); 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(tr, "test1", FILE_OPEN_NO_CREATE, "test3", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); file_move_expect(tr, "test1", FILE_OPEN_NO_CREATE, "test2", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_EXIST); file_move_expect(tr, "test1", FILE_OPEN_NO_CREATE, "test1", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_EXIST); 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(tr, "test1", FILE_OPEN_NO_CREATE, "test3", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); file_move_expect(tr, "test1", FILE_OPEN_NO_CREATE, "test2", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_EXIST); file_move_expect(tr, "test1", FILE_OPEN_NO_CREATE, "test1", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_EXIST); 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 storage_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; struct obj_ref 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; enum file_op_result res; /* iterate over all files in one pass */ res = file_iterate(tr, NULL, false, &state.iter, false); assert(state.found = (1ull << file_test_many_file_count) - 1); assert(res == FILE_OP_SUCCESS); res = file_iterate(tr, NULL, true, &state.iter, false); assert(res == FILE_OP_SUCCESS); /* lookup one file at a time */ state.found = 0; state.stop = true; res = file_iterate(tr, NULL, false, &state.iter, false); assert(res == FILE_OP_SUCCESS); while (state.found != last_found) { last_found = state.found; res = file_iterate(tr, state.last_path, false, &state.iter, false); assert(res == FILE_OP_SUCCESS); } assert(state.found = (1ull << file_test_many_file_count) - 1); res = file_iterate(tr, NULL, true, &state.iter, false); assert(res == FILE_OP_SUCCESS); } 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 fs_create_checkpoint(struct transaction* tr) { file_test(tr, "test_checkpoint", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete_update_checkpoint(tr); assert(!tr->failed); transaction_activate(tr); assert(get_fs_checkpoint_count(tr) > 3); file_delete(tr, "test_checkpoint", false); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(get_fs_checkpoint_count(tr) > 3); } static void fs_modify_with_checkpoint(struct transaction* tr) { file_test(tr, "test_checkpoint", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete_update_checkpoint(tr); assert(!tr->failed); /* modify the active filesystem with an active checkpoint */ transaction_activate(tr); file_test(tr, "test_checkpoint", FILE_OPEN_NO_CREATE, file_test_block_count, 0, 0, false, 2); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(get_fs_checkpoint_count(tr) > 3); file_delete(tr, "test_checkpoint", false); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(get_fs_checkpoint_count(tr) > 3); } static void fs_clear_checkpoint(struct transaction* tr) { assert(get_fs_checkpoint_count(tr) > 3); file_delete(tr, "test_checkpoint", false); /* at this point the file-system should be empty, all files are deleted */ transaction_complete_update_checkpoint(tr); /* * one block each for the checkpoint metadata block, checkpoint file tree * root, and checkpoint free set, no file blocks should exist now */ assert(get_fs_checkpoint_count(tr) == 3); transaction_activate(tr); } static void fs_rebuild_free_set(struct transaction* tr) { data_block_t block; ssize_t initial_free_count, free_count; bool pending_modifications = false; check_fs_prepare(tr); initial_free_count = 0; block = block_set_find_next_block(tr, &tr->fs->free, 1, true); while (block) { initial_free_count++; block = block_set_find_next_block(tr, &tr->fs->free, block + 1, true); } block = block_set_find_next_block(tr, &tr->allocated, 1, true); while (block) { /* we allocated blocks already in this transaction */ pending_modifications = true; initial_free_count--; block = block_set_find_next_block(tr, &tr->allocated, block + 1, true); } assert(initial_free_count > 0); if (print_test_verbose) { printf("files before rebuild:\n"); block_tree_print(tr, &tr->fs->files); printf("free set before rebuild:\n"); block_set_print(tr, &tr->fs->free); } tr->rebuild_free_set = true; transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); if (print_test_verbose) { printf("files after rebuild:\n"); block_tree_print(tr, &tr->fs->files); printf("free set after rebuild:\n"); block_set_print(tr, &tr->fs->free); } free_count = 0; block = block_set_find_next_block(tr, &tr->fs->free, 1, true); while (block) { free_count++; /* * free the old free set nodes, because we should have replaced them * with the rebuilt free set tree */ if (!block_set_replace_used_by(block, "free_tree_node", "free", 0, false)) { /* * Ensure that all blocks in the new free set were in the old free * set. We can only do this if there were no pending modifications * in the transaction; if there were the files tree has been * re-written and some blocks in the new free set will have been in * the previous file tree. */ if (!pending_modifications) { block_set_used_by(block, "free", 0); } } block = block_set_find_next_block(tr, &tr->fs->free, block + 1, true); } assert(free_count == initial_free_count); } static void fs_rebuild_fragmented_free_set(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); } /* * Delete half the files, leaving the free set fragmented so we have a free * set tree with depth > 1. */ for (i = 0; i < file_test_many_file_count; i += 2) { snprintf(path, sizeof(path), "test%d", i); file_test(tr, path, false, 0, 1, 1, true, 7 + i); } transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); fs_rebuild_free_set(tr); /* clean up */ for (i = 0; i < file_test_many_file_count; i++) { snprintf(path, sizeof(path), "test%d", i); file_delete(tr, path, false); } } static void fs_rebuild_with_pending_file(struct transaction* tr) { file_test(tr, "test_rebuild", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); fs_rebuild_free_set(tr); file_delete(tr, "test_rebuild", false); } static void fs_rebuild_with_pending_transaction(struct transaction* tr) { struct transaction other_tr; transaction_init(&other_tr, tr->fs, true); fs_rebuild_free_set(tr); assert(other_tr.failed); transaction_free(&other_tr); } static void fs_repair_flag(struct transaction* tr) { enum file_op_result result; struct storage_file_handle file; /* clear FS to reset repair flag */ transaction_fail(tr); block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); /* a non-existent file should not return FS_REPAIRED */ result = file_open(tr, "test_simulated_repair_nonexistent", &file, FILE_OPEN_NO_CREATE, false); assert(result == FILE_OP_ERR_NOT_FOUND); /* simulate an operation that requires setting the repair flag */ file_test(tr, "test_simulated_repair", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); tr->repaired = true; transaction_complete(tr); assert(!tr->failed); assert(fs_is_repaired(tr->fs)); transaction_activate(tr); /* globally acknowledge repair in test helpers */ allow_repaired = true; /* and again */ file_test(tr, "test_simulated_repair", FILE_OPEN_NO_CREATE, file_test_block_count, 0, 0, false, 2); tr->repaired = true; transaction_complete(tr); assert(!tr->failed); assert(fs_is_repaired(tr->fs)); transaction_activate(tr); /* Opening existing files must ack repair, because we reset the whole FS */ result = file_open(tr, "test_simulated_repair", &file, FILE_OPEN_NO_CREATE, false); assert(result == FILE_OP_ERR_FS_REPAIRED); /* a non-existent file should now report FS_REPAIRED */ result = file_open(tr, "test_simulated_repair_nonexistent", &file, FILE_OPEN_NO_CREATE, false); assert(result == FILE_OP_ERR_FS_REPAIRED); /* ...unless we allow a repaired FS */ result = file_open(tr, "test_simulated_repair_nonexistent", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_ERR_NOT_FOUND); result = file_open(tr, "test_simulated_repair", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_SUCCESS); file_close(&file); result = file_open(tr, "test_simulated_repair_nonexistent", &file, FILE_OPEN_CREATE, true); assert(result == FILE_OP_SUCCESS); file_close(&file); /* * re-initialize the fs to make sure we propagate the repaired state through * the super block */ transaction_fail(tr); block_test_reinit(tr, FS_INIT_FLAGS_NONE); assert(fs_is_repaired(tr->fs)); transaction_complete(tr); /* disallow repair globally in test helpers */ allow_repaired = false; block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); assert(!fs_is_repaired(tr->fs)); /* force the cleared superblock to be written */ file_test(tr, "test_simulated_repair", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); /* did the cleared repair flag persist? */ block_test_reinit(tr, FS_INIT_FLAGS_NONE); assert(!fs_is_repaired(tr->fs)); file_delete(tr, "test_simulated_repair", false); } /* * We don't allow repairs of the alternate FS, but we must persist it from the * main FS across usage of the alternate. */ static void fs_repair_with_alternate(struct transaction* tr) { enum file_op_result result; struct storage_file_handle file; /* simulate an operation that requires setting the repair flag */ file_test(tr, "test_repair_with_alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); tr->repaired = true; transaction_complete(tr); assert(!tr->failed); assert(fs_is_repaired(tr->fs)); transaction_activate(tr); /* * re-initialize the fs to make sure we propagate the repaired state through * the super block */ transaction_fail(tr); block_test_swap_clear_reinit( tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); assert(tr->fs->main_repaired); assert(!fs_is_repaired(tr->fs)); /* Opening a non-existent file does not return FILE_OP_ERR_FS_REPAIRED */ result = file_open(tr, "test_alternate_nonexistent", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_ERR_NOT_FOUND); /* Make sure we rewrite the alternate superblock */ file_test(tr, "test_alternate_create", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, true, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); /* But we can't do a repair on the alternate FS */ file_test(tr, "test_alternate_repair", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); tr->repaired = true; transaction_complete(tr); assert(tr->failed); assert(!fs_is_repaired(tr->fs)); /* Back to the main FS, which must still be repaired */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); assert(fs_is_repaired(tr->fs)); transaction_complete(tr); /* Clear the repair flag */ block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); assert(!fs_is_repaired(tr->fs)); } static void future_fs_version_test(struct transaction* tr) { struct obj_ref 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; uint16_t* super_rw; data_block_t block; int ret; struct storage_file_handle file; enum file_op_result open_result; /* offset of fs_version field in uint16_t words */ size_t fs_version_offset = 28 / 2; file_test(tr, "future_fs_version_file", FILE_OPEN_CREATE_EXCLUSIVE, 0, 0, 0, false, 1); 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[fs_version_offset]++; block_put_dirty_no_mac(super_rw, &super_ref, false); block_cache_clean_transaction(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "future_fs_version_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_DO_CLEAR); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "future_fs_version_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); /* * fs is not mountable, but we want to rewrite a block. Set up the bare * minimum required fs so we can rewrite the superblock manually. */ fs->dev = dev; fs->super_dev = super_dev; fs->readable = true; fs->writable = true; 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[fs_version_offset]--; block_put_dirty_no_mac(super_rw, &super_ref, false); block_cache_clean_transaction(tr); transaction_free(tr); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); transaction_init(tr, fs, true); file_test(tr, "future_fs_version_file", FILE_OPEN_NO_CREATE, 0, 0, 0, true /* delete */, 1); } /** * set_required_flags - helper to modify the required_flags super block field * @fs: File system object. * @required_flags: Flags to write into the required_flags field. * * Write @required_flags into the required_flags field of the active super * block. @fs may have been destroyed with fs_destroy() but @fs->dev and * @fs->super_dev must be reset correctly after it was destroyed. * * Returns: the previous required_flags value of the active super block. */ static uint16_t set_required_flags(struct fs* fs, uint16_t required_flags) { /* offset of required_flags field in uint16_t words */ size_t required_flags_offset = 30 / 2; struct obj_ref super_ref = OBJ_REF_INITIAL_VALUE(super_ref); struct transaction tr; struct block_device* dev = fs->dev; const void* super_ro; uint16_t* super_rw; data_block_t block; uint16_t old_required_flags; /* * If the fs was mounted read-only due to an error, we need to override this * state. We want to manually rewrite the superblock, so we have to override * the read-only state for block_dirty() to be allowed. */ transaction_init(&tr, fs, false); block = 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); old_required_flags = super_rw[required_flags_offset]; super_rw[required_flags_offset] = required_flags; block_put_dirty_no_mac(super_rw, &super_ref, false); block_cache_clean_transaction(&tr); transaction_free(&tr); block_cache_dev_destroy(dev); return old_required_flags; } static void unknown_required_flags_test(struct transaction* tr) { 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; int ret; uint16_t initial_required_flags; struct storage_file_handle file; enum file_op_result open_result; /* update when SUPER_BLOCK_REQUIRED_FLAGS_MASK changes in super.c */ uint16_t first_unsupported_required_flag = 0x2U; file_test(tr, "unknown_flags_file", FILE_OPEN_CREATE_EXCLUSIVE, 0, 0, 0, false, 1); transaction_complete(tr); transaction_free(tr); initial_required_flags = set_required_flags(fs, first_unsupported_required_flag); fs_destroy(fs); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_DO_CLEAR); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); /* * fs is not mountable, but we want to rewrite a block. Set up the bare * minimum required fs so we can rewrite the superblock manually. */ fs->dev = dev; fs->super_dev = super_dev; fs->readable = true; fs->writable = true; /* set all flag bits, this should fail unless we support 16 flags */ set_required_flags(fs, UINT16_MAX); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_DO_CLEAR); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); fs->dev = dev; fs->super_dev = super_dev; fs->readable = true; fs->writable = true; /* set highest flag bit, this should fail unless we support 16 flags */ set_required_flags(fs, 0x1U << 15); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_DO_CLEAR); assert(ret == 0); assert(!fs_is_readable(fs)); expect_errors(TRUSTY_STORAGE_ERROR_SUPERBLOCK_INVALID, 1); transaction_init(tr, fs, true); open_result = file_open(tr, "unknown_flags_file", &file, FILE_OPEN_NO_CREATE, false); assert(open_result == FILE_OP_ERR_FAILED); transaction_fail(tr); transaction_free(tr); fs_destroy(fs); block_cache_dev_destroy(dev); fs->dev = dev; fs->super_dev = super_dev; fs->readable = true; fs->writable = true; set_required_flags(fs, initial_required_flags); ret = fs_init(fs, FILE_SYSTEM_TEST, key, dev, super_dev, FS_INIT_FLAGS_NONE); assert(ret == 0); transaction_init(tr, fs, true); file_test(tr, "unknown_flags_file", FILE_OPEN_NO_CREATE, 0, 0, 0, true /* delete */, 1); } typedef data_block_t (*block_selector)(struct transaction* tr, unsigned int arg); static void fs_corruption_helper(struct transaction* tr, block_selector callback, unsigned int arg, bool expect_missing_file) { struct storage_file_handle file; enum file_op_result result; struct fs* fs = tr->fs; file_test(tr, "recovery", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "recovery", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); /* Corrupt the provided block */ memset(&blocks[callback(tr, arg)], 0, sizeof(struct block)); block_cache_dev_destroy(fs->dev); result = file_open(tr, "recovery", &file, FILE_OPEN_NO_CREATE, false); if (expect_missing_file) { assert(result == FILE_OP_ERR_FAILED); } else { assert(result == FILE_OP_SUCCESS); file_close(&file); } transaction_complete(tr); assert(!expect_missing_file || tr->failed); /* re-initialize the filesystem without recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_NONE); open_test_file_etc( tr, &file, "recovery", FILE_OPEN_NO_CREATE, expect_missing_file ? FILE_OP_ERR_FAILED : FILE_OP_SUCCESS); if (!expect_missing_file) { file_close(&file); } transaction_complete(tr); } static data_block_t select_files_block(struct transaction* tr, unsigned int depth) { struct block_tree_path path; block_tree_walk(tr, &tr->fs->files, 0, true, &path); assert(path.count > depth); return block_mac_to_block(tr, &path.entry[depth].block_mac); } static data_block_t select_free_block(struct transaction* tr, unsigned int depth) { struct block_tree_path path; block_tree_walk(tr, &tr->fs->free.block_tree, 0, true, &path); assert(path.count > depth); return block_mac_to_block(tr, &path.entry[depth].block_mac); } static data_block_t select_data_block(struct transaction* tr, unsigned int block) { struct storage_file_handle file; const void* block_data_ro = NULL; struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); data_block_t data_block_num; open_test_file_etc(tr, &file, "recovery", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); block_data_ro = file_get_block(tr, &file, block, &ref); assert(block_data_ro); data_block_num = data_to_block_num(block_data_ro - sizeof(struct iv)); file_block_put(block_data_ro, &ref); file_close(&file); return data_block_num; } static void create_and_delete(struct transaction* tr, const char* filename) { struct storage_file_handle file; open_test_file_etc(tr, &file, filename, FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_SUCCESS); transaction_complete(tr); assert(!tr->failed); file_close(&file); transaction_activate(tr); file_delete(tr, filename, false); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); } static void fs_recovery_clear_roots_test(struct transaction* tr) { /* * Create and delete a file to ensure that we have a root files block and * not just an empty super block. */ create_and_delete(tr, "ensure_roots"); assert(!tr->fs->needs_full_scan); /* Corrupt the root files block */ assert(select_files_block(tr, 0) == block_mac_to_block(tr, &tr->fs->files.root)); fs_corruption_helper(tr, select_files_block, 0, true); assert(tr->failed); /* * fs_corruption_helper hits the corrupted block while checking the file, * again while re-initializing the FS, and then again checking the file. */ expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 3); assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_BLOCK); assert(tr->fs->needs_full_scan); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* Did we recover correctly? */ create_and_delete(tr, "recovery"); assert(!tr->fs->needs_full_scan); /* Corrupt the root of the free list */ assert(select_free_block(tr, 0) == block_mac_to_block(tr, &tr->fs->free.block_tree.root)); fs_corruption_helper(tr, select_free_block, 0, false); assert(tr->failed); assert(tr->invalid_block_found); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 3); assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_FREE_SET); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* Did we recover correctly? */ create_and_delete(tr, "recovery"); assert(!tr->invalid_block_found); assert(!tr->fs->needs_full_scan); } static void fs_check_file_child_test(struct transaction* tr) { /* Create lots of files */ file_create_many_test(tr); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(!tr->fs->needs_full_scan); /* Corrupt a child in the files list */ fs_corruption_helper(tr, select_files_block, 1, false); /* * We corrupt a file tree block, but the file that fs_corruption_helper * probes is linked in a different file tree leaf and is unaffected by this, * so the transaction succeeds. */ /* Ensure that we detect this corruption */ assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_BLOCK); assert(tr->fs->needs_full_scan); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); /* recovery doesn't fix this error */ assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_BLOCK); assert(tr->fs->needs_full_scan); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); transaction_fail(tr); block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); assert(fs_check_full(tr->fs) == FS_CHECK_NO_ERROR); assert(!tr->fs->needs_full_scan); } static void fs_check_free_child_test(struct transaction* tr) { /* Fragment the free list */ allocate_frag_test(tr); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(!tr->fs->needs_full_scan); /* Corrupt a child in the free list */ fs_corruption_helper(tr, select_free_block, 1, false); assert(tr->failed); assert(tr->invalid_block_found); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 4); /* Ensure that we detect this corruption */ assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_FREE_SET); assert(tr->fs->needs_full_scan); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); /* recovery clear doesn't fix this error */ assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_FREE_SET); assert(tr->fs->needs_full_scan); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); transaction_fail(tr); block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); assert(fs_check_full(tr->fs) == FS_CHECK_NO_ERROR); assert(!tr->fs->needs_full_scan); } static void fs_check_sparse_file_test(struct transaction* tr) { struct storage_file_handle file; int i; int* block_data_rw; struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); size_t file_block_size = tr->fs->dev->block_size - sizeof(struct iv); open_test_file(tr, &file, "sparse_file", FILE_OPEN_CREATE); for (i = 0; i < 20; i += 5) { block_data_rw = file_get_block_write(tr, &file, i, true, &ref); assert(block_data_rw); block_data_rw[0] = i; block_data_rw[1] = ~i; file_block_put_dirty(tr, &file, i, block_data_rw, &ref); } file_set_size(tr, &file, i * file_block_size); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(fs_check_full(tr->fs) == FS_CHECK_NO_ERROR); assert(!tr->fs->needs_full_scan); file_close(&file); file_delete(tr, "sparse_file", false); } static void fs_corrupt_data_blocks_test(struct transaction* tr) { fs_corruption_helper(tr, select_data_block, 0, false); assert(!tr->failed); transaction_activate(tr); assert(fs_check_full(tr->fs) == FS_CHECK_NO_ERROR); assert(!tr->fs->needs_full_scan); /* file should still exist because we don't scan data blocks */ file_test(tr, "recovery", FILE_OPEN_NO_CREATE, 0, 0, 0, true, 1); transaction_complete(tr); assert(!tr->failed); assert(!tr->invalid_block_found); transaction_activate(tr); /* we can delete files with corrupted data blocks */ file_delete(tr, "recovery", false); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); create_and_delete(tr, "recovery"); assert(!tr->invalid_block_found); assert(!tr->fs->needs_full_scan); } static void fs_persist_needs_full_scan_test(struct transaction* tr) { const int* block_data_ro; struct storage_file_handle file; struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); assert(!tr->fs->needs_full_scan); transaction_complete(tr); /* clear the FS to get a known empty starting point */ block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); /* Corrupt a data block */ fs_corruption_helper(tr, select_data_block, 0, false); assert(!tr->failed); assert(!tr->fs->needs_full_scan); /* Try to read back the corrupted block */ open_test_file(tr, &file, "recovery", FILE_OPEN_NO_CREATE); block_data_ro = file_get_block(tr, &file, 0, &ref); assert(!block_data_ro); assert(tr->failed); assert(tr->fs->needs_full_scan); file_close(&file); /* Does the flag survive remount? */ block_test_reinit(tr, FS_INIT_FLAGS_NONE); assert(tr->fs->needs_full_scan); transaction_complete(tr); /* And a switch back and forth to the alternate FS? */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); assert(tr->fs->needs_full_scan); transaction_complete(tr); block_test_reinit(tr, FS_INIT_FLAGS_NONE); assert(tr->fs->needs_full_scan); transaction_complete(tr); /* Clear the FS, clearing the scan flag. */ block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); assert(!tr->fs->needs_full_scan); } static void fs_recovery_clear_test(struct transaction* tr) { struct storage_file_handle file; file_test(tr, "recovery", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); /* * Backup, then clear and re-initialize the filesystem with only clear * recovery enabled. */ block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* test file should be missing */ open_test_file_etc(tr, &file, "recovery", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_complete(tr); block_test_swap_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); /* test file should NOT be back */ open_test_file_etc(tr, &file, "recovery", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); } static void fs_restore_nonexistent_checkpoint_test(struct transaction* tr) { /* ensure that there is no existing checkpoint */ transaction_fail(tr); block_test_clear_superblock_reinit(tr, FS_INIT_FLAGS_NONE); file_test(tr, "nonexistent_checkpoint", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); /* attempt to restore a non-existent checkpoint */ block_test_reinit(tr, FS_INIT_FLAGS_RESTORE_CHECKPOINT); /* reads should work */ file_test(tr, "nonexistent_checkpoint", FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, false, 1); /* but writes should fail */ file_delete(tr, "nonexistent_checkpoint", allow_repaired); transaction_complete(tr); assert(tr->failed); block_test_reinit(tr, FS_INIT_FLAGS_NONE); file_delete(tr, "nonexistent_checkpoint", allow_repaired); } static void fs_auto_checkpoint_test(struct transaction* tr) { /* ensure that there is no existing checkpoint */ transaction_fail(tr); block_test_clear_superblock_reinit(tr, FS_INIT_FLAGS_NONE); assert(!block_mac_valid(tr, &tr->fs->checkpoint)); /* Create lots of files */ file_create_many_test(tr); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); fs_corruption_helper(tr, select_files_block, 1, false); assert(fs_check_full(tr->fs) == FS_CHECK_INVALID_BLOCK); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); /* test that we don't checkpoint a corrupt FS */ block_test_reinit(tr, FS_INIT_FLAGS_AUTO_CHECKPOINT); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 2); assert(!block_mac_valid(tr, &tr->fs->checkpoint)); transaction_fail(tr); block_test_clear_superblock_reinit(tr, FS_INIT_FLAGS_NONE); assert(!block_mac_valid(tr, &tr->fs->checkpoint)); file_test(tr, "auto_checkpoint", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); block_test_reinit(tr, FS_INIT_FLAGS_AUTO_CHECKPOINT); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); assert(block_mac_valid(tr, &tr->fs->checkpoint)); file_delete(tr, "auto_checkpoint", allow_repaired); transaction_complete(tr); assert(!tr->failed); block_test_reinit(tr, FS_INIT_FLAGS_RESTORE_CHECKPOINT); /* globally acknowledge repair in test helpers */ allow_repaired = true; file_test(tr, "auto_checkpoint", FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, true, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); } static void fs_recovery_restore_test(struct transaction* tr) { struct storage_file_handle file; enum file_op_result result; /* ensure that there is no existing checkpoint */ transaction_fail(tr); block_test_clear_superblock_reinit(tr, FS_INIT_FLAGS_NONE); /* create empty checkpoint block */ transaction_complete_update_checkpoint(tr); /* restore the empty checkpoint, do we still end up with a usable fs */ full_assert(check_fs(tr)); transaction_fail(tr); block_test_reinit(tr, FS_INIT_FLAGS_RESTORE_CHECKPOINT); full_assert(check_fs(tr)); /* globally acknowledge repair in test helpers */ allow_repaired = true; file_test(tr, "recovery_restore", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete_update_checkpoint(tr); assert(!tr->failed); transaction_activate(tr); /* check and delete the file */ file_test(tr, "recovery_restore", FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, true, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); /* make sure it's gone */ open_test_file_etc(tr, &file, "recovery_restore", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); file_test(tr, "recovery_not_in_checkpoint", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); full_assert(check_fs(tr)); block_test_reinit(tr, FS_INIT_FLAGS_RESTORE_CHECKPOINT); full_assert(check_fs(tr)); /* globally acknowledge repair in test helpers */ allow_repaired = true; /* * check and delete the file again (note this writes a new superblock, * persisting the repair flag) */ file_test(tr, "recovery_restore", FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, true, 1); result = file_open(tr, "recovery_not_in_checkpoint", &file, FILE_OPEN_NO_CREATE, false); assert(result == FILE_OP_ERR_FS_REPAIRED); result = file_open(tr, "recovery_not_in_checkpoint", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_ERR_NOT_FOUND); } /* Attempt to restore the checkpoint again */ static void fs_recovery_restore_test2(struct transaction* tr) { struct storage_file_handle file; enum file_op_result result; /* this file should only be in the checkpoint */ result = file_open(tr, "recovery_restore", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_ERR_NOT_FOUND); transaction_complete(tr); assert(!tr->failed); full_assert(check_fs(tr)); block_test_reinit(tr, FS_INIT_FLAGS_RESTORE_CHECKPOINT); full_assert(check_fs(tr)); /* check that the file is back again (and delete it) */ file_test(tr, "recovery_restore", FILE_OPEN_NO_CREATE, 0, file_test_block_count, 0, true, 1); /* but this one isn't */ result = file_open(tr, "recovery_not_in_checkpoint", &file, FILE_OPEN_NO_CREATE, false); assert(result == FILE_OP_ERR_FS_REPAIRED); result = file_open(tr, "recovery_not_in_checkpoint", &file, FILE_OPEN_NO_CREATE, true); assert(result == FILE_OP_ERR_NOT_FOUND); } static void fs_recovery_restore_cleanup(struct transaction* tr) { transaction_complete(tr); /* clear the FS to reset the repair flag */ block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); transaction_complete_update_checkpoint(tr); transaction_activate(tr); allow_repaired = false; } /* * Main and Alternate data states: * a) empty superblock, empty backing file * b) empty superblock, uncommitted backing file * c) non-empty superblock, non-empty backing file * d) non-empty superblock, empty backing file * * Transitions: * Main a-d -> Alternate a-c * Alternate a-c -> Alternate a-c * Alternate a-c -> Main a-c * * (d is not listed as a transition target as it should be replaced by an empty * superblock on init) */ static void fs_alternate_negative_test(struct transaction* tr) { struct storage_file_handle file; /* Initialize and commit a file to the FS */ file_test(tr, "main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* Swap and clear backing file without using alternate superblock */ block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); /* Ensure that the file is missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); /* Flush the cleared superblock here */ transaction_complete(tr); block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* * Ensure that the file is still missing (i.e. we did not create and restore * a backup) */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); } /* * Test that we can correctly alternate between a non-empty FS and an alternate * FS. * Tests the sequence: * Main c -> Alternate a -> Alternate a -> Alternate b -> Main c -> * Alternate b -> Alternate c -> Main c -> Alternate c */ static void fs_alternate_test(struct transaction* tr) { struct storage_file_handle file; /* Initialize and commit a file to the FS */ file_test(tr, "main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot into alternate and clear */ block_test_swap_clear_reinit( tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); /* test file should be missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* * simulate a reboot with an empty backing file, staying in alternate * mode */ block_test_reinit(tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); /* test file should still be missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); transaction_activate(tr); /* flush blocks to disk so the backing store is non-empty */ file_test_etc(tr, false, "alternate", FILE_OPEN_CREATE_EXCLUSIVE, "", FILE_OPEN_NO_CREATE, 80, 0, 0, false, 1); transaction_fail(tr); /* simulate a reboot with a cleared superblock but non-empty backing file */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* simulate a reboot, switching to main mode */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* test file should be available */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* and alternate test file should not be */ open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* simulate a reboot, switching to alternate mode */ block_test_swap_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); /* main test file should not exist */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); transaction_activate(tr); /* write a file */ file_test(tr, "alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* simulate reboot, still in alternate mode */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* simulate reboot back into main mode */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* test file should be back */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); /* and alternate file should be gone */ open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* simulate reboot back into alternate */ block_test_swap_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); /* alternate test file should be back */ open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); /* and regular file should be gone */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); } /* * Test that we can correctly backup from and recover a empty FS state. * Tests the sequence: * Main a -> Alternate a -> Alternate c -> Main a -> Alternate c -> Main a * -> Main b -> Alternate a -> Alternate c -> Main b */ static void fs_alternate_empty_test(struct transaction* tr) { struct storage_file_handle file; transaction_fail(tr); /* clear main fs */ block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); /* Ensure that the file is missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* swap to alternate and clear */ block_test_swap_clear_reinit( tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); /* Ensure that the file is still missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); transaction_activate(tr); /* write a file */ file_test(tr, "alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot back to alternate with data */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot to cleared main */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* Ensure that the file is missing */ open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* reboot to alternate to check that our data is still there */ block_test_swap_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot back to cleared main */ block_test_swap_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); /* flush blocks to disk so we have a non-empty backing file */ file_test_etc(tr, false, "main", FILE_OPEN_CREATE_EXCLUSIVE, "", FILE_OPEN_NO_CREATE, 80, 0, 0, false, 1); transaction_fail(tr); /* reboot with a non-empty backing file and cleared main superblock */ block_test_reinit(tr, FS_INIT_FLAGS_NONE); /* Ensure that the file is still missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); /* reboot to alternate, clearing */ block_test_swap_reinit( tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); /* write a file */ file_test(tr, "alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot to non-empty alternate */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); transaction_fail(tr); /* reboot to empty main with non-empty backing file */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* write a file */ file_test(tr, "main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_DO_CLEAR); } /* * Tests the interaction between alternate data and corruption recovery. * Tests the sequence: * Main c -> Alternate a -> Alternate c -> Recover from corrupt alternate -> * Main c -> Recover from corrupt main -> Alternate c */ static void fs_alternate_recovery_test(struct transaction* tr) { data_block_t block; struct storage_file_handle file; struct fs* fs = tr->fs; file_test(tr, "recovery_main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "recovery_main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); block_test_swap_clear_reinit( tr, FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALTERNATE_DATA); file_test(tr, "recovery_alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "recovery_alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* Corrupt the files root block */ block = block_mac_to_block(tr, &fs->files.root); memset(&blocks[block], 0, sizeof(struct block)); block_cache_dev_destroy(fs->dev); transaction_activate(tr); open_test_file_etc(tr, &file, "recovery_alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_FAILED); transaction_complete(tr); assert(tr->failed); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* re-initialize the filesystem without recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); open_test_file_etc(tr, &file, "recovery_alternate", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_FAILED); transaction_complete(tr); assert(tr->failed); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED | FS_INIT_FLAGS_ALTERNATE_DATA); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); file_test(tr, "recovery_alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); /* Swap to main and verify that our file still exists */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); /* alternate test file should be missing */ open_test_file_etc(tr, &file, "recovery_alternate", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_complete(tr); transaction_activate(tr); /* * Main test file should exist. We write to it to force the pending * superblock to get written before we do the corruption to avoid tripping a * dirty transaction assert */ file_test(tr, "recovery_main", FILE_OPEN_NO_CREATE, file_test_block_count, 0, 0, false, 2); transaction_complete(tr); assert(!tr->failed); /* Corrupt the files root block */ block = block_mac_to_block(tr, &fs->files.root); memset(&blocks[block], 0, sizeof(struct block)); block_cache_dev_destroy(fs->dev); transaction_activate(tr); open_test_file_etc(tr, &file, "recovery_main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_FAILED); transaction_complete(tr); assert(tr->failed); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* re-initialize the filesystem without recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_NONE); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); open_test_file_etc(tr, &file, "recovery_main", FILE_OPEN_CREATE_EXCLUSIVE, FILE_OP_ERR_FAILED); transaction_complete(tr); assert(tr->failed); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); /* re-initialize the filesystem with recovery enabled */ block_test_reinit(tr, FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED); expect_errors(TRUSTY_STORAGE_ERROR_BLOCK_MAC_MISMATCH, 1); file_test(tr, "recovery_main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); /* Swap to alternate and verify that our file still exists */ block_test_swap_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); /* alternate test file should exist */ open_test_file_etc(tr, &file, "recovery_alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* * Swap data back to main to finish off the test so we don't end up with a * mismatch during cleanup */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); } static void fs_alternate_init_test(struct transaction* tr) { struct storage_file_handle file; transaction_fail(tr); block_test_clear_superblock_reinit(tr, FS_INIT_FLAGS_NONE); /* Initialize and commit a file to the FS */ file_test(tr, "main", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot into alternate but do not pass clear flag */ block_test_swap_clear_reinit(tr, FS_INIT_FLAGS_ALTERNATE_DATA); /* test file should be missing */ open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_ERR_NOT_FOUND); transaction_fail(tr); transaction_activate(tr); /* Initialize and commit a file to the FS */ file_test(tr, "alternate", FILE_OPEN_CREATE_EXCLUSIVE, file_test_block_count, 0, 0, false, 1); transaction_complete(tr); assert(!tr->failed); transaction_activate(tr); open_test_file_etc(tr, &file, "alternate", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); transaction_complete(tr); assert(!tr->failed); /* reboot into main */ block_test_swap_reinit(tr, FS_INIT_FLAGS_NONE); open_test_file_etc(tr, &file, "main", FILE_OPEN_NO_CREATE, FILE_OP_SUCCESS); file_close(&file); } #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", false); 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(super_block_write_failure_test), TEST(block_put_dirty_discard_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(fs_create_checkpoint), TEST(fs_modify_with_checkpoint), TEST(fs_clear_checkpoint), TEST(fs_rebuild_free_set), TEST(fs_rebuild_fragmented_free_set), TEST(fs_rebuild_with_pending_file), TEST(fs_rebuild_with_pending_transaction), TEST(fs_repair_flag), TEST(fs_repair_with_alternate), TEST(future_fs_version_test), TEST(unknown_required_flags_test), TEST(fs_recovery_clear_roots_test), TEST(fs_check_file_child_test), TEST(fs_check_free_child_test), TEST(fs_check_sparse_file_test), TEST(fs_corrupt_data_blocks_test), TEST(fs_persist_needs_full_scan_test), TEST(fs_recovery_clear_test), TEST(fs_restore_nonexistent_checkpoint_test), TEST(fs_auto_checkpoint_test), TEST(fs_recovery_restore_cleanup), TEST(fs_recovery_restore_test), TEST(fs_recovery_restore_test2), TEST(fs_recovery_restore_cleanup), TEST(fs_alternate_negative_test), TEST(fs_alternate_test), TEST(fs_alternate_empty_test), TEST(fs_alternate_recovery_test), TEST(fs_alternate_init_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, FILE_SYSTEM_TEST, &key, &dev, &dev, FS_INIT_FLAGS_DO_CLEAR); fs.reserved_count = 18; /* HACK: override default reserved space */ transaction_init(&tr, &fs, false); for (i = 0; i < countof(tests); i++) { mock_error_report_clear(); 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_destroy(&fs); block_cache_dev_destroy(&dev); fs_init(&fs, FILE_SYSTEM_TEST, &key, &dev, &dev, FS_INIT_FLAGS_NONE); 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); fs_destroy(&fs); block_cache_dev_destroy(&dev); crypt_shutdown(); printf("%s: done\n", __func__); return 0; }