| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Lee Jones <lee.jones@linaro.org> |
| Date: Wed, 23 Mar 2022 12:25:29 +0000 |
| Subject: ANDROID: Incremental fs: Provide self-tests |
| |
| This is a squashed commit containing all of the Incremental FS self-tests. |
| |
| [CPNOTE: 20/07/21] Lee: Asked Paul to open an OoT bug to follow progress |
| |
| Signed-off-by: Lee Jones <lee.jones@linaro.org> |
| Change-Id: I8310228739e6619c2566b2f967ae8bf88a7670e0 |
| Signed-off-by: Lee Jones <joneslee@google.com> |
| --- |
| .../selftests/filesystems/incfs/.gitignore | 3 + |
| .../selftests/filesystems/incfs/Makefile | 11 + |
| .../selftests/filesystems/incfs/incfs_perf.c | 717 +++ |
| .../filesystems/incfs/incfs_stress.c | 322 ++ |
| .../selftests/filesystems/incfs/incfs_test.c | 4803 +++++++++++++++++ |
| .../selftests/filesystems/incfs/utils.c | 391 ++ |
| .../selftests/filesystems/incfs/utils.h | 71 + |
| 7 files changed, 6318 insertions(+) |
| create mode 100644 tools/testing/selftests/filesystems/incfs/.gitignore |
| create mode 100644 tools/testing/selftests/filesystems/incfs/Makefile |
| create mode 100644 tools/testing/selftests/filesystems/incfs/incfs_perf.c |
| create mode 100644 tools/testing/selftests/filesystems/incfs/incfs_stress.c |
| create mode 100644 tools/testing/selftests/filesystems/incfs/incfs_test.c |
| create mode 100644 tools/testing/selftests/filesystems/incfs/utils.c |
| create mode 100644 tools/testing/selftests/filesystems/incfs/utils.h |
| |
| diff --git a/tools/testing/selftests/filesystems/incfs/.gitignore b/tools/testing/selftests/filesystems/incfs/.gitignore |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/.gitignore |
| @@ -0,0 +1,3 @@ |
| +incfs_test |
| +incfs_stress |
| +incfs_perf |
| diff --git a/tools/testing/selftests/filesystems/incfs/Makefile b/tools/testing/selftests/filesystems/incfs/Makefile |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/Makefile |
| @@ -0,0 +1,11 @@ |
| +# SPDX-License-Identifier: GPL-2.0 |
| +CFLAGS += -D_FILE_OFFSET_BITS=64 -Wall -Werror -I../.. -I../../../../.. -fno-omit-frame-pointer -fsanitize=address -g |
| +LDLIBS := -llz4 -lzstd -lcrypto -lpthread -fsanitize=address |
| +TEST_GEN_PROGS := incfs_test incfs_stress incfs_perf |
| + |
| +include ../../lib.mk |
| + |
| +# Put after include ../../lib.mk since that changes $(TEST_GEN_PROGS) |
| +# Otherwise you get multiple targets, this becomes the default, and it's a mess |
| +EXTRA_SOURCES := utils.c |
| +$(TEST_GEN_PROGS) : $(EXTRA_SOURCES) |
| diff --git a/tools/testing/selftests/filesystems/incfs/incfs_perf.c b/tools/testing/selftests/filesystems/incfs/incfs_perf.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/incfs_perf.c |
| @@ -0,0 +1,717 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright 2020 Google LLC |
| + */ |
| +#include <errno.h> |
| +#include <fcntl.h> |
| +#include <getopt.h> |
| +#include <lz4.h> |
| +#include <stdbool.h> |
| +#include <stdint.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <sys/mount.h> |
| +#include <sys/stat.h> |
| +#include <time.h> |
| +#include <ctype.h> |
| +#include <unistd.h> |
| + |
| +#include "utils.h" |
| + |
| +#define err_msg(...) \ |
| + do { \ |
| + fprintf(stderr, "%s: (%d) ", TAG, __LINE__); \ |
| + fprintf(stderr, __VA_ARGS__); \ |
| + fprintf(stderr, " (%s)\n", strerror(errno)); \ |
| + } while (false) |
| + |
| +#define TAG "incfs_perf" |
| + |
| +struct options { |
| + int blocks; /* -b number of diff block sizes */ |
| + bool no_cleanup; /* -c don't clean up after */ |
| + const char *test_dir; /* -d working directory */ |
| + const char *file_types; /* -f sScCvV */ |
| + bool no_native; /* -n don't test native files */ |
| + bool no_random; /* -r don't do random reads*/ |
| + bool no_linear; /* -R random reads only */ |
| + size_t size; /* -s file size as power of 2 */ |
| + int tries; /* -t times to run test*/ |
| +}; |
| + |
| +enum flags { |
| + SHUFFLE = 1, |
| + COMPRESS = 2, |
| + VERIFY = 4, |
| + LAST_FLAG = 8, |
| +}; |
| + |
| +void print_help(void) |
| +{ |
| + puts( |
| + "incfs_perf. Performance test tool for incfs\n" |
| + "\tTests read performance of incfs by creating files of various types\n" |
| + "\tflushing caches and then reading them back.\n" |
| + "\tEach file is read with different block sizes and average\n" |
| + "\tthroughput in megabytes/second and memory usage are reported for\n" |
| + "\teach block size\n" |
| + "\tNative files are tested for comparison\n" |
| + "\tNative files are created in native folder, incfs files are created\n" |
| + "\tin src folder which is mounted on dst folder\n" |
| + "\n" |
| + "\t-bn (default 8) number of different block sizes, starting at 4096\n" |
| + "\t and doubling\n" |
| + "\t-c don't Clean up - leave files and mount point\n" |
| + "\t-d dir create directories in dir\n" |
| + "\t-fs|Sc|Cv|V restrict which files are created.\n" |
| + "\t s blocks not shuffled, S blocks shuffled\n" |
| + "\t c blocks not compress, C blocks compressed\n" |
| + "\t v files not verified, V files verified\n" |
| + "\t If a letter is omitted, both options are tested\n" |
| + "\t If no letter are given, incfs is not tested\n" |
| + "\t-n Don't test native files\n" |
| + "\t-r No random reads (sequential only)\n" |
| + "\t-R Random reads only (no sequential)\n" |
| + "\t-sn (default 30)File size as power of 2\n" |
| + "\t-tn (default 5) Number of tries per file. Results are averaged\n" |
| + ); |
| +} |
| + |
| +int parse_options(int argc, char *const *argv, struct options *options) |
| +{ |
| + signed char c; |
| + |
| + /* Set defaults here */ |
| + *options = (struct options){ |
| + .blocks = 8, |
| + .test_dir = ".", |
| + .tries = 5, |
| + .size = 30, |
| + }; |
| + |
| + /* Load options from command line here */ |
| + while ((c = getopt(argc, argv, "b:cd:f::hnrRs:t:")) != -1) { |
| + switch (c) { |
| + case 'b': |
| + options->blocks = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 'c': |
| + options->no_cleanup = true; |
| + break; |
| + |
| + case 'd': |
| + options->test_dir = optarg; |
| + break; |
| + |
| + case 'f': |
| + if (optarg) |
| + options->file_types = optarg; |
| + else |
| + options->file_types = "sS"; |
| + break; |
| + |
| + case 'h': |
| + print_help(); |
| + exit(0); |
| + |
| + case 'n': |
| + options->no_native = true; |
| + break; |
| + |
| + case 'r': |
| + options->no_random = true; |
| + break; |
| + |
| + case 'R': |
| + options->no_linear = true; |
| + break; |
| + |
| + case 's': |
| + options->size = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 't': |
| + options->tries = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + default: |
| + print_help(); |
| + return -EINVAL; |
| + } |
| + } |
| + |
| + options->size = 1L << options->size; |
| + |
| + return 0; |
| +} |
| + |
| +void shuffle(size_t *buffer, size_t size) |
| +{ |
| + size_t i; |
| + |
| + for (i = 0; i < size; ++i) { |
| + size_t j = random() * (size - i - 1) / RAND_MAX; |
| + size_t temp = buffer[i]; |
| + |
| + buffer[i] = buffer[j]; |
| + buffer[j] = temp; |
| + } |
| +} |
| + |
| +int get_free_memory(void) |
| +{ |
| + FILE *meminfo = fopen("/proc/meminfo", "re"); |
| + char field[256]; |
| + char value[256] = {}; |
| + |
| + if (!meminfo) |
| + return -ENOENT; |
| + |
| + while (fscanf(meminfo, "%[^:]: %s kB\n", field, value) == 2) { |
| + if (!strcmp(field, "MemFree")) |
| + break; |
| + *value = 0; |
| + } |
| + |
| + fclose(meminfo); |
| + |
| + if (!*value) |
| + return -ENOENT; |
| + |
| + return strtol(value, NULL, 10); |
| +} |
| + |
| +int write_data(int cmd_fd, int dir_fd, const char *name, size_t size, int flags) |
| +{ |
| + int fd = openat(dir_fd, name, O_RDWR | O_CLOEXEC); |
| + struct incfs_permit_fill permit_fill = { |
| + .file_descriptor = fd, |
| + }; |
| + int block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + size_t *blocks = malloc(sizeof(size_t) * block_count); |
| + int error = 0; |
| + size_t i; |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + uint8_t compressed_data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + struct incfs_fill_block fill_block = { |
| + .compression = COMPRESSION_NONE, |
| + .data_len = sizeof(data), |
| + .data = ptr_to_u64(data), |
| + }; |
| + |
| + if (!blocks) { |
| + err_msg("Out of memory"); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + if (fd == -1) { |
| + err_msg("Could not open file for writing %s", name); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + err_msg("Failed to call PERMIT_FILL"); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + for (i = 0; i < block_count; ++i) |
| + blocks[i] = i; |
| + |
| + if (flags & SHUFFLE) |
| + shuffle(blocks, block_count); |
| + |
| + if (flags & COMPRESS) { |
| + size_t comp_size = LZ4_compress_default( |
| + (char *)data, (char *)compressed_data, sizeof(data), |
| + ARRAY_SIZE(compressed_data)); |
| + |
| + if (comp_size <= 0) { |
| + error = -EBADMSG; |
| + goto out; |
| + } |
| + fill_block.compression = COMPRESSION_LZ4; |
| + fill_block.data = ptr_to_u64(compressed_data); |
| + fill_block.data_len = comp_size; |
| + } |
| + |
| + for (i = 0; i < block_count; ++i) { |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = 1, |
| + .fill_blocks = ptr_to_u64(&fill_block), |
| + }; |
| + |
| + fill_block.block_index = blocks[i]; |
| + int written = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + |
| + if (written != 1) { |
| + error = -errno; |
| + err_msg("Failed to write block %lu in file %s", i, |
| + name); |
| + break; |
| + } |
| + } |
| + |
| +out: |
| + free(blocks); |
| + close(fd); |
| + sync(); |
| + return error; |
| +} |
| + |
| +int measure_read_throughput_internal(const char *tag, int dir, const char *name, |
| + const struct options *options, bool random) |
| +{ |
| + int block; |
| + |
| + if (random) |
| + printf("%32s(random)", tag); |
| + else |
| + printf("%40s", tag); |
| + |
| + for (block = 0; block < options->blocks; ++block) { |
| + size_t buffer_size; |
| + char *buffer; |
| + int try; |
| + double time = 0; |
| + double throughput; |
| + int memory = 0; |
| + |
| + buffer_size = 1 << (block + 12); |
| + buffer = malloc(buffer_size); |
| + |
| + for (try = 0; try < options->tries; ++try) { |
| + int err; |
| + struct timespec start_time, end_time; |
| + off_t i; |
| + int fd; |
| + size_t offsets_size = options->size / buffer_size; |
| + size_t *offsets = |
| + malloc(offsets_size * sizeof(*offsets)); |
| + int start_memory, end_memory; |
| + |
| + if (!offsets) { |
| + err_msg("Not enough memory"); |
| + return -ENOMEM; |
| + } |
| + |
| + for (i = 0; i < offsets_size; ++i) |
| + offsets[i] = i * buffer_size; |
| + |
| + if (random) |
| + shuffle(offsets, offsets_size); |
| + |
| + err = drop_caches(); |
| + if (err) { |
| + err_msg("Failed to drop caches"); |
| + return err; |
| + } |
| + |
| + start_memory = get_free_memory(); |
| + if (start_memory < 0) { |
| + err_msg("Failed to get start memory"); |
| + return start_memory; |
| + } |
| + |
| + fd = openat(dir, name, O_RDONLY | O_CLOEXEC); |
| + if (fd == -1) { |
| + err_msg("Failed to open file"); |
| + return err; |
| + } |
| + |
| + err = clock_gettime(CLOCK_MONOTONIC, &start_time); |
| + if (err) { |
| + err_msg("Failed to get start time"); |
| + return err; |
| + } |
| + |
| + for (i = 0; i < offsets_size; ++i) |
| + if (pread(fd, buffer, buffer_size, |
| + offsets[i]) != buffer_size) { |
| + err_msg("Failed to read file"); |
| + err = -errno; |
| + goto fail; |
| + } |
| + |
| + err = clock_gettime(CLOCK_MONOTONIC, &end_time); |
| + if (err) { |
| + err_msg("Failed to get start time"); |
| + goto fail; |
| + } |
| + |
| + end_memory = get_free_memory(); |
| + if (end_memory < 0) { |
| + err_msg("Failed to get end memory"); |
| + return end_memory; |
| + } |
| + |
| + time += end_time.tv_sec - start_time.tv_sec; |
| + time += (end_time.tv_nsec - start_time.tv_nsec) / 1e9; |
| + |
| + close(fd); |
| + fd = -1; |
| + memory += start_memory - end_memory; |
| + |
| +fail: |
| + free(offsets); |
| + close(fd); |
| + if (err) |
| + return err; |
| + } |
| + |
| + throughput = options->size * options->tries / time; |
| + printf("%10.3e %10d", throughput, memory / options->tries); |
| + free(buffer); |
| + } |
| + |
| + printf("\n"); |
| + return 0; |
| +} |
| + |
| +int measure_read_throughput(const char *tag, int dir, const char *name, |
| + const struct options *options) |
| +{ |
| + int err = 0; |
| + |
| + if (!options->no_linear) |
| + err = measure_read_throughput_internal(tag, dir, name, options, |
| + false); |
| + |
| + if (!err && !options->no_random) |
| + err = measure_read_throughput_internal(tag, dir, name, options, |
| + true); |
| + return err; |
| +} |
| + |
| +int test_native_file(int dir, const struct options *options) |
| +{ |
| + const char *name = "file"; |
| + int fd; |
| + char buffer[4096] = {}; |
| + off_t i; |
| + int err; |
| + |
| + fd = openat(dir, name, O_CREAT | O_WRONLY | O_CLOEXEC, 0600); |
| + if (fd == -1) { |
| + err_msg("Could not open native file"); |
| + return -errno; |
| + } |
| + |
| + for (i = 0; i < options->size; i += sizeof(buffer)) |
| + if (pwrite(fd, buffer, sizeof(buffer), i) != sizeof(buffer)) { |
| + err_msg("Failed to write file"); |
| + err = -errno; |
| + goto fail; |
| + } |
| + |
| + close(fd); |
| + sync(); |
| + fd = -1; |
| + |
| + err = measure_read_throughput("native", dir, name, options); |
| + |
| +fail: |
| + close(fd); |
| + return err; |
| +} |
| + |
| +struct hash_block { |
| + char data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| +}; |
| + |
| +static struct hash_block *build_mtree(size_t size, char *root_hash, |
| + int *mtree_block_count) |
| +{ |
| + char data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + const int digest_size = SHA256_DIGEST_SIZE; |
| + const int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size; |
| + int block_count = 0; |
| + int hash_block_count = 0; |
| + int total_tree_block_count = 0; |
| + int tree_lvl_index[INCFS_MAX_MTREE_LEVELS] = {}; |
| + int tree_lvl_count[INCFS_MAX_MTREE_LEVELS] = {}; |
| + int levels_count = 0; |
| + int i, level; |
| + struct hash_block *mtree; |
| + |
| + if (size == 0) |
| + return 0; |
| + |
| + block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + hash_block_count = block_count; |
| + for (i = 0; hash_block_count > 1; i++) { |
| + hash_block_count = (hash_block_count + hash_per_block - 1) / |
| + hash_per_block; |
| + tree_lvl_count[i] = hash_block_count; |
| + total_tree_block_count += hash_block_count; |
| + } |
| + levels_count = i; |
| + |
| + for (i = 0; i < levels_count; i++) { |
| + int prev_lvl_base = (i == 0) ? total_tree_block_count : |
| + tree_lvl_index[i - 1]; |
| + |
| + tree_lvl_index[i] = prev_lvl_base - tree_lvl_count[i]; |
| + } |
| + |
| + *mtree_block_count = total_tree_block_count; |
| + mtree = calloc(total_tree_block_count, sizeof(*mtree)); |
| + /* Build level 0 hashes. */ |
| + for (i = 0; i < block_count; i++) { |
| + int block_index = tree_lvl_index[0] + i / hash_per_block; |
| + int block_off = (i % hash_per_block) * digest_size; |
| + char *hash_ptr = mtree[block_index].data + block_off; |
| + |
| + sha256(data, INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr); |
| + } |
| + |
| + /* Build higher levels of hash tree. */ |
| + for (level = 1; level < levels_count; level++) { |
| + int prev_lvl_base = tree_lvl_index[level - 1]; |
| + int prev_lvl_count = tree_lvl_count[level - 1]; |
| + |
| + for (i = 0; i < prev_lvl_count; i++) { |
| + int block_index = |
| + i / hash_per_block + tree_lvl_index[level]; |
| + int block_off = (i % hash_per_block) * digest_size; |
| + char *hash_ptr = mtree[block_index].data + block_off; |
| + |
| + sha256(mtree[i + prev_lvl_base].data, |
| + INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr); |
| + } |
| + } |
| + |
| + /* Calculate root hash from the top block */ |
| + sha256(mtree[0].data, INCFS_DATA_FILE_BLOCK_SIZE, root_hash); |
| + |
| + return mtree; |
| +} |
| + |
| +static int load_hash_tree(int cmd_fd, int dir, const char *name, |
| + struct hash_block *mtree, int mtree_block_count) |
| +{ |
| + int err; |
| + int i; |
| + int fd; |
| + struct incfs_fill_block *fill_block_array = |
| + calloc(mtree_block_count, sizeof(struct incfs_fill_block)); |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = mtree_block_count, |
| + .fill_blocks = ptr_to_u64(fill_block_array), |
| + }; |
| + struct incfs_permit_fill permit_fill; |
| + |
| + if (!fill_block_array) |
| + return -ENOMEM; |
| + |
| + for (i = 0; i < fill_blocks.count; i++) { |
| + fill_block_array[i] = (struct incfs_fill_block){ |
| + .block_index = i, |
| + .data_len = INCFS_DATA_FILE_BLOCK_SIZE, |
| + .data = ptr_to_u64(mtree[i].data), |
| + .flags = INCFS_BLOCK_FLAGS_HASH |
| + }; |
| + } |
| + |
| + fd = openat(dir, name, O_RDONLY | O_CLOEXEC); |
| + if (fd < 0) { |
| + err = errno; |
| + goto failure; |
| + } |
| + |
| + permit_fill.file_descriptor = fd; |
| + if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + err_msg("Failed to call PERMIT_FILL"); |
| + err = -errno; |
| + goto failure; |
| + } |
| + |
| + err = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + close(fd); |
| + if (err < fill_blocks.count) |
| + err = errno; |
| + else |
| + err = 0; |
| + |
| +failure: |
| + free(fill_block_array); |
| + return err; |
| +} |
| + |
| +int test_incfs_file(int dst_dir, const struct options *options, int flags) |
| +{ |
| + int cmd_file = openat(dst_dir, INCFS_PENDING_READS_FILENAME, |
| + O_RDONLY | O_CLOEXEC); |
| + int err; |
| + char name[4]; |
| + incfs_uuid_t id; |
| + char tag[256]; |
| + |
| + snprintf(name, sizeof(name), "%c%c%c", |
| + flags & SHUFFLE ? 'S' : 's', |
| + flags & COMPRESS ? 'C' : 'c', |
| + flags & VERIFY ? 'V' : 'v'); |
| + |
| + if (cmd_file == -1) { |
| + err_msg("Could not open command file"); |
| + return -errno; |
| + } |
| + |
| + if (flags & VERIFY) { |
| + char root_hash[INCFS_MAX_HASH_SIZE]; |
| + int mtree_block_count; |
| + struct hash_block *mtree = build_mtree(options->size, root_hash, |
| + &mtree_block_count); |
| + |
| + if (!mtree) { |
| + err_msg("Failed to build hash tree"); |
| + err = -ENOMEM; |
| + goto fail; |
| + } |
| + |
| + err = crypto_emit_file(cmd_file, NULL, name, &id, options->size, |
| + root_hash, "add_data"); |
| + |
| + if (!err) |
| + err = load_hash_tree(cmd_file, dst_dir, name, mtree, |
| + mtree_block_count); |
| + |
| + free(mtree); |
| + } else |
| + err = emit_file(cmd_file, NULL, name, &id, options->size, NULL); |
| + |
| + if (err) { |
| + err_msg("Failed to create file %s", name); |
| + goto fail; |
| + } |
| + |
| + if (write_data(cmd_file, dst_dir, name, options->size, flags)) |
| + goto fail; |
| + |
| + snprintf(tag, sizeof(tag), "incfs%s%s%s", |
| + flags & SHUFFLE ? "(shuffle)" : "", |
| + flags & COMPRESS ? "(compress)" : "", |
| + flags & VERIFY ? "(verify)" : ""); |
| + |
| + err = measure_read_throughput(tag, dst_dir, name, options); |
| + |
| +fail: |
| + close(cmd_file); |
| + return err; |
| +} |
| + |
| +bool skip(struct options const *options, int flag, char c) |
| +{ |
| + if (!options->file_types) |
| + return false; |
| + |
| + if (flag && strchr(options->file_types, tolower(c))) |
| + return true; |
| + |
| + if (!flag && strchr(options->file_types, toupper(c))) |
| + return true; |
| + |
| + return false; |
| +} |
| + |
| +int main(int argc, char *const *argv) |
| +{ |
| + struct options options; |
| + int err; |
| + const char *native_dir = "native"; |
| + const char *src_dir = "src"; |
| + const char *dst_dir = "dst"; |
| + int native_dir_fd = -1; |
| + int src_dir_fd = -1; |
| + int dst_dir_fd = -1; |
| + int block; |
| + int flags; |
| + |
| + err = parse_options(argc, argv, &options); |
| + if (err) |
| + return err; |
| + |
| + err = chdir(options.test_dir); |
| + if (err) { |
| + err_msg("Failed to change to %s", options.test_dir); |
| + return -errno; |
| + } |
| + |
| + /* Clean up any interrupted previous runs */ |
| + while (!umount(dst_dir)) |
| + ; |
| + |
| + err = remove_dir(native_dir) || remove_dir(src_dir) || |
| + remove_dir(dst_dir); |
| + if (err) |
| + return err; |
| + |
| + err = mkdir(native_dir, 0700); |
| + if (err) { |
| + err_msg("Failed to make directory %s", src_dir); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = mkdir(src_dir, 0700); |
| + if (err) { |
| + err_msg("Failed to make directory %s", src_dir); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = mkdir(dst_dir, 0700); |
| + if (err) { |
| + err_msg("Failed to make directory %s", src_dir); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = mount_fs_opt(dst_dir, src_dir, "readahead=0,rlog_pages=0", 0); |
| + if (err) { |
| + err_msg("Failed to mount incfs"); |
| + goto cleanup; |
| + } |
| + |
| + native_dir_fd = open(native_dir, O_RDONLY | O_CLOEXEC); |
| + src_dir_fd = open(src_dir, O_RDONLY | O_CLOEXEC); |
| + dst_dir_fd = open(dst_dir, O_RDONLY | O_CLOEXEC); |
| + if (native_dir_fd == -1 || src_dir_fd == -1 || dst_dir_fd == -1) { |
| + err_msg("Failed to open native, src or dst dir"); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + printf("%40s", ""); |
| + for (block = 0; block < options.blocks; ++block) |
| + printf("%21d", 1 << (block + 12)); |
| + printf("\n"); |
| + |
| + if (!err && !options.no_native) |
| + err = test_native_file(native_dir_fd, &options); |
| + |
| + for (flags = 0; flags < LAST_FLAG && !err; ++flags) { |
| + if (skip(&options, flags & SHUFFLE, 's') || |
| + skip(&options, flags & COMPRESS, 'c') || |
| + skip(&options, flags & VERIFY, 'v')) |
| + continue; |
| + err = test_incfs_file(dst_dir_fd, &options, flags); |
| + } |
| + |
| +cleanup: |
| + close(native_dir_fd); |
| + close(src_dir_fd); |
| + close(dst_dir_fd); |
| + if (!options.no_cleanup) { |
| + umount(dst_dir); |
| + remove_dir(native_dir); |
| + remove_dir(dst_dir); |
| + remove_dir(src_dir); |
| + } |
| + |
| + return err; |
| +} |
| diff --git a/tools/testing/selftests/filesystems/incfs/incfs_stress.c b/tools/testing/selftests/filesystems/incfs/incfs_stress.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/incfs_stress.c |
| @@ -0,0 +1,322 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright 2020 Google LLC |
| + */ |
| +#include <errno.h> |
| +#include <fcntl.h> |
| +#include <getopt.h> |
| +#include <pthread.h> |
| +#include <stdbool.h> |
| +#include <stdint.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <sys/mount.h> |
| +#include <sys/stat.h> |
| +#include <unistd.h> |
| + |
| +#include "utils.h" |
| + |
| +#define err_msg(...) \ |
| + do { \ |
| + fprintf(stderr, "%s: (%d) ", TAG, __LINE__); \ |
| + fprintf(stderr, __VA_ARGS__); \ |
| + fprintf(stderr, " (%s)\n", strerror(errno)); \ |
| + } while (false) |
| + |
| +#define TAG "incfs_stress" |
| + |
| +struct options { |
| + bool no_cleanup; /* -c */ |
| + const char *test_dir; /* -d */ |
| + unsigned int rng_seed; /* -g */ |
| + int num_reads; /* -n */ |
| + int readers; /* -r */ |
| + int size; /* -s */ |
| + int timeout; /* -t */ |
| +}; |
| + |
| +struct read_data { |
| + const char *filename; |
| + int dir_fd; |
| + size_t filesize; |
| + int num_reads; |
| + unsigned int rng_seed; |
| +}; |
| + |
| +int cancel_threads; |
| + |
| +int parse_options(int argc, char *const *argv, struct options *options) |
| +{ |
| + signed char c; |
| + |
| + /* Set defaults here */ |
| + *options = (struct options){ |
| + .test_dir = ".", |
| + .num_reads = 1000, |
| + .readers = 10, |
| + .size = 10, |
| + }; |
| + |
| + /* Load options from command line here */ |
| + while ((c = getopt(argc, argv, "cd:g:n:r:s:t:")) != -1) { |
| + switch (c) { |
| + case 'c': |
| + options->no_cleanup = true; |
| + break; |
| + |
| + case 'd': |
| + options->test_dir = optarg; |
| + break; |
| + |
| + case 'g': |
| + options->rng_seed = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 'n': |
| + options->num_reads = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 'r': |
| + options->readers = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 's': |
| + options->size = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 't': |
| + options->timeout = strtol(optarg, NULL, 10); |
| + break; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +void *reader(void *data) |
| +{ |
| + struct read_data *read_data = (struct read_data *)data; |
| + int i; |
| + int fd = -1; |
| + void *buffer = malloc(read_data->filesize); |
| + |
| + if (!buffer) { |
| + err_msg("Failed to alloc read buffer"); |
| + goto out; |
| + } |
| + |
| + fd = openat(read_data->dir_fd, read_data->filename, |
| + O_RDONLY | O_CLOEXEC); |
| + if (fd == -1) { |
| + err_msg("Failed to open file"); |
| + goto out; |
| + } |
| + |
| + for (i = 0; i < read_data->num_reads && !cancel_threads; ++i) { |
| + off_t offset = rnd(read_data->filesize, &read_data->rng_seed); |
| + size_t count = |
| + rnd(read_data->filesize - offset, &read_data->rng_seed); |
| + ssize_t err = pread(fd, buffer, count, offset); |
| + |
| + if (err != count) |
| + err_msg("failed to read with value %lu", err); |
| + } |
| + |
| +out: |
| + close(fd); |
| + free(read_data); |
| + free(buffer); |
| + return NULL; |
| +} |
| + |
| +int write_data(int cmd_fd, int dir_fd, const char *name, size_t size) |
| +{ |
| + int fd = openat(dir_fd, name, O_RDWR | O_CLOEXEC); |
| + struct incfs_permit_fill permit_fill = { |
| + .file_descriptor = fd, |
| + }; |
| + int error = 0; |
| + int i; |
| + int block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + |
| + if (fd == -1) { |
| + err_msg("Could not open file for writing %s", name); |
| + return -errno; |
| + } |
| + |
| + if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + err_msg("Failed to call PERMIT_FILL"); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + for (i = 0; i < block_count; ++i) { |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + size_t block_size = |
| + size > i * INCFS_DATA_FILE_BLOCK_SIZE ? |
| + INCFS_DATA_FILE_BLOCK_SIZE : |
| + size - (i * INCFS_DATA_FILE_BLOCK_SIZE); |
| + struct incfs_fill_block fill_block = { |
| + .compression = COMPRESSION_NONE, |
| + .block_index = i, |
| + .data_len = block_size, |
| + .data = ptr_to_u64(data), |
| + }; |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = 1, |
| + .fill_blocks = ptr_to_u64(&fill_block), |
| + }; |
| + int written = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + |
| + if (written != 1) { |
| + error = -errno; |
| + err_msg("Failed to write block %d in file %s", i, name); |
| + break; |
| + } |
| + } |
| +out: |
| + close(fd); |
| + return error; |
| +} |
| + |
| +int test_files(int src_dir, int dst_dir, struct options const *options) |
| +{ |
| + unsigned int seed = options->rng_seed; |
| + int cmd_file = openat(dst_dir, INCFS_PENDING_READS_FILENAME, |
| + O_RDONLY | O_CLOEXEC); |
| + int err; |
| + const char *name = "001"; |
| + incfs_uuid_t id; |
| + size_t size; |
| + int i; |
| + pthread_t *threads = NULL; |
| + |
| + size = 1 << (rnd(options->size, &seed) + 12); |
| + size += rnd(size, &seed); |
| + |
| + if (cmd_file == -1) { |
| + err_msg("Could not open command file"); |
| + return -errno; |
| + } |
| + |
| + err = emit_file(cmd_file, NULL, name, &id, size, NULL); |
| + if (err) { |
| + err_msg("Failed to create file %s", name); |
| + return err; |
| + } |
| + |
| + threads = malloc(sizeof(pthread_t) * options->readers); |
| + if (!threads) { |
| + err_msg("Could not allocate memory for threads"); |
| + return -ENOMEM; |
| + } |
| + |
| + for (i = 0; i < options->readers; ++i) { |
| + struct read_data *read_data = malloc(sizeof(*read_data)); |
| + |
| + if (!read_data) { |
| + err_msg("Failed to allocate read_data"); |
| + err = -ENOMEM; |
| + break; |
| + } |
| + |
| + *read_data = (struct read_data){ |
| + .filename = name, |
| + .dir_fd = dst_dir, |
| + .filesize = size, |
| + .num_reads = options->num_reads, |
| + .rng_seed = seed, |
| + }; |
| + |
| + rnd(0, &seed); |
| + |
| + err = pthread_create(threads + i, 0, reader, read_data); |
| + if (err) { |
| + err_msg("Failed to create thread"); |
| + free(read_data); |
| + break; |
| + } |
| + } |
| + |
| + if (err) |
| + cancel_threads = 1; |
| + else |
| + err = write_data(cmd_file, dst_dir, name, size); |
| + |
| + for (; i > 0; --i) { |
| + if (pthread_join(threads[i - 1], NULL)) { |
| + err_msg("FATAL: failed to join thread"); |
| + exit(-errno); |
| + } |
| + } |
| + |
| + free(threads); |
| + close(cmd_file); |
| + return err; |
| +} |
| + |
| +int main(int argc, char *const *argv) |
| +{ |
| + struct options options; |
| + int err; |
| + const char *src_dir = "src"; |
| + const char *dst_dir = "dst"; |
| + int src_dir_fd = -1; |
| + int dst_dir_fd = -1; |
| + |
| + err = parse_options(argc, argv, &options); |
| + if (err) |
| + return err; |
| + |
| + err = chdir(options.test_dir); |
| + if (err) { |
| + err_msg("Failed to change to %s", options.test_dir); |
| + return -errno; |
| + } |
| + |
| + err = remove_dir(src_dir) || remove_dir(dst_dir); |
| + if (err) |
| + return err; |
| + |
| + err = mkdir(src_dir, 0700); |
| + if (err) { |
| + err_msg("Failed to make directory %s", src_dir); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = mkdir(dst_dir, 0700); |
| + if (err) { |
| + err_msg("Failed to make directory %s", src_dir); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = mount_fs(dst_dir, src_dir, options.timeout); |
| + if (err) { |
| + err_msg("Failed to mount incfs"); |
| + goto cleanup; |
| + } |
| + |
| + src_dir_fd = open(src_dir, O_RDONLY | O_CLOEXEC); |
| + dst_dir_fd = open(dst_dir, O_RDONLY | O_CLOEXEC); |
| + if (src_dir_fd == -1 || dst_dir_fd == -1) { |
| + err_msg("Failed to open src or dst dir"); |
| + err = -errno; |
| + goto cleanup; |
| + } |
| + |
| + err = test_files(src_dir_fd, dst_dir_fd, &options); |
| + |
| +cleanup: |
| + close(src_dir_fd); |
| + close(dst_dir_fd); |
| + if (!options.no_cleanup) { |
| + umount(dst_dir); |
| + remove_dir(dst_dir); |
| + remove_dir(src_dir); |
| + } |
| + |
| + return err; |
| +} |
| diff --git a/tools/testing/selftests/filesystems/incfs/incfs_test.c b/tools/testing/selftests/filesystems/incfs/incfs_test.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/incfs_test.c |
| @@ -0,0 +1,4803 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright 2018 Google LLC |
| + */ |
| +#define _GNU_SOURCE |
| + |
| +#include <alloca.h> |
| +#include <dirent.h> |
| +#include <errno.h> |
| +#include <fcntl.h> |
| +#include <lz4.h> |
| +#include <poll.h> |
| +#include <stdbool.h> |
| +#include <stdint.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <time.h> |
| +#include <unistd.h> |
| +#include <zstd.h> |
| + |
| +#include <sys/inotify.h> |
| +#include <sys/mman.h> |
| +#include <sys/mount.h> |
| +#include <sys/stat.h> |
| +#include <sys/types.h> |
| +#include <sys/wait.h> |
| +#include <sys/xattr.h> |
| +#include <sys/statvfs.h> |
| + |
| +#include <linux/random.h> |
| +#include <linux/stat.h> |
| +#include <linux/unistd.h> |
| + |
| +#include <openssl/pem.h> |
| +#include <openssl/x509.h> |
| + |
| +#include <kselftest.h> |
| +#include <include/uapi/linux/fsverity.h> |
| + |
| +#include "utils.h" |
| + |
| +/* Can't include uapi/linux/fs.h because it clashes with mount.h */ |
| +#define FS_IOC_GETFLAGS _IOR('f', 1, long) |
| +#define FS_VERITY_FL 0x00100000 /* Verity protected inode */ |
| + |
| +#define TEST_SKIP 2 |
| +#define TEST_FAILURE 1 |
| +#define TEST_SUCCESS 0 |
| + |
| +#define INCFS_ROOT_INODE 0 |
| + |
| +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
| +#define le16_to_cpu(x) (x) |
| +#define le32_to_cpu(x) (x) |
| +#define le64_to_cpu(x) (x) |
| +#else |
| +#error Big endian not supported! |
| +#endif |
| + |
| +struct { |
| + int file; |
| + int test; |
| + bool verbose; |
| +} options; |
| + |
| +#define TESTCOND(condition) \ |
| + do { \ |
| + if (!(condition)) { \ |
| + ksft_print_msg("%s failed %d\n", \ |
| + __func__, __LINE__); \ |
| + goto out; \ |
| + } else if (options.verbose) \ |
| + ksft_print_msg("%s succeeded %d\n", \ |
| + __func__, __LINE__); \ |
| + } while (false) |
| + |
| +#define TEST(statement, condition) \ |
| + do { \ |
| + statement; \ |
| + TESTCOND(condition); \ |
| + } while (false) |
| + |
| +#define TESTEQUAL(statement, res) \ |
| + TESTCOND((statement) == (res)) |
| + |
| +#define TESTNE(statement, res) \ |
| + TESTCOND((statement) != (res)) |
| + |
| +#define TESTSYSCALL(statement) \ |
| + do { \ |
| + int res = statement; \ |
| + \ |
| + if (res) \ |
| + ksft_print_msg("Failed: %s (%d)\n", \ |
| + strerror(errno), errno); \ |
| + TESTEQUAL(res, 0); \ |
| + } while (false) |
| + |
| +void print_bytes(const void *data, size_t size) |
| +{ |
| + const uint8_t *bytes = data; |
| + int i; |
| + |
| + for (i = 0; i < size; ++i) { |
| + if (i % 0x10 == 0) |
| + printf("%08x:", i); |
| + printf("%02x ", (unsigned int) bytes[i]); |
| + if (i % 0x10 == 0x0f) |
| + printf("\n"); |
| + } |
| + |
| + if (i % 0x10 != 0) |
| + printf("\n"); |
| +} |
| + |
| +struct hash_block { |
| + char data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| +}; |
| + |
| +struct test_signature { |
| + void *data; |
| + size_t size; |
| + |
| + char add_data[100]; |
| + size_t add_data_size; |
| +}; |
| + |
| +struct test_file { |
| + int index; |
| + incfs_uuid_t id; |
| + char *name; |
| + off_t size; |
| + char root_hash[INCFS_MAX_HASH_SIZE]; |
| + struct hash_block *mtree; |
| + int mtree_block_count; |
| + struct test_signature sig; |
| + unsigned char *verity_sig; |
| + size_t verity_sig_size; |
| +}; |
| + |
| +struct test_files_set { |
| + struct test_file *files; |
| + int files_count; |
| +}; |
| + |
| +struct linux_dirent64 { |
| + uint64_t d_ino; |
| + int64_t d_off; |
| + unsigned short d_reclen; |
| + unsigned char d_type; |
| + char d_name[0]; |
| +} __packed; |
| + |
| +struct test_files_set get_test_files_set(void) |
| +{ |
| + static struct test_file files[] = { |
| + { .index = 0, .name = "file_one_byte", .size = 1 }, |
| + { .index = 1, |
| + .name = "file_one_block", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE }, |
| + { .index = 2, |
| + .name = "file_one_and_a_half_blocks", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE + |
| + INCFS_DATA_FILE_BLOCK_SIZE / 2 }, |
| + { .index = 3, |
| + .name = "file_three", |
| + .size = 300 * INCFS_DATA_FILE_BLOCK_SIZE + 3 }, |
| + { .index = 4, |
| + .name = "file_four", |
| + .size = 400 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 5, |
| + .name = "file_five", |
| + .size = 500 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 6, |
| + .name = "file_six", |
| + .size = 600 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 7, |
| + .name = "file_seven", |
| + .size = 700 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 8, |
| + .name = "file_eight", |
| + .size = 800 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 9, |
| + .name = "file_nine", |
| + .size = 900 * INCFS_DATA_FILE_BLOCK_SIZE + 7 }, |
| + { .index = 10, .name = "file_big", .size = 500 * 1024 * 1024 } |
| + }; |
| + |
| + if (options.file) |
| + return (struct test_files_set) { |
| + .files = files + options.file - 1, |
| + .files_count = 1, |
| + }; |
| + |
| + return (struct test_files_set){ .files = files, |
| + .files_count = ARRAY_SIZE(files) }; |
| +} |
| + |
| +struct test_files_set get_small_test_files_set(void) |
| +{ |
| + static struct test_file files[] = { |
| + { .index = 0, .name = "file_one_byte", .size = 1 }, |
| + { .index = 1, |
| + .name = "file_one_block", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE }, |
| + { .index = 2, |
| + .name = "file_one_and_a_half_blocks", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE + |
| + INCFS_DATA_FILE_BLOCK_SIZE / 2 }, |
| + { .index = 3, |
| + .name = "file_three", |
| + .size = 300 * INCFS_DATA_FILE_BLOCK_SIZE + 3 }, |
| + { .index = 4, |
| + .name = "file_four", |
| + .size = 400 * INCFS_DATA_FILE_BLOCK_SIZE + 7 } |
| + }; |
| + return (struct test_files_set){ .files = files, |
| + .files_count = ARRAY_SIZE(files) }; |
| +} |
| + |
| +static int get_file_block_seed(int file, int block) |
| +{ |
| + return 7919 * file + block; |
| +} |
| + |
| +static loff_t min(loff_t a, loff_t b) |
| +{ |
| + return a < b ? a : b; |
| +} |
| + |
| +static int ilog2(size_t n) |
| +{ |
| + int l = 0; |
| + |
| + while (n > 1) { |
| + ++l; |
| + n >>= 1; |
| + } |
| + return l; |
| +} |
| + |
| +static pid_t flush_and_fork(void) |
| +{ |
| + fflush(stdout); |
| + return fork(); |
| +} |
| + |
| +static void print_error(char *msg) |
| +{ |
| + ksft_print_msg("%s: %s\n", msg, strerror(errno)); |
| +} |
| + |
| +static int wait_for_process(pid_t pid) |
| +{ |
| + int status; |
| + int wait_res; |
| + |
| + wait_res = waitpid(pid, &status, 0); |
| + if (wait_res <= 0) { |
| + print_error("Can't wait for the child"); |
| + return -EINVAL; |
| + } |
| + if (!WIFEXITED(status)) { |
| + ksft_print_msg("Unexpected child status pid=%d\n", pid); |
| + return -EINVAL; |
| + } |
| + status = WEXITSTATUS(status); |
| + if (status != 0) |
| + return status; |
| + return 0; |
| +} |
| + |
| +static void rnd_buf(uint8_t *data, size_t len, unsigned int seed) |
| +{ |
| + int i; |
| + |
| + for (i = 0; i < len; i++) { |
| + seed = 1103515245 * seed + 12345; |
| + data[i] = (uint8_t)(seed >> (i % 13)); |
| + } |
| +} |
| + |
| +char *bin2hex(char *dst, const void *src, size_t count) |
| +{ |
| + const unsigned char *_src = src; |
| + static const char hex_asc[] = "0123456789abcdef"; |
| + |
| + while (count--) { |
| + unsigned char x = *_src++; |
| + |
| + *dst++ = hex_asc[(x & 0xf0) >> 4]; |
| + *dst++ = hex_asc[(x & 0x0f)]; |
| + } |
| + *dst = 0; |
| + return dst; |
| +} |
| + |
| +static char *get_index_filename(const char *mnt_dir, incfs_uuid_t id) |
| +{ |
| + char path[FILENAME_MAX]; |
| + char str_id[1 + 2 * sizeof(id)]; |
| + |
| + bin2hex(str_id, id.bytes, sizeof(id.bytes)); |
| + snprintf(path, ARRAY_SIZE(path), "%s/.index/%s", mnt_dir, str_id); |
| + |
| + return strdup(path); |
| +} |
| + |
| +static char *get_incomplete_filename(const char *mnt_dir, incfs_uuid_t id) |
| +{ |
| + char path[FILENAME_MAX]; |
| + char str_id[1 + 2 * sizeof(id)]; |
| + |
| + bin2hex(str_id, id.bytes, sizeof(id.bytes)); |
| + snprintf(path, ARRAY_SIZE(path), "%s/.incomplete/%s", mnt_dir, str_id); |
| + |
| + return strdup(path); |
| +} |
| + |
| +int open_file_by_id(const char *mnt_dir, incfs_uuid_t id, bool use_ioctl) |
| +{ |
| + char *path = get_index_filename(mnt_dir, id); |
| + int cmd_fd = open_commands_file(mnt_dir); |
| + int fd = open(path, O_RDWR | O_CLOEXEC); |
| + struct incfs_permit_fill permit_fill = { |
| + .file_descriptor = fd, |
| + }; |
| + int error = 0; |
| + |
| + if (fd < 0) { |
| + print_error("Can't open file by id."); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + if (use_ioctl && ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + print_error("Failed to call PERMIT_FILL"); |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + if (ioctl(fd, INCFS_IOC_PERMIT_FILL, &permit_fill) != -1) { |
| + print_error( |
| + "Successfully called PERMIT_FILL on non pending_read file"); |
| + return -errno; |
| + goto out; |
| + } |
| + |
| +out: |
| + free(path); |
| + close(cmd_fd); |
| + |
| + if (error) { |
| + close(fd); |
| + return error; |
| + } |
| + |
| + return fd; |
| +} |
| + |
| +int get_file_attr(const char *mnt_dir, incfs_uuid_t id, char *value, int size) |
| +{ |
| + char *path = get_index_filename(mnt_dir, id); |
| + int res; |
| + |
| + res = getxattr(path, INCFS_XATTR_METADATA_NAME, value, size); |
| + if (res < 0) |
| + res = -errno; |
| + |
| + free(path); |
| + return res; |
| +} |
| + |
| +static bool same_id(incfs_uuid_t *id1, incfs_uuid_t *id2) |
| +{ |
| + return !memcmp(id1->bytes, id2->bytes, sizeof(id1->bytes)); |
| +} |
| + |
| +ssize_t ZSTD_compress_default(char *data, char *comp_data, size_t data_size, |
| + size_t comp_size) |
| +{ |
| + return ZSTD_compress(comp_data, comp_size, data, data_size, 1); |
| +} |
| + |
| +static int emit_test_blocks(const char *mnt_dir, struct test_file *file, |
| + int blocks[], int count) |
| +{ |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + uint8_t comp_data[2 * INCFS_DATA_FILE_BLOCK_SIZE]; |
| + int block_count = (count > 32) ? 32 : count; |
| + int data_buf_size = 2 * INCFS_DATA_FILE_BLOCK_SIZE * block_count; |
| + uint8_t *data_buf = malloc(data_buf_size); |
| + uint8_t *current_data = data_buf; |
| + uint8_t *data_end = data_buf + data_buf_size; |
| + struct incfs_fill_block *block_buf = |
| + calloc(block_count, sizeof(struct incfs_fill_block)); |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = block_count, |
| + .fill_blocks = ptr_to_u64(block_buf), |
| + }; |
| + ssize_t write_res = 0; |
| + int fd = -1; |
| + int error = 0; |
| + int i = 0; |
| + int blocks_written = 0; |
| + |
| + for (i = 0; i < block_count; i++) { |
| + int block_index = blocks[i]; |
| + bool compress_zstd = (file->index + block_index) % 4 == 2; |
| + bool compress_lz4 = (file->index + block_index) % 4 == 0; |
| + int seed = get_file_block_seed(file->index, block_index); |
| + off_t block_offset = |
| + ((off_t)block_index) * INCFS_DATA_FILE_BLOCK_SIZE; |
| + size_t block_size = 0; |
| + |
| + if (block_offset > file->size) { |
| + error = -EINVAL; |
| + break; |
| + } |
| + if (file->size - block_offset > |
| + INCFS_DATA_FILE_BLOCK_SIZE) |
| + block_size = INCFS_DATA_FILE_BLOCK_SIZE; |
| + else |
| + block_size = file->size - block_offset; |
| + |
| + rnd_buf(data, block_size, seed); |
| + if (compress_lz4) { |
| + size_t comp_size = LZ4_compress_default((char *)data, |
| + (char *)comp_data, block_size, |
| + ARRAY_SIZE(comp_data)); |
| + |
| + if (comp_size <= 0) { |
| + error = -EBADMSG; |
| + break; |
| + } |
| + if (current_data + comp_size > data_end) { |
| + error = -ENOMEM; |
| + break; |
| + } |
| + memcpy(current_data, comp_data, comp_size); |
| + block_size = comp_size; |
| + block_buf[i].compression = COMPRESSION_LZ4; |
| + } else if (compress_zstd) { |
| + size_t comp_size = ZSTD_compress(comp_data, |
| + ARRAY_SIZE(comp_data), data, block_size, |
| + 1); |
| + |
| + if (comp_size <= 0) { |
| + error = -EBADMSG; |
| + break; |
| + } |
| + if (current_data + comp_size > data_end) { |
| + error = -ENOMEM; |
| + break; |
| + } |
| + memcpy(current_data, comp_data, comp_size); |
| + block_size = comp_size; |
| + block_buf[i].compression = COMPRESSION_ZSTD; |
| + } else { |
| + if (current_data + block_size > data_end) { |
| + error = -ENOMEM; |
| + break; |
| + } |
| + memcpy(current_data, data, block_size); |
| + block_buf[i].compression = COMPRESSION_NONE; |
| + } |
| + |
| + block_buf[i].block_index = block_index; |
| + block_buf[i].data_len = block_size; |
| + block_buf[i].data = ptr_to_u64(current_data); |
| + current_data += block_size; |
| + } |
| + |
| + if (!error) { |
| + fd = open_file_by_id(mnt_dir, file->id, false); |
| + if (fd < 0) { |
| + error = -errno; |
| + goto out; |
| + } |
| + write_res = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + if (write_res >= 0) { |
| + ksft_print_msg("Wrote to file via normal fd error\n"); |
| + error = -EPERM; |
| + goto out; |
| + } |
| + |
| + close(fd); |
| + fd = open_file_by_id(mnt_dir, file->id, true); |
| + if (fd < 0) { |
| + error = -errno; |
| + goto out; |
| + } |
| + write_res = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + if (write_res < 0) |
| + error = -errno; |
| + else |
| + blocks_written = write_res; |
| + } |
| + if (error) { |
| + ksft_print_msg( |
| + "Writing data block error. Write returned: %d. Error:%s\n", |
| + write_res, strerror(-error)); |
| + } |
| + |
| +out: |
| + free(block_buf); |
| + free(data_buf); |
| + close(fd); |
| + return (error < 0) ? error : blocks_written; |
| +} |
| + |
| +static int emit_test_block(const char *mnt_dir, struct test_file *file, |
| + int block_index) |
| +{ |
| + int res = emit_test_blocks(mnt_dir, file, &block_index, 1); |
| + |
| + if (res == 0) |
| + return -EINVAL; |
| + if (res == 1) |
| + return 0; |
| + return res; |
| +} |
| + |
| +static void shuffle(int array[], int count, unsigned int seed) |
| +{ |
| + int i; |
| + |
| + for (i = 0; i < count - 1; i++) { |
| + int items_left = count - i; |
| + int shuffle_index; |
| + int v; |
| + |
| + seed = 1103515245 * seed + 12345; |
| + shuffle_index = i + seed % items_left; |
| + |
| + v = array[shuffle_index]; |
| + array[shuffle_index] = array[i]; |
| + array[i] = v; |
| + } |
| +} |
| + |
| +static int emit_test_file_data(const char *mount_dir, struct test_file *file) |
| +{ |
| + int i; |
| + int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + int *block_indexes = NULL; |
| + int result = 0; |
| + int blocks_written = 0; |
| + |
| + if (file->size == 0) |
| + return 0; |
| + |
| + block_indexes = calloc(block_cnt, sizeof(*block_indexes)); |
| + for (i = 0; i < block_cnt; i++) |
| + block_indexes[i] = i; |
| + shuffle(block_indexes, block_cnt, file->index); |
| + |
| + for (i = 0; i < block_cnt; i += blocks_written) { |
| + blocks_written = emit_test_blocks(mount_dir, file, |
| + block_indexes + i, block_cnt - i); |
| + if (blocks_written < 0) { |
| + result = blocks_written; |
| + goto out; |
| + } |
| + if (blocks_written == 0) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + } |
| +out: |
| + free(block_indexes); |
| + return result; |
| +} |
| + |
| +static loff_t read_whole_file(const char *filename) |
| +{ |
| + int fd = -1; |
| + loff_t result; |
| + loff_t bytes_read = 0; |
| + uint8_t buff[16 * 1024]; |
| + |
| + fd = open(filename, O_RDONLY | O_CLOEXEC); |
| + if (fd <= 0) |
| + return fd; |
| + |
| + while (1) { |
| + int read_result = read(fd, buff, ARRAY_SIZE(buff)); |
| + |
| + if (read_result < 0) { |
| + print_error("Error during reading from a file."); |
| + result = -errno; |
| + goto cleanup; |
| + } else if (read_result == 0) |
| + break; |
| + |
| + bytes_read += read_result; |
| + } |
| + result = bytes_read; |
| + |
| +cleanup: |
| + close(fd); |
| + return result; |
| +} |
| + |
| +static int read_test_file(uint8_t *buf, size_t len, char *filename, |
| + int block_idx) |
| +{ |
| + int fd = -1; |
| + int result; |
| + int bytes_read = 0; |
| + size_t bytes_to_read = len; |
| + off_t offset = ((off_t)block_idx) * INCFS_DATA_FILE_BLOCK_SIZE; |
| + |
| + fd = open(filename, O_RDONLY | O_CLOEXEC); |
| + if (fd <= 0) |
| + return fd; |
| + |
| + if (lseek(fd, offset, SEEK_SET) != offset) { |
| + print_error("Seek error"); |
| + return -errno; |
| + } |
| + |
| + while (bytes_read < bytes_to_read) { |
| + int read_result = |
| + read(fd, buf + bytes_read, bytes_to_read - bytes_read); |
| + if (read_result < 0) { |
| + result = -errno; |
| + goto cleanup; |
| + } else if (read_result == 0) |
| + break; |
| + |
| + bytes_read += read_result; |
| + } |
| + result = bytes_read; |
| + |
| +cleanup: |
| + close(fd); |
| + return result; |
| +} |
| + |
| +static char *create_backing_dir(const char *mount_dir) |
| +{ |
| + struct stat st; |
| + char backing_dir_name[255]; |
| + |
| + snprintf(backing_dir_name, ARRAY_SIZE(backing_dir_name), "%s-src", |
| + mount_dir); |
| + |
| + if (stat(backing_dir_name, &st) == 0) { |
| + if (S_ISDIR(st.st_mode)) { |
| + int error = delete_dir_tree(backing_dir_name); |
| + |
| + if (error) { |
| + ksft_print_msg( |
| + "Can't delete existing backing dir. %d\n", |
| + error); |
| + return NULL; |
| + } |
| + } else { |
| + if (unlink(backing_dir_name)) { |
| + print_error("Can't clear backing dir"); |
| + return NULL; |
| + } |
| + } |
| + } |
| + |
| + if (mkdir(backing_dir_name, 0777)) { |
| + if (errno != EEXIST) { |
| + print_error("Can't open/create backing dir"); |
| + return NULL; |
| + } |
| + } |
| + |
| + return strdup(backing_dir_name); |
| +} |
| + |
| +static int validate_test_file_content_with_seed(const char *mount_dir, |
| + struct test_file *file, |
| + unsigned int shuffle_seed) |
| +{ |
| + int error = -1; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + off_t size = file->size; |
| + loff_t actual_size = get_file_size(filename); |
| + int block_cnt = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + int *block_indexes = NULL; |
| + int i; |
| + |
| + block_indexes = alloca(sizeof(int) * block_cnt); |
| + for (i = 0; i < block_cnt; i++) |
| + block_indexes[i] = i; |
| + |
| + if (shuffle_seed != 0) |
| + shuffle(block_indexes, block_cnt, shuffle_seed); |
| + |
| + if (actual_size != size) { |
| + ksft_print_msg( |
| + "File size doesn't match. name: %s expected size:%ld actual size:%ld\n", |
| + filename, size, actual_size); |
| + error = -1; |
| + goto failure; |
| + } |
| + |
| + for (i = 0; i < block_cnt; i++) { |
| + int block_idx = block_indexes[i]; |
| + uint8_t expected_block[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + uint8_t actual_block[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + int seed = get_file_block_seed(file->index, block_idx); |
| + size_t bytes_to_compare = min( |
| + (off_t)INCFS_DATA_FILE_BLOCK_SIZE, |
| + size - ((off_t)block_idx) * INCFS_DATA_FILE_BLOCK_SIZE); |
| + int read_result = |
| + read_test_file(actual_block, INCFS_DATA_FILE_BLOCK_SIZE, |
| + filename, block_idx); |
| + if (read_result < 0) { |
| + ksft_print_msg( |
| + "Error reading block %d from file %s. Error: %s\n", |
| + block_idx, filename, strerror(-read_result)); |
| + error = read_result; |
| + goto failure; |
| + } |
| + rnd_buf(expected_block, INCFS_DATA_FILE_BLOCK_SIZE, seed); |
| + if (memcmp(expected_block, actual_block, bytes_to_compare)) { |
| + ksft_print_msg( |
| + "File contents don't match. name: %s block:%d\n", |
| + file->name, block_idx); |
| + error = -2; |
| + goto failure; |
| + } |
| + } |
| + free(filename); |
| + return 0; |
| + |
| +failure: |
| + free(filename); |
| + return error; |
| +} |
| + |
| +static int validate_test_file_content(const char *mount_dir, |
| + struct test_file *file) |
| +{ |
| + return validate_test_file_content_with_seed(mount_dir, file, 0); |
| +} |
| + |
| +static int data_producer(const char *mount_dir, struct test_files_set *test_set) |
| +{ |
| + int ret = 0; |
| + int timeout_ms = 1000; |
| + struct incfs_pending_read_info prs[100] = {}; |
| + int prs_size = ARRAY_SIZE(prs); |
| + int fd = open_commands_file(mount_dir); |
| + |
| + if (fd < 0) |
| + return -errno; |
| + |
| + while ((ret = wait_for_pending_reads(fd, timeout_ms, prs, prs_size)) > |
| + 0) { |
| + int read_count = ret; |
| + int i; |
| + |
| + for (i = 0; i < read_count; i++) { |
| + int j = 0; |
| + struct test_file *file = NULL; |
| + |
| + for (j = 0; j < test_set->files_count; j++) { |
| + bool same = same_id(&(test_set->files[j].id), |
| + &(prs[i].file_id)); |
| + |
| + if (same) { |
| + file = &test_set->files[j]; |
| + break; |
| + } |
| + } |
| + if (!file) { |
| + ksft_print_msg( |
| + "Unknown file in pending reads.\n"); |
| + break; |
| + } |
| + |
| + ret = emit_test_block(mount_dir, file, |
| + prs[i].block_index); |
| + if (ret < 0) { |
| + ksft_print_msg("Emitting test data error: %s\n", |
| + strerror(-ret)); |
| + break; |
| + } |
| + } |
| + } |
| + close(fd); |
| + return ret; |
| +} |
| + |
| +static int data_producer2(const char *mount_dir, |
| + struct test_files_set *test_set) |
| +{ |
| + int ret = 0; |
| + int timeout_ms = 1000; |
| + struct incfs_pending_read_info2 prs[100] = {}; |
| + int prs_size = ARRAY_SIZE(prs); |
| + int fd = open_commands_file(mount_dir); |
| + |
| + if (fd < 0) |
| + return -errno; |
| + |
| + while ((ret = wait_for_pending_reads2(fd, timeout_ms, prs, prs_size)) > |
| + 0) { |
| + int read_count = ret; |
| + int i; |
| + |
| + for (i = 0; i < read_count; i++) { |
| + int j = 0; |
| + struct test_file *file = NULL; |
| + |
| + for (j = 0; j < test_set->files_count; j++) { |
| + bool same = same_id(&(test_set->files[j].id), |
| + &(prs[i].file_id)); |
| + |
| + if (same) { |
| + file = &test_set->files[j]; |
| + break; |
| + } |
| + } |
| + if (!file) { |
| + ksft_print_msg( |
| + "Unknown file in pending reads.\n"); |
| + break; |
| + } |
| + |
| + ret = emit_test_block(mount_dir, file, |
| + prs[i].block_index); |
| + if (ret < 0) { |
| + ksft_print_msg("Emitting test data error: %s\n", |
| + strerror(-ret)); |
| + break; |
| + } |
| + } |
| + } |
| + close(fd); |
| + return ret; |
| +} |
| + |
| +static int build_mtree(struct test_file *file) |
| +{ |
| + char data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + const int digest_size = SHA256_DIGEST_SIZE; |
| + const int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size; |
| + int block_count = 0; |
| + int hash_block_count = 0; |
| + int total_tree_block_count = 0; |
| + int tree_lvl_index[INCFS_MAX_MTREE_LEVELS] = {}; |
| + int tree_lvl_count[INCFS_MAX_MTREE_LEVELS] = {}; |
| + int levels_count = 0; |
| + int i, level; |
| + |
| + if (file->size == 0) |
| + return 0; |
| + |
| + block_count = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + hash_block_count = block_count; |
| + for (i = 0; hash_block_count > 1; i++) { |
| + hash_block_count = (hash_block_count + hash_per_block - 1) |
| + / hash_per_block; |
| + tree_lvl_count[i] = hash_block_count; |
| + total_tree_block_count += hash_block_count; |
| + } |
| + levels_count = i; |
| + |
| + for (i = 0; i < levels_count; i++) { |
| + int prev_lvl_base = (i == 0) ? total_tree_block_count : |
| + tree_lvl_index[i - 1]; |
| + |
| + tree_lvl_index[i] = prev_lvl_base - tree_lvl_count[i]; |
| + } |
| + |
| + file->mtree_block_count = total_tree_block_count; |
| + if (block_count == 1) { |
| + int seed = get_file_block_seed(file->index, 0); |
| + |
| + memset(data, 0, INCFS_DATA_FILE_BLOCK_SIZE); |
| + rnd_buf((uint8_t *)data, file->size, seed); |
| + sha256(data, INCFS_DATA_FILE_BLOCK_SIZE, file->root_hash); |
| + return 0; |
| + } |
| + |
| + file->mtree = calloc(total_tree_block_count, sizeof(*file->mtree)); |
| + /* Build level 0 hashes. */ |
| + for (i = 0; i < block_count; i++) { |
| + off_t offset = i * INCFS_DATA_FILE_BLOCK_SIZE; |
| + size_t block_size = INCFS_DATA_FILE_BLOCK_SIZE; |
| + int block_index = tree_lvl_index[0] + |
| + i / hash_per_block; |
| + int block_off = (i % hash_per_block) * digest_size; |
| + int seed = get_file_block_seed(file->index, i); |
| + char *hash_ptr = file->mtree[block_index].data + block_off; |
| + |
| + if (file->size - offset < block_size) { |
| + block_size = file->size - offset; |
| + memset(data, 0, INCFS_DATA_FILE_BLOCK_SIZE); |
| + } |
| + |
| + rnd_buf((uint8_t *)data, block_size, seed); |
| + sha256(data, INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr); |
| + } |
| + |
| + /* Build higher levels of hash tree. */ |
| + for (level = 1; level < levels_count; level++) { |
| + int prev_lvl_base = tree_lvl_index[level - 1]; |
| + int prev_lvl_count = tree_lvl_count[level - 1]; |
| + |
| + for (i = 0; i < prev_lvl_count; i++) { |
| + int block_index = |
| + i / hash_per_block + tree_lvl_index[level]; |
| + int block_off = (i % hash_per_block) * digest_size; |
| + char *hash_ptr = |
| + file->mtree[block_index].data + block_off; |
| + |
| + sha256(file->mtree[i + prev_lvl_base].data, |
| + INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr); |
| + } |
| + } |
| + |
| + /* Calculate root hash from the top block */ |
| + sha256(file->mtree[0].data, |
| + INCFS_DATA_FILE_BLOCK_SIZE, file->root_hash); |
| + |
| + return 0; |
| +} |
| + |
| +static int load_hash_tree(const char *mount_dir, struct test_file *file) |
| +{ |
| + int err; |
| + int i; |
| + int fd; |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = file->mtree_block_count, |
| + }; |
| + struct incfs_fill_block *fill_block_array = |
| + calloc(fill_blocks.count, sizeof(struct incfs_fill_block)); |
| + |
| + if (fill_blocks.count == 0) |
| + return 0; |
| + |
| + if (!fill_block_array) |
| + return -ENOMEM; |
| + fill_blocks.fill_blocks = ptr_to_u64(fill_block_array); |
| + |
| + for (i = 0; i < fill_blocks.count; i++) { |
| + fill_block_array[i] = (struct incfs_fill_block){ |
| + .block_index = i, |
| + .data_len = INCFS_DATA_FILE_BLOCK_SIZE, |
| + .data = ptr_to_u64(file->mtree[i].data), |
| + .flags = INCFS_BLOCK_FLAGS_HASH |
| + }; |
| + } |
| + |
| + fd = open_file_by_id(mount_dir, file->id, false); |
| + if (fd < 0) { |
| + err = errno; |
| + goto failure; |
| + } |
| + |
| + err = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + close(fd); |
| + if (err >= 0) { |
| + err = -EPERM; |
| + goto failure; |
| + } |
| + |
| + fd = open_file_by_id(mount_dir, file->id, true); |
| + if (fd < 0) { |
| + err = errno; |
| + goto failure; |
| + } |
| + |
| + err = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + close(fd); |
| + if (err < fill_blocks.count) |
| + err = errno; |
| + else |
| + err = 0; |
| + |
| +failure: |
| + free(fill_block_array); |
| + return err; |
| +} |
| + |
| +static int cant_touch_index_test(const char *mount_dir) |
| +{ |
| + char *file_name = "test_file"; |
| + int file_size = 123; |
| + incfs_uuid_t file_id; |
| + char *index_path = concat_file_name(mount_dir, ".index"); |
| + char *subdir = concat_file_name(index_path, "subdir"); |
| + char *dst_name = concat_file_name(mount_dir, "something"); |
| + char *filename_in_index = NULL; |
| + char *file_path = concat_file_name(mount_dir, file_name); |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int err; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + free(backing_dir); |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + |
| + err = mkdir(subdir, 0777); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error("Shouldn't be able to crate subdir in index\n"); |
| + goto failure; |
| + } |
| + |
| + err = rmdir(index_path); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error(".index directory should not be removed\n"); |
| + goto failure; |
| + } |
| + |
| + err = emit_file(cmd_fd, ".index", file_name, &file_id, |
| + file_size, NULL); |
| + if (err != -EBUSY) { |
| + print_error("Shouldn't be able to crate a file in index\n"); |
| + goto failure; |
| + } |
| + |
| + err = emit_file(cmd_fd, NULL, file_name, &file_id, |
| + file_size, NULL); |
| + if (err < 0) |
| + goto failure; |
| + filename_in_index = get_index_filename(mount_dir, file_id); |
| + |
| + err = unlink(filename_in_index); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error("Shouldn't be delete from index\n"); |
| + goto failure; |
| + } |
| + |
| + |
| + err = rename(filename_in_index, dst_name); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error("Shouldn't be able to move from index\n"); |
| + goto failure; |
| + } |
| + |
| + free(filename_in_index); |
| + filename_in_index = concat_file_name(index_path, "abc"); |
| + err = link(file_path, filename_in_index); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error("Shouldn't be able to link inside index\n"); |
| + goto failure; |
| + } |
| + |
| + err = rename(index_path, dst_name); |
| + if (err == 0 || errno != EBUSY) { |
| + print_error("Shouldn't rename .index directory\n"); |
| + goto failure; |
| + } |
| + |
| + close(cmd_fd); |
| + free(subdir); |
| + free(index_path); |
| + free(dst_name); |
| + free(filename_in_index); |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + free(subdir); |
| + free(dst_name); |
| + free(index_path); |
| + free(filename_in_index); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static bool iterate_directory(const char *dir_to_iterate, bool root, |
| + int file_count) |
| +{ |
| + struct expected_name { |
| + const char *name; |
| + bool root_only; |
| + bool found; |
| + } names[] = { |
| + {INCFS_LOG_FILENAME, true, false}, |
| + {INCFS_PENDING_READS_FILENAME, true, false}, |
| + {INCFS_BLOCKS_WRITTEN_FILENAME, true, false}, |
| + {".index", true, false}, |
| + {".incomplete", true, false}, |
| + {"..", false, false}, |
| + {".", false, false}, |
| + }; |
| + |
| + bool pass = true, found; |
| + int i; |
| + |
| + /* Test directory iteration */ |
| + int fd = open(dir_to_iterate, O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
| + |
| + if (fd < 0) { |
| + print_error("Can't open directory\n"); |
| + return false; |
| + } |
| + |
| + for (;;) { |
| + /* Enough space for one dirent - no name over 30 */ |
| + char buf[sizeof(struct linux_dirent64) + NAME_MAX]; |
| + struct linux_dirent64 *dirent = (struct linux_dirent64 *) buf; |
| + int nread; |
| + int i; |
| + |
| + for (i = 0; i < NAME_MAX; ++i) { |
| + nread = syscall(__NR_getdents64, fd, buf, |
| + sizeof(struct linux_dirent64) + i); |
| + |
| + if (nread >= 0) |
| + break; |
| + if (errno != EINVAL) |
| + break; |
| + } |
| + |
| + if (nread == 0) |
| + break; |
| + if (nread < 0) { |
| + print_error("Error iterating directory\n"); |
| + pass = false; |
| + goto failure; |
| + } |
| + |
| + /* Expected size is rounded up to 8 byte boundary. Not sure if |
| + * this is universal truth or just happenstance, but useful test |
| + * for the moment |
| + */ |
| + if (nread != (((sizeof(struct linux_dirent64) |
| + + strlen(dirent->d_name) + 1) + 7) & ~7)) { |
| + print_error("Wrong dirent size"); |
| + pass = false; |
| + goto failure; |
| + } |
| + |
| + found = false; |
| + for (i = 0; i < sizeof(names) / sizeof(*names); ++i) |
| + if (!strcmp(dirent->d_name, names[i].name)) { |
| + if (names[i].root_only && !root) { |
| + print_error("Root file error"); |
| + pass = false; |
| + goto failure; |
| + } |
| + |
| + if (names[i].found) { |
| + print_error("File appears twice"); |
| + pass = false; |
| + goto failure; |
| + } |
| + |
| + names[i].found = true; |
| + found = true; |
| + break; |
| + } |
| + |
| + if (!found) |
| + --file_count; |
| + } |
| + |
| + for (i = 0; i < sizeof(names) / sizeof(*names); ++i) { |
| + if (!names[i].found) |
| + if (root || !names[i].root_only) { |
| + print_error("Expected file not present"); |
| + pass = false; |
| + goto failure; |
| + } |
| + } |
| + |
| + if (file_count) { |
| + print_error("Wrong number of files\n"); |
| + pass = false; |
| + goto failure; |
| + } |
| + |
| +failure: |
| + close(fd); |
| + return pass; |
| +} |
| + |
| +static int basic_file_ops_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + char *subdir1 = concat_file_name(mount_dir, "subdir1"); |
| + char *subdir2 = concat_file_name(mount_dir, "subdir2"); |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i, err; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + free(backing_dir); |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + err = mkdir(subdir1, 0777); |
| + if (err < 0 && errno != EEXIST) { |
| + print_error("Can't create subdir1\n"); |
| + goto failure; |
| + } |
| + |
| + err = mkdir(subdir2, 0777); |
| + if (err < 0 && errno != EEXIST) { |
| + print_error("Can't create subdir2\n"); |
| + goto failure; |
| + } |
| + |
| + /* Create all test files in subdir1 directory */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + loff_t size; |
| + char *file_path = concat_file_name(subdir1, file->name); |
| + |
| + err = emit_file(cmd_fd, "subdir1", file->name, &file->id, |
| + file->size, NULL); |
| + if (err < 0) |
| + goto failure; |
| + |
| + size = get_file_size(file_path); |
| + free(file_path); |
| + if (size != file->size) { |
| + ksft_print_msg("Wrong size %lld of %s.\n", |
| + size, file->name); |
| + goto failure; |
| + } |
| + } |
| + |
| + if (!iterate_directory(subdir1, false, file_num)) |
| + goto failure; |
| + |
| + /* Link the files to subdir2 */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *src_name = concat_file_name(subdir1, file->name); |
| + char *dst_name = concat_file_name(subdir2, file->name); |
| + loff_t size; |
| + |
| + err = link(src_name, dst_name); |
| + if (err < 0) { |
| + print_error("Can't move file\n"); |
| + goto failure; |
| + } |
| + |
| + size = get_file_size(dst_name); |
| + if (size != file->size) { |
| + ksft_print_msg("Wrong size %lld of %s.\n", |
| + size, file->name); |
| + goto failure; |
| + } |
| + free(src_name); |
| + free(dst_name); |
| + } |
| + |
| + /* Move the files from subdir2 to the mount dir */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *src_name = concat_file_name(subdir2, file->name); |
| + char *dst_name = concat_file_name(mount_dir, file->name); |
| + loff_t size; |
| + |
| + err = rename(src_name, dst_name); |
| + if (err < 0) { |
| + print_error("Can't move file\n"); |
| + goto failure; |
| + } |
| + |
| + size = get_file_size(dst_name); |
| + if (size != file->size) { |
| + ksft_print_msg("Wrong size %lld of %s.\n", |
| + size, file->name); |
| + goto failure; |
| + } |
| + free(src_name); |
| + free(dst_name); |
| + } |
| + |
| + /* +2 because there are 2 subdirs */ |
| + if (!iterate_directory(mount_dir, true, file_num + 2)) |
| + goto failure; |
| + |
| + /* Open and close all files from the mount dir */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *path = concat_file_name(mount_dir, file->name); |
| + int fd; |
| + |
| + fd = open(path, O_RDWR | O_CLOEXEC); |
| + free(path); |
| + if (fd <= 0) { |
| + print_error("Can't open file"); |
| + goto failure; |
| + } |
| + if (close(fd)) { |
| + print_error("Can't close file"); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Delete all files from the mount dir */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *path = concat_file_name(mount_dir, file->name); |
| + |
| + err = unlink(path); |
| + free(path); |
| + if (err < 0) { |
| + print_error("Can't unlink file"); |
| + goto failure; |
| + } |
| + } |
| + |
| + err = delete_dir_tree(subdir1); |
| + if (err) { |
| + ksft_print_msg("Error deleting subdir1 %d", err); |
| + goto failure; |
| + } |
| + |
| + err = rmdir(subdir2); |
| + if (err) { |
| + print_error("Error deleting subdir2"); |
| + goto failure; |
| + } |
| + |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int dynamic_files_and_data_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + const int missing_file_idx = 5; |
| + int cmd_fd = -1; |
| + char *backing_dir; |
| + int i; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + free(backing_dir); |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Check that test files don't exist in the filesystem. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + |
| + if (access(filename, F_OK) != -1) { |
| + ksft_print_msg( |
| + "File %s somehow already exists in a clean FS.\n", |
| + filename); |
| + goto failure; |
| + } |
| + free(filename); |
| + } |
| + |
| + /* Write test data into the command file. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + int res; |
| + |
| + res = emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL); |
| + if (res < 0) { |
| + ksft_print_msg("Error %s emiting file %s.\n", |
| + strerror(-res), file->name); |
| + goto failure; |
| + } |
| + |
| + /* Skip writing data to one file so we can check */ |
| + /* that it's missing later. */ |
| + if (i == missing_file_idx) |
| + continue; |
| + |
| + res = emit_test_file_data(mount_dir, file); |
| + if (res) { |
| + ksft_print_msg("Error %s emiting data for %s.\n", |
| + strerror(-res), file->name); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Validate contents of the FS */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (i == missing_file_idx) { |
| + /* No data has been written to this file. */ |
| + /* Check for read error; */ |
| + uint8_t buf; |
| + char *filename = |
| + concat_file_name(mount_dir, file->name); |
| + int res = read_test_file(&buf, 1, filename, 0); |
| + |
| + free(filename); |
| + if (res > 0) { |
| + ksft_print_msg( |
| + "Data present, even though never writtern.\n"); |
| + goto failure; |
| + } |
| + if (res != -ETIME) { |
| + ksft_print_msg("Wrong error code: %d.\n", res); |
| + goto failure; |
| + } |
| + } else { |
| + if (validate_test_file_content(mount_dir, file) < 0) |
| + goto failure; |
| + } |
| + } |
| + |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int concurrent_reads_and_writes_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + /* Validate each file from that many child processes. */ |
| + const int child_multiplier = 3; |
| + int cmd_fd = -1; |
| + char *backing_dir; |
| + int status; |
| + int i; |
| + pid_t producer_pid; |
| + pid_t *child_pids = alloca(child_multiplier * file_num * sizeof(pid_t)); |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 500) != 0) |
| + goto failure; |
| + free(backing_dir); |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Tell FS about the files, without actually providing the data. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + int res; |
| + |
| + res = emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL); |
| + if (res) |
| + goto failure; |
| + } |
| + |
| + /* Start child processes acessing data in the files */ |
| + for (i = 0; i < file_num * child_multiplier; i++) { |
| + struct test_file *file = &test.files[i / child_multiplier]; |
| + pid_t child_pid = flush_and_fork(); |
| + |
| + if (child_pid == 0) { |
| + /* This is a child process, do the data validation. */ |
| + int ret = validate_test_file_content_with_seed( |
| + mount_dir, file, i); |
| + if (ret >= 0) { |
| + /* Zero exit status if data is valid. */ |
| + exit(0); |
| + } |
| + |
| + /* Positive status if validation error found. */ |
| + exit(-ret); |
| + } else if (child_pid > 0) { |
| + child_pids[i] = child_pid; |
| + } else { |
| + print_error("Fork error"); |
| + goto failure; |
| + } |
| + } |
| + |
| + producer_pid = flush_and_fork(); |
| + if (producer_pid == 0) { |
| + int ret; |
| + /* |
| + * This is a child that should provide data to |
| + * pending reads. |
| + */ |
| + |
| + ret = data_producer(mount_dir, &test); |
| + exit(-ret); |
| + } else { |
| + status = wait_for_process(producer_pid); |
| + if (status != 0) { |
| + ksft_print_msg("Data produces failed. %d(%s) ", status, |
| + strerror(status)); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Check that all children has finished with 0 exit status */ |
| + for (i = 0; i < file_num * child_multiplier; i++) { |
| + struct test_file *file = &test.files[i / child_multiplier]; |
| + |
| + status = wait_for_process(child_pids[i]); |
| + if (status != 0) { |
| + ksft_print_msg( |
| + "Validation for the file %s failed with code %d (%s)\n", |
| + file->name, status, strerror(status)); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Check that there are no pending reads left */ |
| + { |
| + struct incfs_pending_read_info prs[1] = {}; |
| + int timeout = 0; |
| + int read_count = wait_for_pending_reads(cmd_fd, timeout, prs, |
| + ARRAY_SIZE(prs)); |
| + |
| + if (read_count) { |
| + ksft_print_msg( |
| + "Pending reads pending when all data written\n"); |
| + goto failure; |
| + } |
| + } |
| + |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int work_after_remount_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + const int file_num_stage1 = file_num / 2; |
| + const int file_num_stage2 = file_num; |
| + char *backing_dir = NULL; |
| + int i = 0; |
| + int cmd_fd = -1; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Write first half of the data into the command file. (stage 1) */ |
| + for (i = 0; i < file_num_stage1; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL)) |
| + goto failure; |
| + |
| + if (emit_test_file_data(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + /* Unmount and mount again, to see that data is persistent. */ |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Write the second half of the data into the command file. (stage 2) */ |
| + for (; i < file_num_stage2; i++) { |
| + struct test_file *file = &test.files[i]; |
| + int res = emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL); |
| + |
| + if (res) |
| + goto failure; |
| + |
| + if (emit_test_file_data(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + /* Validate contents of the FS */ |
| + for (i = 0; i < file_num_stage2; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (validate_test_file_content(mount_dir, file) < 0) |
| + goto failure; |
| + } |
| + |
| + /* Delete all files */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + char *filename_in_index = get_index_filename(mount_dir, |
| + file->id); |
| + |
| + if (access(filename, F_OK) != 0) { |
| + ksft_print_msg("File %s is not visible.\n", filename); |
| + goto failure; |
| + } |
| + |
| + if (access(filename_in_index, F_OK) != 0) { |
| + ksft_print_msg("File %s is not visible.\n", |
| + filename_in_index); |
| + goto failure; |
| + } |
| + |
| + unlink(filename); |
| + |
| + if (access(filename, F_OK) != -1) { |
| + ksft_print_msg("File %s is still present.\n", filename); |
| + goto failure; |
| + } |
| + |
| + if (access(filename_in_index, F_OK) != -1) { |
| + ksft_print_msg("File %s is still present.\n", |
| + filename_in_index); |
| + goto failure; |
| + } |
| + free(filename); |
| + free(filename_in_index); |
| + } |
| + |
| + /* Unmount and mount again, to see that deleted files stay deleted. */ |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Validate all deleted files are still deleted. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + |
| + if (access(filename, F_OK) != -1) { |
| + ksft_print_msg("File %s is still visible.\n", filename); |
| + goto failure; |
| + } |
| + free(filename); |
| + } |
| + |
| + /* Final unmount */ |
| + close(cmd_fd); |
| + free(backing_dir); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int attribute_test(const char *mount_dir) |
| +{ |
| + char file_attr[] = "metadata123123"; |
| + char attr_buf[INCFS_MAX_FILE_ATTR_SIZE] = {}; |
| + int cmd_fd = -1; |
| + incfs_uuid_t file_id; |
| + int attr_res = 0; |
| + char *backing_dir; |
| + |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + if (emit_file(cmd_fd, NULL, "file", &file_id, 12, file_attr)) |
| + goto failure; |
| + |
| + /* Test attribute values */ |
| + attr_res = get_file_attr(mount_dir, file_id, attr_buf, |
| + ARRAY_SIZE(attr_buf)); |
| + if (attr_res != strlen(file_attr)) { |
| + ksft_print_msg("Get file attr error: %d\n", attr_res); |
| + goto failure; |
| + } |
| + if (strcmp(attr_buf, file_attr) != 0) { |
| + ksft_print_msg("Incorrect file attr value: '%s'", attr_buf); |
| + goto failure; |
| + } |
| + |
| + /* Unmount and mount again, to see that attributes are persistent. */ |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Test attribute values again after remount*/ |
| + attr_res = get_file_attr(mount_dir, file_id, attr_buf, |
| + ARRAY_SIZE(attr_buf)); |
| + if (attr_res != strlen(file_attr)) { |
| + ksft_print_msg("Get dir attr error: %d\n", attr_res); |
| + goto failure; |
| + } |
| + if (strcmp(attr_buf, file_attr) != 0) { |
| + ksft_print_msg("Incorrect file attr value: '%s'", attr_buf); |
| + goto failure; |
| + } |
| + |
| + /* Final unmount */ |
| + close(cmd_fd); |
| + free(backing_dir); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int child_procs_waiting_for_data_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + int cmd_fd = -1; |
| + int i; |
| + pid_t *child_pids = alloca(file_num * sizeof(pid_t)); |
| + char *backing_dir; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. (10s wait time) */ |
| + if (mount_fs(mount_dir, backing_dir, 10000) != 0) |
| + goto failure; |
| + |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Tell FS about the files, without actually providing the data. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL); |
| + } |
| + |
| + /* Start child processes acessing data in the files */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + pid_t child_pid = flush_and_fork(); |
| + |
| + if (child_pid == 0) { |
| + /* This is a child process, do the data validation. */ |
| + int ret = validate_test_file_content(mount_dir, file); |
| + |
| + if (ret >= 0) { |
| + /* Zero exit status if data is valid. */ |
| + exit(0); |
| + } |
| + |
| + /* Positive status if validation error found. */ |
| + exit(-ret); |
| + } else if (child_pid > 0) { |
| + child_pids[i] = child_pid; |
| + } else { |
| + print_error("Fork error"); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Write test data into the command file. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (emit_test_file_data(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + /* Check that all children has finished with 0 exit status */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + int status = wait_for_process(child_pids[i]); |
| + |
| + if (status != 0) { |
| + ksft_print_msg( |
| + "Validation for the file %s failed with code %d (%s)\n", |
| + file->name, status, strerror(status)); |
| + goto failure; |
| + } |
| + } |
| + |
| + close(cmd_fd); |
| + free(backing_dir); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int multiple_providers_test(const char *mount_dir) |
| +{ |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + const int producer_count = 5; |
| + int cmd_fd = -1; |
| + int status; |
| + int i; |
| + pid_t *producer_pids = alloca(producer_count * sizeof(pid_t)); |
| + char *backing_dir; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. (10s wait time) */ |
| + if (mount_fs_opt(mount_dir, backing_dir, |
| + "read_timeout_ms=10000,report_uid", false) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Tell FS about the files, without actually providing the data. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL) < 0) |
| + goto failure; |
| + } |
| + |
| + /* Start producer processes */ |
| + for (i = 0; i < producer_count; i++) { |
| + pid_t producer_pid = flush_and_fork(); |
| + |
| + if (producer_pid == 0) { |
| + int ret; |
| + /* |
| + * This is a child that should provide data to |
| + * pending reads. |
| + */ |
| + |
| + ret = data_producer2(mount_dir, &test); |
| + exit(-ret); |
| + } else if (producer_pid > 0) { |
| + producer_pids[i] = producer_pid; |
| + } else { |
| + print_error("Fork error"); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Validate FS content */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + loff_t read_result = read_whole_file(filename); |
| + |
| + free(filename); |
| + if (read_result != file->size) { |
| + ksft_print_msg( |
| + "Error validating file %s. Result: %ld\n", |
| + file->name, read_result); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Check that all producers has finished with 0 exit status */ |
| + for (i = 0; i < producer_count; i++) { |
| + status = wait_for_process(producer_pids[i]); |
| + if (status != 0) { |
| + ksft_print_msg("Producer %d failed with code (%s)\n", i, |
| + strerror(status)); |
| + goto failure; |
| + } |
| + } |
| + |
| + close(cmd_fd); |
| + free(backing_dir); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int validate_hash_tree(const char *mount_dir, struct test_file *file) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + int fd = -1; |
| + unsigned char *buf; |
| + int i, err; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TEST(buf = malloc(INCFS_DATA_FILE_BLOCK_SIZE * 8), buf); |
| + |
| + for (i = 0; i < file->mtree_block_count; ) { |
| + int blocks_to_read = i % 7 + 1; |
| + struct fsverity_read_metadata_arg args = { |
| + .metadata_type = FS_VERITY_METADATA_TYPE_MERKLE_TREE, |
| + .offset = i * INCFS_DATA_FILE_BLOCK_SIZE, |
| + .length = blocks_to_read * INCFS_DATA_FILE_BLOCK_SIZE, |
| + .buf_ptr = ptr_to_u64(buf), |
| + }; |
| + |
| + TEST(err = ioctl(fd, FS_IOC_READ_VERITY_METADATA, &args), |
| + err == min(args.length, (file->mtree_block_count - i) * |
| + INCFS_DATA_FILE_BLOCK_SIZE)); |
| + TESTEQUAL(memcmp(buf, file->mtree[i].data, err), 0); |
| + |
| + i += blocks_to_read; |
| + } |
| + |
| + result = TEST_SUCCESS; |
| + |
| +out: |
| + free(buf); |
| + close(fd); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int hash_tree_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + const int corrupted_file_idx = 5; |
| + int i = 0; |
| + int cmd_fd = -1; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + /* Mount FS and release the backing file. */ |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Write hashes and data. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + int res; |
| + |
| + build_mtree(file); |
| + res = crypto_emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, file->root_hash, |
| + file->sig.add_data); |
| + |
| + if (i == corrupted_file_idx) { |
| + /* Corrupt third blocks hash */ |
| + file->mtree[0].data[2 * SHA256_DIGEST_SIZE] ^= 0xff; |
| + } |
| + if (emit_test_file_data(mount_dir, file)) |
| + goto failure; |
| + |
| + res = load_hash_tree(mount_dir, file); |
| + if (res) { |
| + ksft_print_msg("Can't load hashes for %s. error: %s\n", |
| + file->name, strerror(-res)); |
| + goto failure; |
| + } |
| + } |
| + |
| + /* Validate data */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (i == corrupted_file_idx) { |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + char *filename = |
| + concat_file_name(mount_dir, file->name); |
| + int res; |
| + |
| + res = read_test_file(data, INCFS_DATA_FILE_BLOCK_SIZE, |
| + filename, 2); |
| + free(filename); |
| + if (res != -EBADMSG) { |
| + ksft_print_msg("Hash violation missed1. %d\n", |
| + res); |
| + goto failure; |
| + } |
| + } else if (validate_test_file_content(mount_dir, file) < 0) |
| + goto failure; |
| + else if (validate_hash_tree(mount_dir, file) < 0) |
| + goto failure; |
| + } |
| + |
| + /* Unmount and mount again, to that hashes are persistent. */ |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + if (umount(mount_dir) != 0) { |
| + print_error("Can't unmout FS"); |
| + goto failure; |
| + } |
| + if (mount_fs(mount_dir, backing_dir, 50) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Validate data again */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (i == corrupted_file_idx) { |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + char *filename = |
| + concat_file_name(mount_dir, file->name); |
| + int res; |
| + |
| + res = read_test_file(data, INCFS_DATA_FILE_BLOCK_SIZE, |
| + filename, 2); |
| + free(filename); |
| + if (res != -EBADMSG) { |
| + ksft_print_msg("Hash violation missed2. %d\n", |
| + res); |
| + goto failure; |
| + } |
| + } else if (validate_test_file_content(mount_dir, file) < 0) |
| + goto failure; |
| + } |
| + result = TEST_SUCCESS; |
| + |
| +failure: |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + free(file->mtree); |
| + } |
| + |
| + close(cmd_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return result; |
| +} |
| + |
| +enum expected_log { FULL_LOG, NO_LOG, PARTIAL_LOG }; |
| + |
| +static int validate_logs(const char *mount_dir, int log_fd, |
| + struct test_file *file, |
| + enum expected_log expected_log, |
| + bool report_uid, bool expect_data) |
| +{ |
| + int result = TEST_FAILURE; |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + struct incfs_pending_read_info prs[2048] = {}; |
| + struct incfs_pending_read_info2 prs2[2048] = {}; |
| + struct incfs_pending_read_info *previous_record = NULL; |
| + int prs_size = ARRAY_SIZE(prs); |
| + int block_count = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + int expected_read_count, read_count, block_index, read_index; |
| + char *filename = NULL; |
| + int fd = -1; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + |
| + if (block_count > prs_size) |
| + block_count = prs_size; |
| + expected_read_count = block_count; |
| + |
| + for (block_index = 0; block_index < block_count; block_index++) { |
| + int result = pread(fd, data, sizeof(data), |
| + INCFS_DATA_FILE_BLOCK_SIZE * block_index); |
| + |
| + /* Make some read logs of type SAME_FILE_NEXT_BLOCK */ |
| + if (block_index % 100 == 10) |
| + usleep(20000); |
| + |
| + /* Skip some blocks to make logs of type SAME_FILE */ |
| + if (block_index % 10 == 5) { |
| + ++block_index; |
| + --expected_read_count; |
| + } |
| + |
| + if (expect_data) |
| + TESTCOND(result > 0); |
| + |
| + if (!expect_data) |
| + TESTEQUAL(result, -1); |
| + } |
| + |
| + if (report_uid) |
| + read_count = wait_for_pending_reads2(log_fd, |
| + expected_log == NO_LOG ? 10 : 0, |
| + prs2, prs_size); |
| + else |
| + read_count = wait_for_pending_reads(log_fd, |
| + expected_log == NO_LOG ? 10 : 0, |
| + prs, prs_size); |
| + |
| + if (expected_log == NO_LOG) |
| + TESTEQUAL(read_count, 0); |
| + |
| + if (expected_log == PARTIAL_LOG) |
| + TESTCOND(read_count > 0 && |
| + read_count <= expected_read_count); |
| + |
| + if (expected_log == FULL_LOG) |
| + TESTEQUAL(read_count, expected_read_count); |
| + |
| + /* If read less than expected, advance block_index appropriately */ |
| + for (block_index = 0, read_index = 0; |
| + read_index < expected_read_count - read_count; |
| + block_index++, read_index++) |
| + if (block_index % 10 == 5) |
| + ++block_index; |
| + |
| + for (read_index = 0; read_index < read_count; |
| + block_index++, read_index++) { |
| + struct incfs_pending_read_info *record = report_uid ? |
| + (struct incfs_pending_read_info *) &prs2[read_index] : |
| + &prs[read_index]; |
| + |
| + TESTCOND(same_id(&record->file_id, &file->id)); |
| + TESTEQUAL(record->block_index, block_index); |
| + TESTNE(record->timestamp_us, 0); |
| + if (previous_record) |
| + TESTEQUAL(record->serial_number, |
| + previous_record->serial_number + 1); |
| + |
| + previous_record = record; |
| + if (block_index % 10 == 5) |
| + ++block_index; |
| + } |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int read_log_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + int i = 0; |
| + int cmd_fd = -1, log_fd = -1; |
| + char *backing_dir = NULL; |
| + |
| + /* Create files */ |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "readahead=0,report_uid,read_timeout_ms=0", |
| + false), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL), 0); |
| + } |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + |
| + /* Validate logs */ |
| + TEST(log_fd = open_log_file(mount_dir), log_fd != -1); |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + FULL_LOG, true, false), 0); |
| + |
| + /* Unmount and mount again without report_uid */ |
| + close(log_fd); |
| + log_fd = -1; |
| + TESTEQUAL(umount(mount_dir), 0); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "readahead=0,read_timeout_ms=0", false), 0); |
| + |
| + TEST(log_fd = open_log_file(mount_dir), log_fd != -1); |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + FULL_LOG, false, false), 0); |
| + |
| + /* No read log to make sure poll doesn't crash */ |
| + close(log_fd); |
| + log_fd = -1; |
| + TESTEQUAL(umount(mount_dir), 0); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "readahead=0,rlog_pages=0,read_timeout_ms=0", |
| + false), 0); |
| + |
| + TEST(log_fd = open_log_file(mount_dir), log_fd != -1); |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + NO_LOG, false, false), 0); |
| + |
| + /* Remount and check that logs start working again */ |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "readahead=0,rlog_pages=1,read_timeout_ms=0", |
| + true), 0); |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + PARTIAL_LOG, false, false), 0); |
| + |
| + /* Remount and check that logs continue working */ |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "readahead=0,rlog_pages=4,read_timeout_ms=0", |
| + true), 0); |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + FULL_LOG, false, false), 0); |
| + |
| + /* Check logs work with data */ |
| + for (i = 0; i < file_num; i++) { |
| + TESTEQUAL(emit_test_file_data(mount_dir, &test.files[i]), 0); |
| + TESTEQUAL(validate_logs(mount_dir, log_fd, &test.files[i], |
| + FULL_LOG, false, true), 0); |
| + } |
| + |
| + /* Final unmount */ |
| + close(log_fd); |
| + log_fd = -1; |
| + TESTEQUAL(umount(mount_dir), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + close(log_fd); |
| + free(backing_dir); |
| + umount(mount_dir); |
| + return result; |
| +} |
| + |
| +static int emit_partial_test_file_data(const char *mount_dir, |
| + struct test_file *file) |
| +{ |
| + int i, j; |
| + int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + int *block_indexes = NULL; |
| + int result = 0; |
| + int blocks_written = 0; |
| + int bw_fd = -1; |
| + char buffer[20]; |
| + struct pollfd pollfd; |
| + long blocks_written_total, blocks_written_new_total; |
| + |
| + if (file->size == 0) |
| + return 0; |
| + |
| + bw_fd = open_blocks_written_file(mount_dir); |
| + if (bw_fd == -1) |
| + return -errno; |
| + |
| + result = read(bw_fd, buffer, sizeof(buffer)); |
| + if (result <= 0) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + |
| + buffer[result] = 0; |
| + blocks_written_total = strtol(buffer, NULL, 10); |
| + result = 0; |
| + |
| + pollfd = (struct pollfd) { |
| + .fd = bw_fd, |
| + .events = POLLIN, |
| + }; |
| + |
| + result = poll(&pollfd, 1, 0); |
| + if (result) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + |
| + /* Emit 2 blocks, skip 2 blocks etc*/ |
| + block_indexes = calloc(block_cnt, sizeof(*block_indexes)); |
| + for (i = 0, j = 0; i < block_cnt; ++i) |
| + if ((i & 2) == 0) { |
| + block_indexes[j] = i; |
| + ++j; |
| + } |
| + |
| + for (i = 0; i < j; i += blocks_written) { |
| + blocks_written = emit_test_blocks(mount_dir, file, |
| + block_indexes + i, j - i); |
| + if (blocks_written < 0) { |
| + result = blocks_written; |
| + goto out; |
| + } |
| + if (blocks_written == 0) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + |
| + result = poll(&pollfd, 1, 0); |
| + if (result != 1 || pollfd.revents != POLLIN) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + |
| + result = read(bw_fd, buffer, sizeof(buffer)); |
| + buffer[result] = 0; |
| + blocks_written_new_total = strtol(buffer, NULL, 10); |
| + |
| + if (blocks_written_new_total - blocks_written_total |
| + != blocks_written) { |
| + result = -EIO; |
| + goto out; |
| + } |
| + |
| + blocks_written_total = blocks_written_new_total; |
| + result = 0; |
| + } |
| +out: |
| + free(block_indexes); |
| + close(bw_fd); |
| + return result; |
| +} |
| + |
| +static int validate_ranges(const char *mount_dir, struct test_file *file) |
| +{ |
| + int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + int fd; |
| + struct incfs_filled_range ranges[128]; |
| + struct incfs_get_filled_blocks_args fba = { |
| + .range_buffer = ptr_to_u64(ranges), |
| + .range_buffer_size = sizeof(ranges), |
| + }; |
| + int error = TEST_SUCCESS; |
| + int i; |
| + int range_cnt; |
| + int cmd_fd = -1; |
| + struct incfs_permit_fill permit_fill; |
| + |
| + fd = open(filename, O_RDONLY | O_CLOEXEC); |
| + free(filename); |
| + if (fd <= 0) |
| + return TEST_FAILURE; |
| + |
| + error = ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + if (error != -1 || errno != EPERM) { |
| + ksft_print_msg("INCFS_IOC_GET_FILLED_BLOCKS not blocked\n"); |
| + error = -EPERM; |
| + goto out; |
| + } |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + permit_fill.file_descriptor = fd; |
| + if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + print_error("INCFS_IOC_PERMIT_FILL failed"); |
| + return -EPERM; |
| + goto out; |
| + } |
| + |
| + error = ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + if (error && errno != ERANGE) |
| + goto out; |
| + |
| + if (error && errno == ERANGE && block_cnt < 509) |
| + goto out; |
| + |
| + if (!error && block_cnt >= 509) { |
| + error = -ERANGE; |
| + goto out; |
| + } |
| + |
| + if (fba.total_blocks_out != block_cnt) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (fba.data_blocks_out != block_cnt) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + range_cnt = (block_cnt + 3) / 4; |
| + if (range_cnt > 128) |
| + range_cnt = 128; |
| + if (range_cnt != fba.range_buffer_size_out / sizeof(*ranges)) { |
| + error = -ERANGE; |
| + goto out; |
| + } |
| + |
| + error = TEST_SUCCESS; |
| + for (i = 0; i < fba.range_buffer_size_out / sizeof(*ranges) - 1; ++i) |
| + if (ranges[i].begin != i * 4 || ranges[i].end != i * 4 + 2) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (ranges[i].begin != i * 4 || |
| + (ranges[i].end != i * 4 + 1 && ranges[i].end != i * 4 + 2)) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + for (i = 0; i < 64; ++i) { |
| + fba.start_index = i * 2; |
| + fba.end_index = i * 2 + 2; |
| + error = ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + if (error) |
| + goto out; |
| + |
| + if (fba.total_blocks_out != block_cnt) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (fba.start_index >= block_cnt) { |
| + if (fba.index_out != fba.start_index) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + break; |
| + } |
| + |
| + if (i % 2) { |
| + if (fba.range_buffer_size_out != 0) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + } else { |
| + if (fba.range_buffer_size_out != sizeof(*ranges)) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (ranges[0].begin != i * 2) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (ranges[0].end != i * 2 + 1 && |
| + ranges[0].end != i * 2 + 2) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + } |
| + } |
| + |
| +out: |
| + close(fd); |
| + close(cmd_fd); |
| + return error; |
| +} |
| + |
| +static int get_blocks_test(const char *mount_dir) |
| +{ |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + /* Write data. */ |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (emit_file(cmd_fd, NULL, file->name, &file->id, file->size, |
| + NULL)) |
| + goto failure; |
| + |
| + if (emit_partial_test_file_data(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (validate_ranges(mount_dir, file)) |
| + goto failure; |
| + |
| + /* |
| + * The smallest files are filled completely, so this checks that |
| + * the fast get_filled_blocks path is not causing issues |
| + */ |
| + if (validate_ranges(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +static int emit_partial_test_file_hash(const char *mount_dir, |
| + struct test_file *file) |
| +{ |
| + int err; |
| + int fd; |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = 1, |
| + }; |
| + struct incfs_fill_block *fill_block_array = |
| + calloc(fill_blocks.count, sizeof(struct incfs_fill_block)); |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + |
| + if (file->size <= 4096 / 32 * 4096) |
| + return 0; |
| + |
| + if (!fill_block_array) |
| + return -ENOMEM; |
| + fill_blocks.fill_blocks = ptr_to_u64(fill_block_array); |
| + |
| + rnd_buf(data, sizeof(data), 0); |
| + |
| + fill_block_array[0] = |
| + (struct incfs_fill_block){ .block_index = 1, |
| + .data_len = |
| + INCFS_DATA_FILE_BLOCK_SIZE, |
| + .data = ptr_to_u64(data), |
| + .flags = INCFS_BLOCK_FLAGS_HASH }; |
| + |
| + fd = open_file_by_id(mount_dir, file->id, true); |
| + if (fd < 0) { |
| + err = errno; |
| + goto failure; |
| + } |
| + |
| + err = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| + close(fd); |
| + if (err < fill_blocks.count) |
| + err = errno; |
| + else |
| + err = 0; |
| + |
| +failure: |
| + free(fill_block_array); |
| + return err; |
| +} |
| + |
| +static int validate_hash_ranges(const char *mount_dir, struct test_file *file) |
| +{ |
| + int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + int fd; |
| + struct incfs_filled_range ranges[128]; |
| + struct incfs_get_filled_blocks_args fba = { |
| + .range_buffer = ptr_to_u64(ranges), |
| + .range_buffer_size = sizeof(ranges), |
| + }; |
| + int error = TEST_SUCCESS; |
| + int file_blocks = (file->size + INCFS_DATA_FILE_BLOCK_SIZE - 1) / |
| + INCFS_DATA_FILE_BLOCK_SIZE; |
| + int cmd_fd = -1; |
| + struct incfs_permit_fill permit_fill; |
| + |
| + if (file->size <= 4096 / 32 * 4096) |
| + return 0; |
| + |
| + fd = open(filename, O_RDONLY | O_CLOEXEC); |
| + free(filename); |
| + if (fd <= 0) |
| + return TEST_FAILURE; |
| + |
| + error = ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + if (error != -1 || errno != EPERM) { |
| + ksft_print_msg("INCFS_IOC_GET_FILLED_BLOCKS not blocked\n"); |
| + error = -EPERM; |
| + goto out; |
| + } |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + permit_fill.file_descriptor = fd; |
| + if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) { |
| + print_error("INCFS_IOC_PERMIT_FILL failed"); |
| + return -EPERM; |
| + goto out; |
| + } |
| + |
| + error = ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + if (error) |
| + goto out; |
| + |
| + if (fba.total_blocks_out <= block_cnt) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (fba.data_blocks_out != block_cnt) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (fba.range_buffer_size_out != sizeof(struct incfs_filled_range)) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| + if (ranges[0].begin != file_blocks + 1 || |
| + ranges[0].end != file_blocks + 2) { |
| + error = -EINVAL; |
| + goto out; |
| + } |
| + |
| +out: |
| + close(cmd_fd); |
| + close(fd); |
| + return error; |
| +} |
| + |
| +static int get_hash_blocks_test(const char *mount_dir) |
| +{ |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (crypto_emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, file->root_hash, |
| + file->sig.add_data)) |
| + goto failure; |
| + |
| + if (emit_partial_test_file_hash(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + if (validate_hash_ranges(mount_dir, file)) |
| + goto failure; |
| + } |
| + |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return TEST_SUCCESS; |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return TEST_FAILURE; |
| +} |
| + |
| +#define THREE_GB (3LL * 1024 * 1024 * 1024) |
| +#define FOUR_GB (4LL * 1024 * 1024 * 1024) /* Have 1GB of margin */ |
| +static int large_file_test(const char *mount_dir) |
| +{ |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i; |
| + int result = TEST_FAILURE, ret; |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| + int block_count = THREE_GB / INCFS_DATA_FILE_BLOCK_SIZE; |
| + struct incfs_fill_block *block_buf = |
| + calloc(block_count, sizeof(struct incfs_fill_block)); |
| + struct incfs_fill_blocks fill_blocks = { |
| + .count = block_count, |
| + .fill_blocks = ptr_to_u64(block_buf), |
| + }; |
| + incfs_uuid_t id; |
| + int fd = -1; |
| + struct statvfs svfs; |
| + unsigned long long free_disksz; |
| + |
| + ret = statvfs(mount_dir, &svfs); |
| + if (ret) { |
| + ksft_print_msg("Can't get disk size. Skipping %s...\n", __func__); |
| + return TEST_SKIP; |
| + } |
| + |
| + free_disksz = (unsigned long long)svfs.f_bavail * svfs.f_bsize; |
| + |
| + if (FOUR_GB > free_disksz) { |
| + ksft_print_msg("Not enough free disk space (%lldMB). Skipping %s...\n", |
| + free_disksz >> 20, __func__); |
| + return TEST_SKIP; |
| + } |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + if (emit_file(cmd_fd, NULL, "very_large_file", &id, |
| + (uint64_t)block_count * INCFS_DATA_FILE_BLOCK_SIZE, |
| + NULL) < 0) |
| + goto failure; |
| + |
| + for (i = 0; i < block_count; i++) { |
| + block_buf[i].compression = COMPRESSION_NONE; |
| + block_buf[i].block_index = i; |
| + block_buf[i].data_len = INCFS_DATA_FILE_BLOCK_SIZE; |
| + block_buf[i].data = ptr_to_u64(data); |
| + } |
| + |
| + fd = open_file_by_id(mount_dir, id, true); |
| + if (fd < 0) |
| + goto failure; |
| + |
| + if (ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks) != block_count) |
| + goto failure; |
| + |
| + if (emit_file(cmd_fd, NULL, "very_very_large_file", &id, 1LL << 40, |
| + NULL) < 0) |
| + goto failure; |
| + |
| + result = TEST_SUCCESS; |
| + |
| +failure: |
| + close(fd); |
| + close(cmd_fd); |
| + unlink("very_large_file"); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int validate_mapped_file(const char *orig_name, const char *name, |
| + size_t size, size_t offset) |
| +{ |
| + struct stat st; |
| + int orig_fd = -1, fd = -1; |
| + size_t block; |
| + int result = TEST_FAILURE; |
| + |
| + if (stat(name, &st)) { |
| + ksft_print_msg("Failed to stat %s with error %s\n", |
| + name, strerror(errno)); |
| + goto failure; |
| + } |
| + |
| + if (size != st.st_size) { |
| + ksft_print_msg("Mismatched file sizes for file %s - expected %llu, got %llu\n", |
| + name, size, st.st_size); |
| + goto failure; |
| + } |
| + |
| + fd = open(name, O_RDONLY | O_CLOEXEC); |
| + if (fd == -1) { |
| + ksft_print_msg("Failed to open %s with error %s\n", name, |
| + strerror(errno)); |
| + goto failure; |
| + } |
| + |
| + orig_fd = open(orig_name, O_RDONLY | O_CLOEXEC); |
| + if (orig_fd == -1) { |
| + ksft_print_msg("Failed to open %s with error %s\n", orig_name, |
| + strerror(errno)); |
| + goto failure; |
| + } |
| + |
| + for (block = 0; block < size; block += INCFS_DATA_FILE_BLOCK_SIZE) { |
| + uint8_t orig_data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + ssize_t orig_read, mapped_read; |
| + |
| + orig_read = pread(orig_fd, orig_data, |
| + INCFS_DATA_FILE_BLOCK_SIZE, block + offset); |
| + mapped_read = pread(fd, data, INCFS_DATA_FILE_BLOCK_SIZE, |
| + block); |
| + |
| + if (orig_read < mapped_read || |
| + mapped_read != min(size - block, |
| + INCFS_DATA_FILE_BLOCK_SIZE)) { |
| + ksft_print_msg("Failed to read enough data: %llu %llu %llu %lld %lld\n", |
| + block, size, offset, orig_read, |
| + mapped_read); |
| + goto failure; |
| + } |
| + |
| + if (memcmp(orig_data, data, mapped_read)) { |
| + ksft_print_msg("Data doesn't match: %llu %llu %llu %lld %lld\n", |
| + block, size, offset, orig_read, |
| + mapped_read); |
| + goto failure; |
| + } |
| + } |
| + |
| + result = TEST_SUCCESS; |
| + |
| +failure: |
| + close(orig_fd); |
| + close(fd); |
| + return result; |
| +} |
| + |
| +static int mapped_file_test(const char *mount_dir) |
| +{ |
| + char *backing_dir; |
| + int result = TEST_FAILURE; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + |
| + backing_dir = create_backing_dir(mount_dir); |
| + if (!backing_dir) |
| + goto failure; |
| + |
| + if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| + goto failure; |
| + |
| + cmd_fd = open_commands_file(mount_dir); |
| + if (cmd_fd < 0) |
| + goto failure; |
| + |
| + for (i = 0; i < file_num; ++i) { |
| + struct test_file *file = &test.files[i]; |
| + size_t blocks = file->size / INCFS_DATA_FILE_BLOCK_SIZE; |
| + size_t mapped_offset = blocks / 4 * |
| + INCFS_DATA_FILE_BLOCK_SIZE; |
| + size_t mapped_size = file->size / 4 * 3 - mapped_offset; |
| + struct incfs_create_mapped_file_args mfa; |
| + char mapped_file_name[FILENAME_MAX]; |
| + char orig_file_path[PATH_MAX]; |
| + char mapped_file_path[PATH_MAX]; |
| + |
| + if (emit_file(cmd_fd, NULL, file->name, &file->id, file->size, |
| + NULL) < 0) |
| + goto failure; |
| + |
| + if (emit_test_file_data(mount_dir, file)) |
| + goto failure; |
| + |
| + if (snprintf(mapped_file_name, ARRAY_SIZE(mapped_file_name), |
| + "%s.mapped", file->name) < 0) |
| + goto failure; |
| + |
| + mfa = (struct incfs_create_mapped_file_args) { |
| + .size = mapped_size, |
| + .mode = 0664, |
| + .file_name = ptr_to_u64(mapped_file_name), |
| + .source_file_id = file->id, |
| + .source_offset = mapped_offset, |
| + }; |
| + |
| + result = ioctl(cmd_fd, INCFS_IOC_CREATE_MAPPED_FILE, &mfa); |
| + if (result) { |
| + ksft_print_msg( |
| + "Failed to create mapped file with error %d\n", |
| + result); |
| + goto failure; |
| + } |
| + |
| + result = snprintf(orig_file_path, |
| + ARRAY_SIZE(orig_file_path), "%s/%s", |
| + mount_dir, file->name); |
| + |
| + if (result < 0 || result >= ARRAY_SIZE(mapped_file_path)) { |
| + result = TEST_FAILURE; |
| + goto failure; |
| + } |
| + |
| + result = snprintf(mapped_file_path, |
| + ARRAY_SIZE(mapped_file_path), "%s/%s", |
| + mount_dir, mapped_file_name); |
| + |
| + if (result < 0 || result >= ARRAY_SIZE(mapped_file_path)) { |
| + result = TEST_FAILURE; |
| + goto failure; |
| + } |
| + |
| + result = validate_mapped_file(orig_file_path, mapped_file_path, |
| + mapped_size, mapped_offset); |
| + if (result) |
| + goto failure; |
| + } |
| + |
| +failure: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static const char v1_file[] = { |
| + /* Header */ |
| + /* 0x00: Magic number */ |
| + 0x49, 0x4e, 0x43, 0x46, 0x53, 0x00, 0x00, 0x00, |
| + /* 0x08: Version */ |
| + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x10: Header size */ |
| + 0x38, 0x00, |
| + /* 0x12: Block size */ |
| + 0x00, 0x10, |
| + /* 0x14: Flags */ |
| + 0x00, 0x00, 0x00, 0x00, |
| + /* 0x18: First md offset */ |
| + 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x20: File size */ |
| + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x28: UUID */ |
| + 0x8c, 0x7d, 0xd9, 0x22, 0xad, 0x47, 0x49, 0x4f, |
| + 0xc0, 0x2c, 0x38, 0x8e, 0x12, 0xc0, 0x0e, 0xac, |
| + |
| + /* 0x38: Attribute */ |
| + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, |
| + 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, |
| + |
| + /* Attribute md record */ |
| + /* 0x46: Type */ |
| + 0x02, |
| + /* 0x47: Size */ |
| + 0x25, 0x00, |
| + /* 0x49: CRC */ |
| + 0x9a, 0xef, 0xef, 0x72, |
| + /* 0x4d: Next md offset */ |
| + 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x55: Prev md offset */ |
| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x5d: fa_offset */ |
| + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x65: fa_size */ |
| + 0x0e, 0x00, |
| + /* 0x67: fa_crc */ |
| + 0xfb, 0x5e, 0x72, 0x89, |
| + |
| + /* Blockmap table */ |
| + /* 0x6b: First 10-byte entry */ |
| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + |
| + /* Blockmap md record */ |
| + /* 0x75: Type */ |
| + 0x01, |
| + /* 0x76: Size */ |
| + 0x23, 0x00, |
| + /* 0x78: CRC */ |
| + 0x74, 0x45, 0xd3, 0xb9, |
| + /* 0x7c: Next md offset */ |
| + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x84: Prev md offset */ |
| + 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x8c: blockmap offset */ |
| + 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| + /* 0x94: blockmap count */ |
| + 0x01, 0x00, 0x00, 0x00, |
| +}; |
| + |
| +static int compatibility_test(const char *mount_dir) |
| +{ |
| + static const char *name = "file"; |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + char *filename = NULL; |
| + int fd = -1; |
| + uint64_t size = 0x0c; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TEST(filename = concat_file_name(backing_dir, name), filename); |
| + TEST(fd = open(filename, O_CREAT | O_WRONLY | O_CLOEXEC, 0777), |
| + fd != -1); |
| + TESTEQUAL(write(fd, v1_file, sizeof(v1_file)), sizeof(v1_file)); |
| + TESTEQUAL(fsetxattr(fd, INCFS_XATTR_SIZE_NAME, &size, sizeof(size), 0), |
| + 0); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 50), 0); |
| + free(filename); |
| + TEST(filename = concat_file_name(mount_dir, name), filename); |
| + close(fd); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int zero_blocks_written_count(int fd, uint32_t data_blocks_written, |
| + uint32_t hash_blocks_written) |
| +{ |
| + int test_result = TEST_FAILURE; |
| + uint64_t offset; |
| + uint8_t type; |
| + uint32_t bw; |
| + |
| + /* Get first md record */ |
| + TESTEQUAL(pread(fd, &offset, sizeof(offset), 24), sizeof(offset)); |
| + |
| + /* Find status md record */ |
| + for (;;) { |
| + TESTNE(offset, 0); |
| + TESTEQUAL(pread(fd, &type, sizeof(type), le64_to_cpu(offset)), |
| + sizeof(type)); |
| + if (type == 4) |
| + break; |
| + TESTEQUAL(pread(fd, &offset, sizeof(offset), |
| + le64_to_cpu(offset) + 7), |
| + sizeof(offset)); |
| + } |
| + |
| + /* Read blocks_written */ |
| + offset = le64_to_cpu(offset); |
| + TESTEQUAL(pread(fd, &bw, sizeof(bw), offset + 23), sizeof(bw)); |
| + TESTEQUAL(le32_to_cpu(bw), data_blocks_written); |
| + TESTEQUAL(pread(fd, &bw, sizeof(bw), offset + 27), sizeof(bw)); |
| + TESTEQUAL(le32_to_cpu(bw), hash_blocks_written); |
| + |
| + /* Write out zero */ |
| + bw = 0; |
| + TESTEQUAL(pwrite(fd, &bw, sizeof(bw), offset + 23), sizeof(bw)); |
| + TESTEQUAL(pwrite(fd, &bw, sizeof(bw), offset + 27), sizeof(bw)); |
| + |
| + test_result = TEST_SUCCESS; |
| +out: |
| + return test_result; |
| +} |
| + |
| +static int validate_block_count(const char *mount_dir, const char *backing_dir, |
| + struct test_file *file, |
| + int total_data_blocks, int filled_data_blocks, |
| + int total_hash_blocks, int filled_hash_blocks) |
| +{ |
| + char *filename = NULL; |
| + char *backing_filename = NULL; |
| + int fd = -1; |
| + struct incfs_get_block_count_args bca = {}; |
| + int test_result = TEST_FAILURE; |
| + struct incfs_filled_range ranges[128]; |
| + struct incfs_get_filled_blocks_args fba = { |
| + .range_buffer = ptr_to_u64(ranges), |
| + .range_buffer_size = sizeof(ranges), |
| + }; |
| + int cmd_fd = -1; |
| + struct incfs_permit_fill permit_fill; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(backing_filename = concat_file_name(backing_dir, file->name), |
| + backing_filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, filled_data_blocks); |
| + TESTEQUAL(bca.total_hash_blocks_out, total_hash_blocks); |
| + TESTEQUAL(bca.filled_hash_blocks_out, filled_hash_blocks); |
| + |
| + close(fd); |
| + TESTEQUAL(umount(mount_dir), 0); |
| + TEST(fd = open(backing_filename, O_RDWR | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(zero_blocks_written_count(fd, filled_data_blocks, |
| + filled_hash_blocks), |
| + TEST_SUCCESS); |
| + close(fd); |
| + fd = -1; |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false), |
| + 0); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, 0); |
| + TESTEQUAL(bca.total_hash_blocks_out, total_hash_blocks); |
| + TESTEQUAL(bca.filled_hash_blocks_out, 0); |
| + |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + permit_fill.file_descriptor = fd; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill), 0); |
| + do { |
| + ioctl(fd, INCFS_IOC_GET_FILLED_BLOCKS, &fba); |
| + fba.start_index = fba.index_out + 1; |
| + } while (fba.index_out < fba.total_blocks_out); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, filled_data_blocks); |
| + TESTEQUAL(bca.total_hash_blocks_out, total_hash_blocks); |
| + TESTEQUAL(bca.filled_hash_blocks_out, filled_hash_blocks); |
| + |
| + test_result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + close(fd); |
| + free(filename); |
| + free(backing_filename); |
| + return test_result; |
| +} |
| + |
| + |
| + |
| +static int validate_data_block_count(const char *mount_dir, |
| + const char *backing_dir, |
| + struct test_file *file) |
| +{ |
| + const int total_data_blocks = 1 + (file->size - 1) / |
| + INCFS_DATA_FILE_BLOCK_SIZE; |
| + const int filled_data_blocks = (total_data_blocks + 1) / 2; |
| + |
| + int test_result = TEST_FAILURE; |
| + char *filename = NULL; |
| + char *incomplete_filename = NULL; |
| + struct stat stat_buf_incomplete, stat_buf_file; |
| + int fd = -1; |
| + struct incfs_get_block_count_args bca = {}; |
| + int i; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(incomplete_filename = get_incomplete_filename(mount_dir, file->id), |
| + incomplete_filename); |
| + |
| + TESTEQUAL(stat(filename, &stat_buf_file), 0); |
| + TESTEQUAL(stat(incomplete_filename, &stat_buf_incomplete), 0); |
| + TESTEQUAL(stat_buf_file.st_ino, stat_buf_incomplete.st_ino); |
| + TESTEQUAL(stat_buf_file.st_nlink, 3); |
| + |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, 0); |
| + TESTEQUAL(bca.total_hash_blocks_out, 0); |
| + TESTEQUAL(bca.filled_hash_blocks_out, 0); |
| + |
| + for (i = 0; i < total_data_blocks; i += 2) |
| + TESTEQUAL(emit_test_block(mount_dir, file, i), 0); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, filled_data_blocks); |
| + TESTEQUAL(bca.total_hash_blocks_out, 0); |
| + TESTEQUAL(bca.filled_hash_blocks_out, 0); |
| + close(fd); |
| + fd = -1; |
| + |
| + TESTEQUAL(validate_block_count(mount_dir, backing_dir, file, |
| + total_data_blocks, filled_data_blocks, |
| + 0, 0), |
| + 0); |
| + |
| + for (i = 1; i < total_data_blocks; i += 2) |
| + TESTEQUAL(emit_test_block(mount_dir, file, i), 0); |
| + |
| + TESTEQUAL(stat(incomplete_filename, &stat_buf_incomplete), -1); |
| + TESTEQUAL(errno, ENOENT); |
| + |
| + test_result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + free(incomplete_filename); |
| + free(filename); |
| + return test_result; |
| +} |
| + |
| +static int data_block_count_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false), |
| + 0); |
| + |
| + for (i = 0; i < test.files_count; ++i) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL), |
| + 0); |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + |
| + TESTEQUAL(validate_data_block_count(mount_dir, backing_dir, |
| + file), |
| + 0); |
| + } |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int validate_hash_block_count(const char *mount_dir, |
| + const char *backing_dir, |
| + struct test_file *file) |
| +{ |
| + const int digest_size = SHA256_DIGEST_SIZE; |
| + const int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size; |
| + const int total_data_blocks = 1 + (file->size - 1) / |
| + INCFS_DATA_FILE_BLOCK_SIZE; |
| + |
| + int result = TEST_FAILURE; |
| + int hash_layer = total_data_blocks; |
| + int total_hash_blocks = 0; |
| + int filled_hash_blocks; |
| + char *filename = NULL; |
| + int fd = -1; |
| + struct incfs_get_block_count_args bca = {}; |
| + |
| + while (hash_layer > 1) { |
| + hash_layer = (hash_layer + hash_per_block - 1) / hash_per_block; |
| + total_hash_blocks += hash_layer; |
| + } |
| + filled_hash_blocks = total_hash_blocks > 1 ? 1 : 0; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, 0); |
| + TESTEQUAL(bca.total_hash_blocks_out, total_hash_blocks); |
| + TESTEQUAL(bca.filled_hash_blocks_out, 0); |
| + |
| + TESTEQUAL(emit_partial_test_file_hash(mount_dir, file), 0); |
| + |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &bca), 0); |
| + TESTEQUAL(bca.total_data_blocks_out, total_data_blocks); |
| + TESTEQUAL(bca.filled_data_blocks_out, 0); |
| + TESTEQUAL(bca.total_hash_blocks_out, total_hash_blocks); |
| + TESTEQUAL(bca.filled_hash_blocks_out, filled_hash_blocks); |
| + close(fd); |
| + fd = -1; |
| + |
| + if (filled_hash_blocks) |
| + TESTEQUAL(validate_block_count(mount_dir, backing_dir, file, |
| + total_data_blocks, 0, |
| + total_hash_blocks, filled_hash_blocks), |
| + 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int hash_block_count_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false), |
| + 0); |
| + |
| + for (i = 0; i < test.files_count; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, file->root_hash, |
| + file->sig.add_data), |
| + 0); |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + |
| + TESTEQUAL(validate_hash_block_count(mount_dir, backing_dir, |
| + &test.files[i]), |
| + 0); |
| + } |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int is_close(struct timespec *start, int expected_ms) |
| +{ |
| + const int allowed_variance = 100; |
| + int result = TEST_FAILURE; |
| + struct timespec finish; |
| + int diff; |
| + |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &finish), 0); |
| + diff = (finish.tv_sec - start->tv_sec) * 1000 + |
| + (finish.tv_nsec - start->tv_nsec) / 1000000; |
| + |
| + TESTCOND(diff >= expected_ms - allowed_variance); |
| + TESTCOND(diff <= expected_ms + allowed_variance); |
| + result = TEST_SUCCESS; |
| +out: |
| + return result; |
| +} |
| + |
| +static int per_uid_read_timeouts_test(const char *mount_dir) |
| +{ |
| + struct test_file file = { |
| + .name = "file", |
| + .size = 16 * INCFS_DATA_FILE_BLOCK_SIZE |
| + }; |
| + |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + int pid = -1; |
| + int cmd_fd = -1; |
| + char *filename = NULL; |
| + int fd = -1; |
| + struct timespec start; |
| + char buffer[4096]; |
| + struct incfs_per_uid_read_timeouts purt_get[1]; |
| + struct incfs_get_read_timeouts_args grt = { |
| + ptr_to_u64(purt_get), |
| + sizeof(purt_get) |
| + }; |
| + struct incfs_per_uid_read_timeouts purt_set[] = { |
| + { |
| + .uid = 0, |
| + .min_time_us = 1000000, |
| + .min_pending_time_us = 2000000, |
| + .max_pending_time_us = 3000000, |
| + }, |
| + }; |
| + struct incfs_set_read_timeouts_args srt = { |
| + ptr_to_u64(purt_set), |
| + sizeof(purt_set) |
| + }; |
| + int status; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "read_timeout_ms=1000,readahead=0", false), 0); |
| + |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, |
| + NULL), 0); |
| + |
| + TEST(filename = concat_file_name(mount_dir, file.name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC), 0); |
| + |
| + /* Default mount options read failure is 1000 */ |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), 0), -1); |
| + TESTEQUAL(is_close(&start, 1000), 0); |
| + |
| + grt.timeouts_array_size = 0; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_GET_READ_TIMEOUTS, &grt), 0); |
| + TESTEQUAL(grt.timeouts_array_size_out, 0); |
| + |
| + /* Set it to 3000 */ |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0); |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), 0), -1); |
| + TESTEQUAL(is_close(&start, 3000), 0); |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_GET_READ_TIMEOUTS, &grt), -1); |
| + TESTEQUAL(errno, E2BIG); |
| + TESTEQUAL(grt.timeouts_array_size_out, sizeof(purt_get)); |
| + grt.timeouts_array_size = sizeof(purt_get); |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_GET_READ_TIMEOUTS, &grt), 0); |
| + TESTEQUAL(grt.timeouts_array_size_out, sizeof(purt_get)); |
| + TESTEQUAL(purt_get[0].uid, purt_set[0].uid); |
| + TESTEQUAL(purt_get[0].min_time_us, purt_set[0].min_time_us); |
| + TESTEQUAL(purt_get[0].min_pending_time_us, |
| + purt_set[0].min_pending_time_us); |
| + TESTEQUAL(purt_get[0].max_pending_time_us, |
| + purt_set[0].max_pending_time_us); |
| + |
| + /* Still 1000 in UID 2 */ |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TEST(pid = fork(), pid != -1); |
| + if (pid == 0) { |
| + TESTEQUAL(setuid(2), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), 0), -1); |
| + exit(0); |
| + } |
| + TESTNE(wait(&status), -1); |
| + TESTEQUAL(WEXITSTATUS(status), 0); |
| + TESTEQUAL(is_close(&start, 1000), 0); |
| + |
| + /* Set it to default */ |
| + purt_set[0].max_pending_time_us = UINT32_MAX; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0); |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), 0), -1); |
| + TESTEQUAL(is_close(&start, 1000), 0); |
| + |
| + /* Test min read time */ |
| + TESTEQUAL(emit_test_block(mount_dir, &file, 0), 0); |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), 0), sizeof(buffer)); |
| + TESTEQUAL(is_close(&start, 1000), 0); |
| + |
| + /* Test min pending time */ |
| + purt_set[0].uid = 2; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0); |
| + TESTEQUAL(clock_gettime(CLOCK_MONOTONIC, &start), 0); |
| + TEST(pid = fork(), pid != -1); |
| + if (pid == 0) { |
| + TESTEQUAL(setuid(2), 0); |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), sizeof(buffer)), |
| + sizeof(buffer)); |
| + exit(0); |
| + } |
| + sleep(1); |
| + TESTEQUAL(emit_test_block(mount_dir, &file, 1), 0); |
| + TESTNE(wait(&status), -1); |
| + TESTEQUAL(WEXITSTATUS(status), 0); |
| + TESTEQUAL(is_close(&start, 2000), 0); |
| + |
| + /* Clear timeouts */ |
| + srt.timeouts_array_size = 0; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0); |
| + grt.timeouts_array_size = 0; |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_GET_READ_TIMEOUTS, &grt), 0); |
| + TESTEQUAL(grt.timeouts_array_size_out, 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + |
| + if (pid == 0) |
| + exit(result); |
| + |
| + free(filename); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +#define DIRS 3 |
| +static int inotify_test(const char *mount_dir) |
| +{ |
| + const char *mapped_file_name = "mapped_name"; |
| + struct test_file file = { |
| + .name = "file", |
| + .size = 16 * INCFS_DATA_FILE_BLOCK_SIZE |
| + }; |
| + |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL, *index_dir = NULL, *incomplete_dir = NULL; |
| + char *file_name = NULL; |
| + int cmd_fd = -1; |
| + int notify_fd = -1; |
| + int wds[DIRS]; |
| + char buffer[DIRS * (sizeof(struct inotify_event) + NAME_MAX + 1)]; |
| + char *ptr = buffer; |
| + struct inotify_event *event; |
| + struct inotify_event *events[DIRS] = {}; |
| + const char *names[DIRS] = {}; |
| + int res; |
| + char id[sizeof(incfs_uuid_t) * 2 + 1]; |
| + struct incfs_create_mapped_file_args mfa; |
| + |
| + /* File creation triggers inotify events in .index and .incomplete? */ |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TEST(index_dir = concat_file_name(mount_dir, ".index"), index_dir); |
| + TEST(incomplete_dir = concat_file_name(mount_dir, ".incomplete"), |
| + incomplete_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 50), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TEST(notify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC), |
| + notify_fd != -1); |
| + TEST(wds[0] = inotify_add_watch(notify_fd, mount_dir, |
| + IN_CREATE | IN_DELETE), |
| + wds[0] != -1); |
| + TEST(wds[1] = inotify_add_watch(notify_fd, index_dir, |
| + IN_CREATE | IN_DELETE), |
| + wds[1] != -1); |
| + TEST(wds[2] = inotify_add_watch(notify_fd, incomplete_dir, |
| + IN_CREATE | IN_DELETE), |
| + wds[2] != -1); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, |
| + NULL), 0); |
| + TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1); |
| + |
| + while (ptr < buffer + res) { |
| + int i; |
| + |
| + event = (struct inotify_event *) ptr; |
| + TESTCOND(ptr + sizeof(*event) <= buffer + res); |
| + for (i = 0; i < DIRS; ++i) |
| + if (event->wd == wds[i]) { |
| + TESTEQUAL(events[i], NULL); |
| + events[i] = event; |
| + ptr += sizeof(*event); |
| + names[i] = ptr; |
| + ptr += events[i]->len; |
| + TESTCOND(ptr <= buffer + res); |
| + break; |
| + } |
| + TESTCOND(i < DIRS); |
| + } |
| + |
| + TESTNE(events[0], NULL); |
| + TESTNE(events[1], NULL); |
| + TESTNE(events[2], NULL); |
| + |
| + bin2hex(id, file.id.bytes, sizeof(incfs_uuid_t)); |
| + |
| + TESTEQUAL(events[0]->mask, IN_CREATE); |
| + TESTEQUAL(events[1]->mask, IN_CREATE); |
| + TESTEQUAL(events[2]->mask, IN_CREATE); |
| + TESTEQUAL(strcmp(names[0], file.name), 0); |
| + TESTEQUAL(strcmp(names[1], id), 0); |
| + TESTEQUAL(strcmp(names[2], id), 0); |
| + |
| + /* Creating a mapped file triggers inotify event */ |
| + mfa = (struct incfs_create_mapped_file_args) { |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE, |
| + .mode = 0664, |
| + .file_name = ptr_to_u64(mapped_file_name), |
| + .source_file_id = file.id, |
| + .source_offset = INCFS_DATA_FILE_BLOCK_SIZE, |
| + }; |
| + |
| + TEST(res = ioctl(cmd_fd, INCFS_IOC_CREATE_MAPPED_FILE, &mfa), |
| + res != -1); |
| + TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1); |
| + event = (struct inotify_event *) buffer; |
| + TESTEQUAL(event->wd, wds[0]); |
| + TESTEQUAL(event->mask, IN_CREATE); |
| + TESTEQUAL(strcmp(event->name, mapped_file_name), 0); |
| + |
| + /* File completion triggers inotify event in .incomplete? */ |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1); |
| + event = (struct inotify_event *) buffer; |
| + TESTEQUAL(event->wd, wds[2]); |
| + TESTEQUAL(event->mask, IN_DELETE); |
| + TESTEQUAL(strcmp(event->name, id), 0); |
| + |
| + /* File unlinking triggers inotify event in .index? */ |
| + TEST(file_name = concat_file_name(mount_dir, file.name), file_name); |
| + TESTEQUAL(unlink(file_name), 0); |
| + TEST(res = read(notify_fd, buffer, sizeof(buffer)), res != -1); |
| + memset(events, 0, sizeof(events)); |
| + memset(names, 0, sizeof(names)); |
| + for (ptr = buffer; ptr < buffer + res;) { |
| + event = (struct inotify_event *) ptr; |
| + int i; |
| + |
| + TESTCOND(ptr + sizeof(*event) <= buffer + res); |
| + for (i = 0; i < DIRS; ++i) |
| + if (event->wd == wds[i]) { |
| + TESTEQUAL(events[i], NULL); |
| + events[i] = event; |
| + ptr += sizeof(*event); |
| + names[i] = ptr; |
| + ptr += events[i]->len; |
| + TESTCOND(ptr <= buffer + res); |
| + break; |
| + } |
| + TESTCOND(i < DIRS); |
| + } |
| + |
| + TESTNE(events[0], NULL); |
| + TESTNE(events[1], NULL); |
| + TESTEQUAL(events[2], NULL); |
| + |
| + TESTEQUAL(events[0]->mask, IN_DELETE); |
| + TESTEQUAL(events[1]->mask, IN_DELETE); |
| + TESTEQUAL(strcmp(names[0], file.name), 0); |
| + TESTEQUAL(strcmp(names[1], id), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + free(file_name); |
| + close(notify_fd); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + free(index_dir); |
| + free(incomplete_dir); |
| + return result; |
| +} |
| + |
| +static EVP_PKEY *create_key(void) |
| +{ |
| + EVP_PKEY *pkey = NULL; |
| + RSA *rsa = NULL; |
| + BIGNUM *bn = NULL; |
| + |
| + pkey = EVP_PKEY_new(); |
| + if (!pkey) |
| + goto fail; |
| + |
| + bn = BN_new(); |
| + BN_set_word(bn, RSA_F4); |
| + |
| + rsa = RSA_new(); |
| + if (!rsa) |
| + goto fail; |
| + |
| + RSA_generate_key_ex(rsa, 4096, bn, NULL); |
| + EVP_PKEY_assign_RSA(pkey, rsa); |
| + |
| + BN_free(bn); |
| + return pkey; |
| + |
| +fail: |
| + BN_free(bn); |
| + EVP_PKEY_free(pkey); |
| + return NULL; |
| +} |
| + |
| +static X509 *get_cert(EVP_PKEY *key) |
| +{ |
| + X509 *x509 = NULL; |
| + X509_NAME *name = NULL; |
| + |
| + x509 = X509_new(); |
| + if (!x509) |
| + return NULL; |
| + |
| + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); |
| + X509_gmtime_adj(X509_get_notBefore(x509), 0); |
| + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); |
| + X509_set_pubkey(x509, key); |
| + |
| + name = X509_get_subject_name(x509); |
| + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, |
| + (const unsigned char *)"US", -1, -1, 0); |
| + X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, |
| + (const unsigned char *)"CA", -1, -1, 0); |
| + X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, |
| + (const unsigned char *)"San Jose", -1, -1, 0); |
| + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, |
| + (const unsigned char *)"Example", -1, -1, 0); |
| + X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, |
| + (const unsigned char *)"Org", -1, -1, 0); |
| + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, |
| + (const unsigned char *)"www.example.com", -1, -1, 0); |
| + X509_set_issuer_name(x509, name); |
| + |
| + if (!X509_sign(x509, key, EVP_sha256())) |
| + return NULL; |
| + |
| + return x509; |
| +} |
| + |
| +static int sign(EVP_PKEY *key, X509 *cert, const char *data, size_t len, |
| + unsigned char **sig, size_t *sig_len) |
| +{ |
| + const int pkcs7_flags = PKCS7_BINARY | PKCS7_NOATTR | PKCS7_PARTIAL | |
| + PKCS7_DETACHED; |
| + const EVP_MD *md = EVP_sha256(); |
| + |
| + int result = TEST_FAILURE; |
| + |
| + BIO *bio = NULL; |
| + PKCS7 *p7 = NULL; |
| + unsigned char *bio_buffer; |
| + |
| + TEST(bio = BIO_new_mem_buf(data, len), bio); |
| + TEST(p7 = PKCS7_sign(NULL, NULL, NULL, bio, pkcs7_flags), p7); |
| + TESTNE(PKCS7_sign_add_signer(p7, cert, key, md, pkcs7_flags), 0); |
| + TESTEQUAL(PKCS7_final(p7, bio, pkcs7_flags), 1); |
| + TEST(*sig_len = i2d_PKCS7(p7, NULL), *sig_len); |
| + TEST(bio_buffer = malloc(*sig_len), bio_buffer); |
| + *sig = bio_buffer; |
| + TEST(*sig_len = i2d_PKCS7(p7, &bio_buffer), *sig_len); |
| + TESTEQUAL(PKCS7_verify(p7, NULL, NULL, bio, NULL, |
| + pkcs7_flags | PKCS7_NOVERIFY | PKCS7_NOSIGS), 1); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + PKCS7_free(p7); |
| + BIO_free(bio); |
| + return result; |
| +} |
| + |
| +static int verity_installed(const char *mount_dir, int cmd_fd, bool *installed) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + int fd = -1; |
| + struct test_file *file = &get_test_files_set().files[0]; |
| + |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, file->size, |
| + NULL), 0); |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, NULL), -1); |
| + *installed = errno != EOPNOTSUPP; |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + if (filename) |
| + remove(filename); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int enable_verity(const char *mount_dir, struct test_file *file, |
| + EVP_PKEY *key, X509 *cert, bool use_signatures) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + int fd = -1; |
| + struct fsverity_enable_arg fear = { |
| + .version = 1, |
| + .hash_algorithm = FS_VERITY_HASH_ALG_SHA256, |
| + .block_size = INCFS_DATA_FILE_BLOCK_SIZE, |
| + .sig_size = 0, |
| + .sig_ptr = 0, |
| + }; |
| + struct { |
| + __u8 version; /* must be 1 */ |
| + __u8 hash_algorithm; /* Merkle tree hash algorithm */ |
| + __u8 log_blocksize; /* log2 of size of data and tree blocks */ |
| + __u8 salt_size; /* size of salt in bytes; 0 if none */ |
| + __le32 sig_size; /* must be 0 */ |
| + __le64 data_size; /* size of file the Merkle tree is built over */ |
| + __u8 root_hash[64]; /* Merkle tree root hash */ |
| + __u8 salt[32]; /* salt prepended to each hashed block */ |
| + __u8 __reserved[144]; /* must be 0's */ |
| + } __packed fsverity_descriptor = { |
| + .version = 1, |
| + .hash_algorithm = 1, |
| + .log_blocksize = 12, |
| + .data_size = file->size, |
| + }; |
| + struct { |
| + char magic[8]; /* must be "FSVerity" */ |
| + __le16 digest_algorithm; |
| + __le16 digest_size; |
| + __u8 digest[32]; |
| + } __packed fsverity_signed_digest = { |
| + .digest_algorithm = 1, |
| + .digest_size = 32 |
| + }; |
| + unsigned char *sig = NULL; |
| + size_t sig_size = 0; |
| + uint64_t flags; |
| + struct statx statxbuf = {}; |
| + |
| + memcpy(fsverity_signed_digest.magic, "FSVerity", 8); |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TESTEQUAL(syscall(__NR_statx, AT_FDCWD, filename, 0, STATX_ALL, |
| + &statxbuf), 0); |
| + TESTEQUAL(statxbuf.stx_attributes_mask & STATX_ATTR_VERITY, |
| + STATX_ATTR_VERITY); |
| + TESTEQUAL(statxbuf.stx_attributes & STATX_ATTR_VERITY, 0); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl(fd, FS_IOC_GETFLAGS, &flags), 0); |
| + TESTEQUAL(flags & FS_VERITY_FL, 0); |
| + |
| + /* First try to enable verity with random digest */ |
| + if (key) { |
| + TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest, |
| + sizeof(fsverity_signed_digest), &sig, &sig_size), |
| + 0); |
| + |
| + fear.sig_size = sig_size; |
| + fear.sig_ptr = ptr_to_u64(sig); |
| + TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), -1); |
| + } |
| + |
| + /* Now try with correct digest */ |
| + memcpy(fsverity_descriptor.root_hash, file->root_hash, 32); |
| + sha256((char *)&fsverity_descriptor, sizeof(fsverity_descriptor), |
| + (char *)fsverity_signed_digest.digest); |
| + |
| + if (ioctl(fd, FS_IOC_ENABLE_VERITY, NULL) == -1 && |
| + errno == EOPNOTSUPP) { |
| + result = TEST_SUCCESS; |
| + goto out; |
| + } |
| + |
| + free(sig); |
| + sig = NULL; |
| + |
| + if (key) |
| + TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest, |
| + sizeof(fsverity_signed_digest), |
| + &sig, &sig_size), |
| + 0); |
| + |
| + if (use_signatures) { |
| + fear.sig_size = sig_size; |
| + file->verity_sig_size = sig_size; |
| + fear.sig_ptr = ptr_to_u64(sig); |
| + file->verity_sig = sig; |
| + sig = NULL; |
| + } else { |
| + fear.sig_size = 0; |
| + fear.sig_ptr = 0; |
| + } |
| + TESTEQUAL(ioctl(fd, FS_IOC_ENABLE_VERITY, &fear), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + free(sig); |
| + close(fd); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int memzero(const unsigned char *buf, size_t size) |
| +{ |
| + size_t i; |
| + |
| + for (i = 0; i < size; ++i) |
| + if (buf[i]) |
| + return -1; |
| + return 0; |
| +} |
| + |
| +static int validate_verity(const char *mount_dir, struct test_file *file) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = concat_file_name(mount_dir, file->name); |
| + int fd = -1; |
| + uint64_t flags; |
| + struct fsverity_digest *digest; |
| + struct statx statxbuf = {}; |
| + struct fsverity_read_metadata_arg frma = {}; |
| + uint8_t *buf = NULL; |
| + struct fsverity_descriptor desc; |
| + |
| + TEST(digest = malloc(sizeof(struct fsverity_digest) + |
| + INCFS_MAX_HASH_SIZE), digest != NULL); |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TESTEQUAL(syscall(__NR_statx, AT_FDCWD, filename, 0, STATX_ALL, |
| + &statxbuf), 0); |
| + TESTEQUAL(statxbuf.stx_attributes & STATX_ATTR_VERITY, |
| + STATX_ATTR_VERITY); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl(fd, FS_IOC_GETFLAGS, &flags), 0); |
| + TESTEQUAL(flags & FS_VERITY_FL, FS_VERITY_FL); |
| + digest->digest_size = INCFS_MAX_HASH_SIZE; |
| + TESTEQUAL(ioctl(fd, FS_IOC_MEASURE_VERITY, digest), 0); |
| + TESTEQUAL(digest->digest_algorithm, FS_VERITY_HASH_ALG_SHA256); |
| + TESTEQUAL(digest->digest_size, 32); |
| + |
| + if (file->verity_sig) { |
| + TEST(buf = malloc(file->verity_sig_size), buf); |
| + frma = (struct fsverity_read_metadata_arg) { |
| + .metadata_type = FS_VERITY_METADATA_TYPE_SIGNATURE, |
| + .length = file->verity_sig_size, |
| + .buf_ptr = ptr_to_u64(buf), |
| + }; |
| + TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma), |
| + file->verity_sig_size); |
| + TESTEQUAL(memcmp(buf, file->verity_sig, file->verity_sig_size), |
| + 0); |
| + } else { |
| + frma = (struct fsverity_read_metadata_arg) { |
| + .metadata_type = FS_VERITY_METADATA_TYPE_SIGNATURE, |
| + }; |
| + TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma), -1); |
| + TESTEQUAL(errno, ENODATA); |
| + } |
| + |
| + frma = (struct fsverity_read_metadata_arg) { |
| + .metadata_type = FS_VERITY_METADATA_TYPE_DESCRIPTOR, |
| + .length = sizeof(desc), |
| + .buf_ptr = ptr_to_u64(&desc), |
| + }; |
| + TESTEQUAL(ioctl(fd, FS_IOC_READ_VERITY_METADATA, &frma), |
| + sizeof(desc)); |
| + TESTEQUAL(desc.version, 1); |
| + TESTEQUAL(desc.hash_algorithm, FS_VERITY_HASH_ALG_SHA256); |
| + TESTEQUAL(desc.log_blocksize, ilog2(INCFS_DATA_FILE_BLOCK_SIZE)); |
| + TESTEQUAL(desc.salt_size, 0); |
| + TESTEQUAL(desc.__reserved_0x04, 0); |
| + TESTEQUAL(desc.data_size, file->size); |
| + TESTEQUAL(memcmp(desc.root_hash, file->root_hash, SHA256_DIGEST_SIZE), |
| + 0); |
| + TESTEQUAL(memzero(desc.root_hash + SHA256_DIGEST_SIZE, |
| + sizeof(desc.root_hash) - SHA256_DIGEST_SIZE), 0); |
| + TESTEQUAL(memzero(desc.salt, sizeof(desc.salt)), 0); |
| + TESTEQUAL(memzero(desc.__reserved, sizeof(desc.__reserved)), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + free(buf); |
| + close(fd); |
| + free(filename); |
| + free(digest); |
| + return result; |
| +} |
| + |
| +static int verity_test_optional_sigs(const char *mount_dir, bool use_signatures) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + bool installed; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + EVP_PKEY *key = NULL; |
| + X509 *cert = NULL; |
| + BIO *mem = NULL; |
| + long len; |
| + void *ptr; |
| + FILE *proc_key_fd = NULL; |
| + char *line = NULL; |
| + size_t read = 0; |
| + int key_id = -1; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false), |
| + 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(verity_installed(mount_dir, cmd_fd, &installed), 0); |
| + if (!installed) { |
| + result = TEST_SUCCESS; |
| + goto out; |
| + } |
| + TEST(key = create_key(), key); |
| + TEST(cert = get_cert(key), cert); |
| + |
| + TEST(proc_key_fd = fopen("/proc/keys", "r"), proc_key_fd != NULL); |
| + while (getline(&line, &read, proc_key_fd) != -1) |
| + if (strstr(line, ".fs-verity")) |
| + key_id = strtol(line, NULL, 16); |
| + |
| + TEST(mem = BIO_new(BIO_s_mem()), mem != NULL); |
| + TESTEQUAL(i2d_X509_bio(mem, cert), 1); |
| + TEST(len = BIO_get_mem_data(mem, &ptr), len != 0); |
| + TESTCOND(key_id == -1 |
| + || syscall(__NR_add_key, "asymmetric", "test:key", ptr, len, |
| + key_id) != -1); |
| + |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + build_mtree(file); |
| + TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, file->root_hash, |
| + file->sig.add_data), 0); |
| + |
| + TESTEQUAL(load_hash_tree(mount_dir, file), 0); |
| + TESTEQUAL(enable_verity(mount_dir, file, key, cert, |
| + use_signatures), |
| + 0); |
| + } |
| + |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0); |
| + |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + TESTEQUAL(umount(mount_dir), 0); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "readahead=0", false), |
| + 0); |
| + |
| + for (i = 0; i < file_num; i++) |
| + TESTEQUAL(validate_verity(mount_dir, &test.files[i]), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + free(file->mtree); |
| + free(file->verity_sig); |
| + |
| + file->mtree = NULL; |
| + file->verity_sig = NULL; |
| + } |
| + |
| + free(line); |
| + BIO_free(mem); |
| + X509_free(cert); |
| + EVP_PKEY_free(key); |
| + fclose(proc_key_fd); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int verity_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + |
| + TESTEQUAL(verity_test_optional_sigs(mount_dir, true), TEST_SUCCESS); |
| + TESTEQUAL(verity_test_optional_sigs(mount_dir, false), TEST_SUCCESS); |
| + result = TEST_SUCCESS; |
| +out: |
| + return result; |
| +} |
| + |
| +static int verity_file_valid(const char *mount_dir, struct test_file *file) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + int fd = -1; |
| + uint8_t buffer[INCFS_DATA_FILE_BLOCK_SIZE]; |
| + struct incfs_get_file_sig_args gfsa = { |
| + .file_signature = ptr_to_u64(buffer), |
| + .file_signature_buf_size = sizeof(buffer), |
| + }; |
| + int i; |
| + |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl(fd, INCFS_IOC_READ_FILE_SIGNATURE, &gfsa), 0); |
| + for (i = 0; i < file->size; i += sizeof(buffer)) |
| + TESTEQUAL(pread(fd, buffer, sizeof(buffer), i), |
| + file->size - i > sizeof(buffer) ? |
| + sizeof(buffer) : file->size - i); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int enable_verity_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + bool installed; |
| + int cmd_fd = -1; |
| + struct test_files_set test = get_test_files_set(); |
| + int i; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(verity_installed(mount_dir, cmd_fd, &installed), 0); |
| + if (!installed) { |
| + result = TEST_SUCCESS; |
| + goto out; |
| + } |
| + for (i = 0; i < test.files_count; ++i) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL), 0); |
| + TESTEQUAL(emit_test_file_data(mount_dir, file), 0); |
| + TESTEQUAL(enable_verity(mount_dir, file, NULL, NULL, false), 0); |
| + } |
| + |
| + /* Check files are valid on disk */ |
| + close(cmd_fd); |
| + cmd_fd = -1; |
| + TESTEQUAL(umount(mount_dir), 0); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + for (i = 0; i < test.files_count; ++i) |
| + TESTEQUAL(verity_file_valid(mount_dir, &test.files[i]), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int mmap_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + int cmd_fd = -1; |
| + /* |
| + * File is big enough to have a two layer tree with two hashes in the |
| + * higher level, so we can corrupt the second one |
| + */ |
| + int shas_per_block = INCFS_DATA_FILE_BLOCK_SIZE / SHA256_DIGEST_SIZE; |
| + struct test_file file = { |
| + .name = "file", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE * shas_per_block * 2, |
| + }; |
| + char *filename = NULL; |
| + int fd = -1; |
| + char *addr = (void *)-1; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + |
| + TESTEQUAL(build_mtree(&file), 0); |
| + file.mtree[1].data[INCFS_DATA_FILE_BLOCK_SIZE] ^= 0xff; |
| + TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file.name, &file.id, |
| + file.size, file.root_hash, |
| + file.sig.add_data), 0); |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTEQUAL(load_hash_tree(mount_dir, &file), 0); |
| + TEST(filename = concat_file_name(mount_dir, file.name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TEST(addr = mmap(NULL, file.size, PROT_READ, MAP_PRIVATE, fd, 0), |
| + addr != (void *) -1); |
| + TESTEQUAL(mlock(addr, INCFS_DATA_FILE_BLOCK_SIZE), 0); |
| + TESTEQUAL(munlock(addr, INCFS_DATA_FILE_BLOCK_SIZE), 0); |
| + TESTEQUAL(mlock(addr + shas_per_block * INCFS_DATA_FILE_BLOCK_SIZE, |
| + INCFS_DATA_FILE_BLOCK_SIZE), -1); |
| + TESTEQUAL(mlock(addr + (shas_per_block - 1) * |
| + INCFS_DATA_FILE_BLOCK_SIZE, |
| + INCFS_DATA_FILE_BLOCK_SIZE), 0); |
| + TESTEQUAL(munlock(addr + (shas_per_block - 1) * |
| + INCFS_DATA_FILE_BLOCK_SIZE, |
| + INCFS_DATA_FILE_BLOCK_SIZE), 0); |
| + TESTEQUAL(mlock(addr + (shas_per_block - 1) * |
| + INCFS_DATA_FILE_BLOCK_SIZE, |
| + INCFS_DATA_FILE_BLOCK_SIZE * 2), -1); |
| + TESTEQUAL(munmap(addr, file.size), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + free(file.mtree); |
| + close(fd); |
| + free(filename); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int truncate_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + int cmd_fd = -1; |
| + struct test_file file = { |
| + .name = "file", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE, |
| + }; |
| + char *backing_file = NULL; |
| + int fd = -1; |
| + struct stat st; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL), |
| + 0); |
| + TEST(backing_file = concat_file_name(backing_dir, file.name), |
| + backing_file); |
| + TEST(fd = open(backing_file, O_RDWR | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(stat(backing_file, &st), 0); |
| + TESTCOND(st.st_blocks < 128); |
| + TESTEQUAL(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1 << 24), 0); |
| + TESTEQUAL(stat(backing_file, &st), 0); |
| + TESTCOND(st.st_blocks > 32768); |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTEQUAL(stat(backing_file, &st), 0); |
| + TESTCOND(st.st_blocks < 128); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(fd); |
| + free(backing_file); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int stat_file_test(const char *mount_dir, int cmd_fd, |
| + struct test_file *file) |
| +{ |
| + int result = TEST_FAILURE; |
| + struct stat st; |
| + char *filename = NULL; |
| + |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, |
| + file->size, NULL), 0); |
| + TEST(filename = concat_file_name(mount_dir, file->name), filename); |
| + TESTEQUAL(stat(filename, &st), 0); |
| + TESTCOND(st.st_blocks < 32); |
| + TESTEQUAL(emit_test_file_data(mount_dir, file), 0); |
| + TESTEQUAL(stat(filename, &st), 0); |
| + TESTCOND(st.st_blocks > file->size / 512); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int stat_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + int cmd_fd = -1; |
| + int i; |
| + struct test_files_set test = get_test_files_set(); |
| + const int file_num = test.files_count; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + |
| + for (i = 0; i < file_num; i++) { |
| + struct test_file *file = &test.files[i]; |
| + |
| + TESTEQUAL(stat_file_test(mount_dir, cmd_fd, file), 0); |
| + } |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +#define SYSFS_DIR "/sys/fs/incremental-fs/instances/test_node/" |
| + |
| +static int sysfs_test_value(const char *name, uint64_t value) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + FILE *file = NULL; |
| + uint64_t res; |
| + |
| + TEST(filename = concat_file_name(SYSFS_DIR, name), filename); |
| + TEST(file = fopen(filename, "re"), file); |
| + TESTEQUAL(fscanf(file, "%lu", &res), 1); |
| + TESTEQUAL(res, value); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + if (file) |
| + fclose(file); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int sysfs_test_value_range(const char *name, uint64_t low, uint64_t high) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *filename = NULL; |
| + FILE *file = NULL; |
| + uint64_t res; |
| + |
| + TEST(filename = concat_file_name(SYSFS_DIR, name), filename); |
| + TEST(file = fopen(filename, "re"), file); |
| + TESTEQUAL(fscanf(file, "%lu", &res), 1); |
| + TESTCOND(res >= low && res <= high); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + if (file) |
| + fclose(file); |
| + free(filename); |
| + return result; |
| +} |
| + |
| +static int ioctl_test_last_error(int cmd_fd, const incfs_uuid_t *file_id, |
| + int page, int error) |
| +{ |
| + int result = TEST_FAILURE; |
| + struct incfs_get_last_read_error_args glre; |
| + |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_GET_LAST_READ_ERROR, &glre), 0); |
| + if (file_id) |
| + TESTEQUAL(memcmp(&glre.file_id_out, file_id, sizeof(*file_id)), |
| + 0); |
| + |
| + TESTEQUAL(glre.page_out, page); |
| + TESTEQUAL(glre.errno_out, error); |
| + result = TEST_SUCCESS; |
| +out: |
| + return result; |
| +} |
| + |
| +static int sysfs_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + int cmd_fd = -1; |
| + struct test_file file = { |
| + .name = "file", |
| + .size = INCFS_DATA_FILE_BLOCK_SIZE, |
| + }; |
| + char *filename = NULL; |
| + int fd = -1; |
| + int pid = -1; |
| + char buffer[32]; |
| + char *null_buf = NULL; |
| + int status; |
| + struct incfs_per_uid_read_timeouts purt_set[] = { |
| + { |
| + .uid = 0, |
| + .min_time_us = 1000000, |
| + .min_pending_time_us = 1000000, |
| + .max_pending_time_us = 2000000, |
| + }, |
| + }; |
| + struct incfs_set_read_timeouts_args srt = { |
| + ptr_to_u64(purt_set), |
| + sizeof(purt_set) |
| + }; |
| + |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "sysfs_name=test_node", |
| + false), |
| + 0); |
| + TEST(cmd_fd = open_commands_file(mount_dir), cmd_fd != -1); |
| + TESTEQUAL(build_mtree(&file), 0); |
| + file.root_hash[0] ^= 0xff; |
| + TESTEQUAL(crypto_emit_file(cmd_fd, NULL, file.name, &file.id, file.size, |
| + file.root_hash, file.sig.add_data), |
| + 0); |
| + TEST(filename = concat_file_name(mount_dir, file.name), filename); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(ioctl_test_last_error(cmd_fd, NULL, 0, 0), 0); |
| + TESTEQUAL(sysfs_test_value("reads_failed_timed_out", 0), 0); |
| + TESTEQUAL(read(fd, null_buf, 1), -1); |
| + TESTEQUAL(ioctl_test_last_error(cmd_fd, &file.id, 0, -ETIME), 0); |
| + TESTEQUAL(sysfs_test_value("reads_failed_timed_out", 2), 0); |
| + |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTEQUAL(sysfs_test_value("reads_failed_hash_verification", 0), 0); |
| + TESTEQUAL(read(fd, null_buf, 1), -1); |
| + TESTEQUAL(sysfs_test_value("reads_failed_hash_verification", 1), 0); |
| + TESTSYSCALL(close(fd)); |
| + fd = -1; |
| + |
| + TESTSYSCALL(unlink(filename)); |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, |
| + "read_timeout_ms=10000,sysfs_name=test_node", |
| + true), |
| + 0); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL), |
| + 0); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTSYSCALL(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)); |
| + TEST(pid = fork(), pid != -1); |
| + if (pid == 0) { |
| + TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer)); |
| + exit(0); |
| + } |
| + sleep(1); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_pending", 0), 0); |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTNE(wait(&status), -1); |
| + TESTEQUAL(status, 0); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_pending", 1), 0); |
| + /* Allow +/- 10% */ |
| + TESTEQUAL(sysfs_test_value_range("reads_delayed_pending_us", 900000, 1100000), |
| + 0); |
| + |
| + TESTSYSCALL(close(fd)); |
| + fd = -1; |
| + |
| + TESTSYSCALL(unlink(filename)); |
| + TESTEQUAL(ioctl(cmd_fd, INCFS_IOC_SET_READ_TIMEOUTS, &srt), 0); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL), |
| + 0); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_min", 0), 0); |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer)); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_min", 1), 0); |
| + /* This should be exact */ |
| + TESTEQUAL(sysfs_test_value("reads_delayed_min_us", 1000000), 0); |
| + |
| + TESTSYSCALL(close(fd)); |
| + fd = -1; |
| + |
| + TESTSYSCALL(unlink(filename)); |
| + TESTEQUAL(emit_file(cmd_fd, NULL, file.name, &file.id, file.size, NULL), |
| + 0); |
| + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTSYSCALL(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC)); |
| + TEST(pid = fork(), pid != -1); |
| + if (pid == 0) { |
| + TESTEQUAL(read(fd, buffer, sizeof(buffer)), sizeof(buffer)); |
| + exit(0); |
| + } |
| + usleep(500000); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_pending", 1), 0); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_min", 1), 0); |
| + TESTEQUAL(emit_test_file_data(mount_dir, &file), 0); |
| + TESTNE(wait(&status), -1); |
| + TESTEQUAL(status, 0); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_pending", 2), 0); |
| + TESTEQUAL(sysfs_test_value("reads_delayed_min", 2), 0); |
| + /* Exact 1000000 plus 500000 +/- 10% */ |
| + TESTEQUAL(sysfs_test_value_range("reads_delayed_min_us", 1450000, 1550000), 0); |
| + /* Allow +/- 10% */ |
| + TESTEQUAL(sysfs_test_value_range("reads_delayed_pending_us", 1350000, 1650000), |
| + 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + if (pid == 0) |
| + exit(result); |
| + free(file.mtree); |
| + free(filename); |
| + close(fd); |
| + close(cmd_fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int sysfs_test_directories(bool one_present, bool two_present) |
| +{ |
| + int result = TEST_FAILURE; |
| + struct stat st; |
| + |
| + TESTEQUAL(stat("/sys/fs/incremental-fs/instances/1", &st), |
| + one_present ? 0 : -1); |
| + if (one_present) |
| + TESTCOND(S_ISDIR(st.st_mode)); |
| + else |
| + TESTEQUAL(errno, ENOENT); |
| + TESTEQUAL(stat("/sys/fs/incremental-fs/instances/2", &st), |
| + two_present ? 0 : -1); |
| + if (two_present) |
| + TESTCOND(S_ISDIR(st.st_mode)); |
| + else |
| + TESTEQUAL(errno, ENOENT); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + return result; |
| +} |
| + |
| +static int sysfs_rename_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + char *mount_dir2 = NULL; |
| + int fd = -1; |
| + char c; |
| + |
| + /* Mount with no node */ |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + TESTEQUAL(sysfs_test_directories(false, false), 0); |
| + |
| + /* Remount with node */ |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "sysfs_name=1", true), |
| + 0); |
| + TESTEQUAL(sysfs_test_directories(true, false), 0); |
| + TEST(fd = open("/sys/fs/incremental-fs/instances/1/reads_delayed_min", |
| + O_RDONLY | O_CLOEXEC), fd != -1); |
| + TESTEQUAL(pread(fd, &c, 1, 0), 1); |
| + TESTEQUAL(c, '0'); |
| + TESTEQUAL(pread(fd, &c, 1, 0), 1); |
| + TESTEQUAL(c, '0'); |
| + |
| + /* Rename node */ |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "sysfs_name=2", true), |
| + 0); |
| + TESTEQUAL(sysfs_test_directories(false, true), 0); |
| + TESTEQUAL(pread(fd, &c, 1, 0), -1); |
| + |
| + /* Try mounting another instance with same node name */ |
| + TEST(mount_dir2 = concat_file_name(backing_dir, "incfs-mount-dir2"), |
| + mount_dir2); |
| + rmdir(mount_dir2); /* In case we crashed before */ |
| + TESTSYSCALL(mkdir(mount_dir2, 0777)); |
| + TEST(mount_fs_opt(mount_dir2, backing_dir, "sysfs_name=2", false), |
| + -1); |
| + |
| + /* Try mounting another instance then remounting with existing name */ |
| + TESTEQUAL(mount_fs(mount_dir2, backing_dir, 0), 0); |
| + TESTEQUAL(mount_fs_opt(mount_dir2, backing_dir, "sysfs_name=2", true), |
| + -1); |
| + |
| + /* Remount with no node */ |
| + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, "", true), |
| + 0); |
| + TESTEQUAL(sysfs_test_directories(false, false), 0); |
| + |
| + result = TEST_SUCCESS; |
| +out: |
| + umount(mount_dir2); |
| + rmdir(mount_dir2); |
| + free(mount_dir2); |
| + close(fd); |
| + umount(mount_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static int stacked_mount_test(const char *mount_dir) |
| +{ |
| + int result = TEST_FAILURE; |
| + char *backing_dir = NULL; |
| + |
| + /* Mount with no node */ |
| + TEST(backing_dir = create_backing_dir(mount_dir), backing_dir); |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + /* Try mounting another instance with same name */ |
| + TESTEQUAL(mount_fs(mount_dir, backing_dir, 0), 0); |
| + /* Try unmounting the first instance */ |
| + TESTEQUAL(umount_fs(mount_dir), 0); |
| + /* Try unmounting the second instance */ |
| + TESTEQUAL(umount_fs(mount_dir), 0); |
| + result = TEST_SUCCESS; |
| +out: |
| + /* Cleanup */ |
| + rmdir(mount_dir); |
| + rmdir(backing_dir); |
| + free(backing_dir); |
| + return result; |
| +} |
| + |
| +static char *setup_mount_dir() |
| +{ |
| + struct stat st; |
| + char *current_dir = getcwd(NULL, 0); |
| + char *mount_dir = concat_file_name(current_dir, "incfs-mount-dir"); |
| + |
| + free(current_dir); |
| + if (stat(mount_dir, &st) == 0) { |
| + if (S_ISDIR(st.st_mode)) |
| + return mount_dir; |
| + |
| + ksft_print_msg("%s is a file, not a dir.\n", mount_dir); |
| + return NULL; |
| + } |
| + |
| + if (mkdir(mount_dir, 0777)) { |
| + print_error("Can't create mount dir."); |
| + return NULL; |
| + } |
| + |
| + return mount_dir; |
| +} |
| + |
| +int parse_options(int argc, char *const *argv) |
| +{ |
| + signed char c; |
| + |
| + while ((c = getopt(argc, argv, "f:t:v")) != -1) |
| + switch (c) { |
| + case 'f': |
| + options.file = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 't': |
| + options.test = strtol(optarg, NULL, 10); |
| + break; |
| + |
| + case 'v': |
| + options.verbose = true; |
| + break; |
| + |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +struct test_case { |
| + int (*pfunc)(const char *dir); |
| + const char *name; |
| +}; |
| + |
| +void run_one_test(const char *mount_dir, struct test_case *test_case) |
| +{ |
| + int ret; |
| + |
| + ksft_print_msg("Running %s\n", test_case->name); |
| + ret = test_case->pfunc(mount_dir); |
| + |
| + if (ret == TEST_SUCCESS) |
| + ksft_test_result_pass("%s\n", test_case->name); |
| + else if (ret == TEST_SKIP) |
| + ksft_test_result_skip("%s\n", test_case->name); |
| + else |
| + ksft_test_result_fail("%s\n", test_case->name); |
| +} |
| + |
| +int main(int argc, char *argv[]) |
| +{ |
| + char *mount_dir = NULL; |
| + int i; |
| + int fd, count; |
| + |
| + if (parse_options(argc, argv)) |
| + ksft_exit_fail_msg("Bad options\n"); |
| + |
| + // Seed randomness pool for testing on QEMU |
| + // NOTE - this abuses the concept of randomness - do *not* ever do this |
| + // on a machine for production use - the device will think it has good |
| + // randomness when it does not. |
| + fd = open("/dev/urandom", O_WRONLY | O_CLOEXEC); |
| + count = 4096; |
| + for (int i = 0; i < 128; ++i) |
| + ioctl(fd, RNDADDTOENTCNT, &count); |
| + close(fd); |
| + |
| + ksft_print_header(); |
| + |
| + if (geteuid() != 0) |
| + ksft_print_msg("Not a root, might fail to mount.\n"); |
| + |
| + mount_dir = setup_mount_dir(); |
| + if (mount_dir == NULL) |
| + ksft_exit_fail_msg("Can't create a mount dir\n"); |
| + |
| +#define MAKE_TEST(test) \ |
| + { \ |
| + test, #test \ |
| + } |
| + struct test_case cases[] = { |
| + MAKE_TEST(basic_file_ops_test), |
| + MAKE_TEST(cant_touch_index_test), |
| + MAKE_TEST(dynamic_files_and_data_test), |
| + MAKE_TEST(concurrent_reads_and_writes_test), |
| + MAKE_TEST(attribute_test), |
| + MAKE_TEST(work_after_remount_test), |
| + MAKE_TEST(child_procs_waiting_for_data_test), |
| + MAKE_TEST(multiple_providers_test), |
| + MAKE_TEST(hash_tree_test), |
| + MAKE_TEST(read_log_test), |
| + MAKE_TEST(get_blocks_test), |
| + MAKE_TEST(get_hash_blocks_test), |
| + MAKE_TEST(large_file_test), |
| + MAKE_TEST(mapped_file_test), |
| + MAKE_TEST(compatibility_test), |
| + MAKE_TEST(data_block_count_test), |
| + MAKE_TEST(hash_block_count_test), |
| + MAKE_TEST(per_uid_read_timeouts_test), |
| + MAKE_TEST(inotify_test), |
| + MAKE_TEST(verity_test), |
| + MAKE_TEST(enable_verity_test), |
| + MAKE_TEST(mmap_test), |
| + MAKE_TEST(truncate_test), |
| + MAKE_TEST(stat_test), |
| + MAKE_TEST(sysfs_test), |
| + MAKE_TEST(sysfs_rename_test), |
| + MAKE_TEST(stacked_mount_test), |
| + }; |
| +#undef MAKE_TEST |
| + |
| + if (options.test) { |
| + if (options.test <= 0 || options.test > ARRAY_SIZE(cases)) |
| + ksft_exit_fail_msg("Invalid test\n"); |
| + |
| + ksft_set_plan(1); |
| + run_one_test(mount_dir, &cases[options.test - 1]); |
| + } else { |
| + ksft_set_plan(ARRAY_SIZE(cases)); |
| + for (i = 0; i < ARRAY_SIZE(cases); ++i) |
| + run_one_test(mount_dir, &cases[i]); |
| + } |
| + |
| + umount2(mount_dir, MNT_FORCE); |
| + rmdir(mount_dir); |
| + return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); |
| +} |
| diff --git a/tools/testing/selftests/filesystems/incfs/utils.c b/tools/testing/selftests/filesystems/incfs/utils.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/utils.c |
| @@ -0,0 +1,391 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +/* |
| + * Copyright 2018 Google LLC |
| + */ |
| +#include <dirent.h> |
| +#include <errno.h> |
| +#include <fcntl.h> |
| +#include <poll.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| +#include <unistd.h> |
| + |
| +#include <sys/ioctl.h> |
| +#include <sys/mount.h> |
| +#include <sys/stat.h> |
| +#include <sys/types.h> |
| + |
| +#include <openssl/sha.h> |
| +#include <openssl/md5.h> |
| + |
| +#include "utils.h" |
| + |
| +#ifndef __S_IFREG |
| +#define __S_IFREG S_IFREG |
| +#endif |
| + |
| +unsigned int rnd(unsigned int max, unsigned int *seed) |
| +{ |
| + return rand_r(seed) * ((uint64_t)max + 1) / RAND_MAX; |
| +} |
| + |
| +int remove_dir(const char *dir) |
| +{ |
| + int err = rmdir(dir); |
| + |
| + if (err && errno == ENOTEMPTY) { |
| + err = delete_dir_tree(dir); |
| + if (err) |
| + return err; |
| + return 0; |
| + } |
| + |
| + if (err && errno != ENOENT) |
| + return -errno; |
| + |
| + return 0; |
| +} |
| + |
| +int drop_caches(void) |
| +{ |
| + int drop_caches = |
| + open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); |
| + int i; |
| + |
| + if (drop_caches == -1) |
| + return -errno; |
| + i = write(drop_caches, "3", 1); |
| + close(drop_caches); |
| + |
| + if (i != 1) |
| + return -errno; |
| + |
| + return 0; |
| +} |
| + |
| +int mount_fs(const char *mount_dir, const char *backing_dir, |
| + int read_timeout_ms) |
| +{ |
| + static const char fs_name[] = INCFS_NAME; |
| + char mount_options[512]; |
| + int result; |
| + |
| + snprintf(mount_options, ARRAY_SIZE(mount_options), |
| + "read_timeout_ms=%u", |
| + read_timeout_ms); |
| + |
| + result = mount(backing_dir, mount_dir, fs_name, 0, mount_options); |
| + if (result != 0) |
| + perror("Error mounting fs."); |
| + return result; |
| +} |
| + |
| +int umount_fs(const char *mount_dir) |
| +{ |
| + int result; |
| + |
| + result = umount(mount_dir); |
| + if (result != 0) |
| + perror("Error unmounting fs."); |
| + return result; |
| +} |
| + |
| +int mount_fs_opt(const char *mount_dir, const char *backing_dir, |
| + const char *opt, bool remount) |
| +{ |
| + static const char fs_name[] = INCFS_NAME; |
| + int result; |
| + |
| + result = mount(backing_dir, mount_dir, fs_name, |
| + remount ? MS_REMOUNT : 0, opt); |
| + if (result != 0) |
| + perror("Error mounting fs."); |
| + return result; |
| +} |
| + |
| +struct hash_section { |
| + uint32_t algorithm; |
| + uint8_t log2_blocksize; |
| + uint32_t salt_size; |
| + /* no salt */ |
| + uint32_t hash_size; |
| + uint8_t hash[SHA256_DIGEST_SIZE]; |
| +} __packed; |
| + |
| +struct signature_blob { |
| + uint32_t version; |
| + uint32_t hash_section_size; |
| + struct hash_section hash_section; |
| + uint32_t signing_section_size; |
| + uint8_t signing_section[]; |
| +} __packed; |
| + |
| +size_t format_signature(void **buf, const char *root_hash, const char *add_data) |
| +{ |
| + size_t size = sizeof(struct signature_blob) + strlen(add_data) + 1; |
| + struct signature_blob *sb = malloc(size); |
| + |
| + if (!sb) |
| + return 0; |
| + |
| + *sb = (struct signature_blob){ |
| + .version = INCFS_SIGNATURE_VERSION, |
| + .hash_section_size = sizeof(struct hash_section), |
| + .hash_section = |
| + (struct hash_section){ |
| + .algorithm = INCFS_HASH_TREE_SHA256, |
| + .log2_blocksize = 12, |
| + .salt_size = 0, |
| + .hash_size = SHA256_DIGEST_SIZE, |
| + }, |
| + .signing_section_size = strlen(add_data) + 1, |
| + }; |
| + |
| + memcpy(sb->hash_section.hash, root_hash, SHA256_DIGEST_SIZE); |
| + memcpy((char *)sb->signing_section, add_data, strlen(add_data) + 1); |
| + *buf = sb; |
| + return size; |
| +} |
| + |
| +int crypto_emit_file(int fd, const char *dir, const char *filename, |
| + incfs_uuid_t *id_out, size_t size, const char *root_hash, |
| + const char *add_data) |
| +{ |
| + int mode = __S_IFREG | 0555; |
| + void *signature; |
| + int error = 0; |
| + |
| + struct incfs_new_file_args args = { |
| + .size = size, |
| + .mode = mode, |
| + .file_name = ptr_to_u64(filename), |
| + .directory_path = ptr_to_u64(dir), |
| + .file_attr = 0, |
| + .file_attr_len = 0 |
| + }; |
| + |
| + args.signature_size = format_signature(&signature, root_hash, add_data); |
| + args.signature_info = ptr_to_u64(signature); |
| + |
| + md5(filename, strlen(filename), (char *)args.file_id.bytes); |
| + |
| + if (ioctl(fd, INCFS_IOC_CREATE_FILE, &args) != 0) { |
| + error = -errno; |
| + goto out; |
| + } |
| + |
| + *id_out = args.file_id; |
| + |
| +out: |
| + free(signature); |
| + return error; |
| +} |
| + |
| +int emit_file(int fd, const char *dir, const char *filename, |
| + incfs_uuid_t *id_out, size_t size, const char *attr) |
| +{ |
| + int mode = __S_IFREG | 0555; |
| + struct incfs_new_file_args args = { .size = size, |
| + .mode = mode, |
| + .file_name = ptr_to_u64(filename), |
| + .directory_path = ptr_to_u64(dir), |
| + .signature_info = ptr_to_u64(NULL), |
| + .signature_size = 0, |
| + .file_attr = ptr_to_u64(attr), |
| + .file_attr_len = |
| + attr ? strlen(attr) : 0 }; |
| + |
| + md5(filename, strlen(filename), (char *)args.file_id.bytes); |
| + |
| + if (ioctl(fd, INCFS_IOC_CREATE_FILE, &args) != 0) |
| + return -errno; |
| + |
| + *id_out = args.file_id; |
| + return 0; |
| +} |
| + |
| +int get_file_bmap(int cmd_fd, int ino, unsigned char *buf, int buf_size) |
| +{ |
| + return 0; |
| +} |
| + |
| +int get_file_signature(int fd, unsigned char *buf, int buf_size) |
| +{ |
| + struct incfs_get_file_sig_args args = { |
| + .file_signature = ptr_to_u64(buf), |
| + .file_signature_buf_size = buf_size |
| + }; |
| + |
| + if (ioctl(fd, INCFS_IOC_READ_FILE_SIGNATURE, &args) == 0) |
| + return args.file_signature_len_out; |
| + return -errno; |
| +} |
| + |
| +loff_t get_file_size(const char *name) |
| +{ |
| + struct stat st; |
| + |
| + if (stat(name, &st) == 0) |
| + return st.st_size; |
| + return -ENOENT; |
| +} |
| + |
| +int open_commands_file(const char *mount_dir) |
| +{ |
| + char cmd_file[255]; |
| + int cmd_fd; |
| + |
| + snprintf(cmd_file, ARRAY_SIZE(cmd_file), |
| + "%s/%s", mount_dir, INCFS_PENDING_READS_FILENAME); |
| + cmd_fd = open(cmd_file, O_RDONLY | O_CLOEXEC); |
| + |
| + if (cmd_fd < 0) |
| + perror("Can't open commands file"); |
| + return cmd_fd; |
| +} |
| + |
| +int open_log_file(const char *mount_dir) |
| +{ |
| + char file[255]; |
| + int fd; |
| + |
| + snprintf(file, ARRAY_SIZE(file), "%s/.log", mount_dir); |
| + fd = open(file, O_RDWR | O_CLOEXEC); |
| + if (fd < 0) |
| + perror("Can't open log file"); |
| + return fd; |
| +} |
| + |
| +int open_blocks_written_file(const char *mount_dir) |
| +{ |
| + char file[255]; |
| + int fd; |
| + |
| + snprintf(file, ARRAY_SIZE(file), |
| + "%s/%s", mount_dir, INCFS_BLOCKS_WRITTEN_FILENAME); |
| + fd = open(file, O_RDONLY | O_CLOEXEC); |
| + |
| + if (fd < 0) |
| + perror("Can't open blocks_written file"); |
| + return fd; |
| +} |
| + |
| +int wait_for_pending_reads(int fd, int timeout_ms, |
| + struct incfs_pending_read_info *prs, int prs_count) |
| +{ |
| + ssize_t read_res = 0; |
| + |
| + if (timeout_ms > 0) { |
| + int poll_res = 0; |
| + struct pollfd pollfd = { |
| + .fd = fd, |
| + .events = POLLIN |
| + }; |
| + |
| + poll_res = poll(&pollfd, 1, timeout_ms); |
| + if (poll_res < 0) |
| + return -errno; |
| + if (poll_res == 0) |
| + return 0; |
| + if (!(pollfd.revents | POLLIN)) |
| + return 0; |
| + } |
| + |
| + read_res = read(fd, prs, prs_count * sizeof(*prs)); |
| + if (read_res < 0) |
| + return -errno; |
| + |
| + return read_res / sizeof(*prs); |
| +} |
| + |
| +int wait_for_pending_reads2(int fd, int timeout_ms, |
| + struct incfs_pending_read_info2 *prs, int prs_count) |
| +{ |
| + ssize_t read_res = 0; |
| + |
| + if (timeout_ms > 0) { |
| + int poll_res = 0; |
| + struct pollfd pollfd = { |
| + .fd = fd, |
| + .events = POLLIN |
| + }; |
| + |
| + poll_res = poll(&pollfd, 1, timeout_ms); |
| + if (poll_res < 0) |
| + return -errno; |
| + if (poll_res == 0) |
| + return 0; |
| + if (!(pollfd.revents | POLLIN)) |
| + return 0; |
| + } |
| + |
| + read_res = read(fd, prs, prs_count * sizeof(*prs)); |
| + if (read_res < 0) |
| + return -errno; |
| + |
| + return read_res / sizeof(*prs); |
| +} |
| + |
| +char *concat_file_name(const char *dir, const char *file) |
| +{ |
| + char full_name[FILENAME_MAX] = ""; |
| + |
| + if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0) |
| + return NULL; |
| + return strdup(full_name); |
| +} |
| + |
| +int delete_dir_tree(const char *dir_path) |
| +{ |
| + DIR *dir = NULL; |
| + struct dirent *dp; |
| + int result = 0; |
| + |
| + dir = opendir(dir_path); |
| + if (!dir) { |
| + result = -errno; |
| + goto out; |
| + } |
| + |
| + while ((dp = readdir(dir))) { |
| + char *full_path; |
| + |
| + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) |
| + continue; |
| + |
| + full_path = concat_file_name(dir_path, dp->d_name); |
| + if (dp->d_type == DT_DIR) |
| + result = delete_dir_tree(full_path); |
| + else |
| + result = unlink(full_path); |
| + free(full_path); |
| + if (result) |
| + goto out; |
| + } |
| + |
| +out: |
| + if (dir) |
| + closedir(dir); |
| + if (!result) |
| + rmdir(dir_path); |
| + return result; |
| +} |
| + |
| +void sha256(const char *data, size_t dsize, char *hash) |
| +{ |
| + SHA256_CTX ctx; |
| + |
| + SHA256_Init(&ctx); |
| + SHA256_Update(&ctx, data, dsize); |
| + SHA256_Final((unsigned char *)hash, &ctx); |
| +} |
| + |
| +void md5(const char *data, size_t dsize, char *hash) |
| +{ |
| + MD5_CTX ctx; |
| + |
| + MD5_Init(&ctx); |
| + MD5_Update(&ctx, data, dsize); |
| + MD5_Final((unsigned char *)hash, &ctx); |
| +} |
| diff --git a/tools/testing/selftests/filesystems/incfs/utils.h b/tools/testing/selftests/filesystems/incfs/utils.h |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/testing/selftests/filesystems/incfs/utils.h |
| @@ -0,0 +1,71 @@ |
| +/* SPDX-License-Identifier: GPL-2.0 */ |
| +/* |
| + * Copyright 2019 Google LLC |
| + */ |
| +#include <stdbool.h> |
| +#include <sys/stat.h> |
| + |
| +#include <include/uapi/linux/incrementalfs.h> |
| + |
| +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) |
| + |
| +#define __packed __attribute__((__packed__)) |
| + |
| +#ifdef __LP64__ |
| +#define ptr_to_u64(p) ((__u64)p) |
| +#else |
| +#define ptr_to_u64(p) ((__u64)(__u32)p) |
| +#endif |
| + |
| +#define SHA256_DIGEST_SIZE 32 |
| +#define INCFS_MAX_MTREE_LEVELS 8 |
| + |
| +unsigned int rnd(unsigned int max, unsigned int *seed); |
| + |
| +int remove_dir(const char *dir); |
| + |
| +int drop_caches(void); |
| + |
| +int mount_fs(const char *mount_dir, const char *backing_dir, |
| + int read_timeout_ms); |
| + |
| +int umount_fs(const char *mount_dir); |
| + |
| +int mount_fs_opt(const char *mount_dir, const char *backing_dir, |
| + const char *opt, bool remount); |
| + |
| +int get_file_bmap(int cmd_fd, int ino, unsigned char *buf, int buf_size); |
| + |
| +int get_file_signature(int fd, unsigned char *buf, int buf_size); |
| + |
| +int emit_node(int fd, char *filename, int *ino_out, int parent_ino, |
| + size_t size, mode_t mode, char *attr); |
| + |
| +int emit_file(int fd, const char *dir, const char *filename, |
| + incfs_uuid_t *id_out, size_t size, const char *attr); |
| + |
| +int crypto_emit_file(int fd, const char *dir, const char *filename, |
| + incfs_uuid_t *id_out, size_t size, const char *root_hash, |
| + const char *add_data); |
| + |
| +loff_t get_file_size(const char *name); |
| + |
| +int open_commands_file(const char *mount_dir); |
| + |
| +int open_log_file(const char *mount_dir); |
| + |
| +int open_blocks_written_file(const char *mount_dir); |
| + |
| +int wait_for_pending_reads(int fd, int timeout_ms, |
| + struct incfs_pending_read_info *prs, int prs_count); |
| + |
| +int wait_for_pending_reads2(int fd, int timeout_ms, |
| + struct incfs_pending_read_info2 *prs, int prs_count); |
| + |
| +char *concat_file_name(const char *dir, const char *file); |
| + |
| +void sha256(const char *data, size_t dsize, char *hash); |
| + |
| +void md5(const char *data, size_t dsize, char *hash); |
| + |
| +int delete_dir_tree(const char *path); |