blob: 999088d0ec42d248f349d18a6b00164a8260df06 [file] [log] [blame]
/*
* Copyright (C) 2015-2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <lk/macros.h>
#include <malloc.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <openssl/rand.h>
#include "block_allocator.h"
#include "block_cache.h"
#include "block_map.h"
#include "block_set.h"
#include "checkpoint.h"
#include "crypt.h"
#include "debug_stats.h"
#include "error_reporting_mock.h"
#include "file.h"
#include "transaction.h"
#include <time.h>
long gettime(uint32_t clock_id, uint32_t flags, int64_t* time) {
int ret;
struct timespec ts;
assert(!clock_id);
assert(!flags);
ret = clock_gettime(CLOCK_MONOTONIC, &ts);
assert(!ret);
*time = ts.tv_sec * 1000000000LL + ts.tv_nsec;
return 0;
}
#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);