From 071106ecf68c401173c58808b1cf5f68cc50d390 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Fri, 05 Jan 2024 08:39:27 +0000 Subject: [PATCH] change wifi driver to cypress --- kernel/tools/testing/selftests/filesystems/incfs/incfs_test.c | 2638 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 2,326 insertions(+), 312 deletions(-) diff --git a/kernel/tools/testing/selftests/filesystems/incfs/incfs_test.c b/kernel/tools/testing/selftests/filesystems/incfs/incfs_test.c index 6809399..bfd5f1a 100644 --- a/kernel/tools/testing/selftests/filesystems/incfs/incfs_test.c +++ b/kernel/tools/testing/selftests/filesystems/incfs/incfs_test.c @@ -2,36 +2,117 @@ /* * 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_MAX_MTREE_LEVELS 8 #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]; @@ -54,6 +135,8 @@ 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 { @@ -103,6 +186,13 @@ .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) }; } @@ -137,6 +227,17 @@ 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) @@ -206,6 +307,17 @@ 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); @@ -228,8 +340,7 @@ goto out; } - if (ioctl(fd, INCFS_IOC_PERMIT_FILL, &permit_fill) != -1 || - errno != EPERM) { + if (ioctl(fd, INCFS_IOC_PERMIT_FILL, &permit_fill) != -1) { print_error( "Successfully called PERMIT_FILL on non pending_read file"); return -errno; @@ -248,7 +359,7 @@ return fd; } -int get_file_attr(char *mnt_dir, incfs_uuid_t id, char *value, int size) +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; @@ -266,7 +377,13 @@ return !memcmp(id1->bytes, id2->bytes, sizeof(id1->bytes)); } -static int emit_test_blocks(char *mnt_dir, struct test_file *file, +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]; @@ -290,7 +407,8 @@ for (i = 0; i < block_count; i++) { int block_index = blocks[i]; - bool compress = (file->index + block_index) % 2 == 0; + 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; @@ -307,10 +425,10 @@ block_size = file->size - block_offset; rnd_buf(data, block_size, seed); - if (compress) { - size_t comp_size = LZ4_compress_default( - (char *)data, (char *)comp_data, block_size, - ARRAY_SIZE(comp_data)); + 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; @@ -323,6 +441,22 @@ 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; @@ -376,7 +510,7 @@ return (error < 0) ? error : blocks_written; } -static int emit_test_block(char *mnt_dir, struct test_file *file, +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); @@ -406,7 +540,7 @@ } } -static int emit_test_file_data(char *mount_dir, struct test_file *file) +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; @@ -439,7 +573,7 @@ return result; } -static loff_t read_whole_file(char *filename) +static loff_t read_whole_file(const char *filename) { int fd = -1; loff_t result; @@ -505,7 +639,7 @@ return result; } -static char *create_backing_dir(char *mount_dir) +static char *create_backing_dir(const char *mount_dir) { struct stat st; char backing_dir_name[255]; @@ -541,7 +675,7 @@ return strdup(backing_dir_name); } -static int validate_test_file_content_with_seed(char *mount_dir, +static int validate_test_file_content_with_seed(const char *mount_dir, struct test_file *file, unsigned int shuffle_seed) { @@ -603,12 +737,13 @@ return error; } -static int validate_test_file_content(char *mount_dir, struct test_file *file) +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(char *mount_dir, struct test_files_set *test_set) +static int data_producer(const char *mount_dir, struct test_files_set *test_set) { int ret = 0; int timeout_ms = 1000; @@ -620,6 +755,55 @@ 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; @@ -793,17 +977,15 @@ close(fd); if (err < fill_blocks.count) err = errno; - else { + else err = 0; - free(file->mtree); - } failure: free(fill_block_array); return err; } -static int cant_touch_index_test(char *mount_dir) +static int cant_touch_index_test(const char *mount_dir) { char *file_name = "test_file"; int file_size = 123; @@ -834,6 +1016,12 @@ 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; } @@ -871,6 +1059,12 @@ 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); @@ -893,7 +1087,8 @@ return TEST_FAILURE; } -static bool iterate_directory(char *dir_to_iterate, bool root, int file_count) +static bool iterate_directory(const char *dir_to_iterate, bool root, + int file_count) { struct expected_name { const char *name; @@ -902,7 +1097,9 @@ } 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}, }; @@ -998,7 +1195,7 @@ return pass; } -static int basic_file_ops_test(char *mount_dir) +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; @@ -1164,7 +1361,7 @@ return TEST_FAILURE; } -static int dynamic_files_and_data_test(char *mount_dir) +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; @@ -1205,7 +1402,6 @@ struct test_file *file = &test.files[i]; int res; - build_mtree(file); res = emit_file(cmd_fd, NULL, file->name, &file->id, file->size, NULL); if (res < 0) { @@ -1270,7 +1466,7 @@ return TEST_FAILURE; } -static int concurrent_reads_and_writes_test(char *mount_dir) +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; @@ -1392,7 +1588,7 @@ return TEST_FAILURE; } -static int work_after_remount_test(char *mount_dir) +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; @@ -1418,7 +1614,6 @@ for (i = 0; i < file_num_stage1; i++) { struct test_file *file = &test.files[i]; - build_mtree(file); if (emit_file(cmd_fd, NULL, file->name, &file->id, file->size, NULL)) goto failure; @@ -1488,7 +1683,7 @@ goto failure; } - if (access(filename_in_index, F_OK) != 0) { + if (access(filename_in_index, F_OK) != -1) { ksft_print_msg("File %s is still present.\n", filename_in_index); goto failure; @@ -1542,7 +1737,7 @@ return TEST_FAILURE; } -static int attribute_test(char *mount_dir) +static int attribute_test(const char *mount_dir) { char file_attr[] = "metadata123123"; char attr_buf[INCFS_MAX_FILE_ATTR_SIZE] = {}; @@ -1625,7 +1820,7 @@ return TEST_FAILURE; } -static int child_procs_waiting_for_data_test(char *mount_dir) +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; @@ -1717,7 +1912,7 @@ return TEST_FAILURE; } -static int multiple_providers_test(char *mount_dir) +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; @@ -1733,7 +1928,8 @@ goto failure; /* Mount FS and release the backing file. (10s wait time) */ - if (mount_fs(mount_dir, backing_dir, 10000) != 0) + 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); @@ -1760,7 +1956,7 @@ * pending reads. */ - ret = data_producer(mount_dir, &test); + ret = data_producer2(mount_dir, &test); exit(-ret); } else if (producer_pid > 0) { producer_pids[i] = producer_pid; @@ -1812,8 +2008,47 @@ return TEST_FAILURE; } -static int hash_tree_test(char *mount_dir) +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; @@ -1878,6 +2113,8 @@ } } 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. */ @@ -1915,319 +2152,250 @@ } else if (validate_test_file_content(mount_dir, file) < 0) goto failure; } - - /* Final unmount */ - close(cmd_fd); - cmd_fd = -1; - if (umount(mount_dir) != 0) { - print_error("Can't unmout FS"); - goto failure; - } - return TEST_SUCCESS; + 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 TEST_FAILURE; + return result; } enum expected_log { FULL_LOG, NO_LOG, PARTIAL_LOG }; -static int validate_logs(char *mount_dir, int log_fd, struct test_file *file, - enum expected_log expected_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_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; - int expected_read_block_cnt; - int res; - int read_count; - int i, j; - char *filename = concat_file_name(mount_dir, file->name); - int fd; + 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; - fd = open(filename, O_RDONLY | O_CLOEXEC); - free(filename); - if (fd <= 0) - return TEST_FAILURE; + TEST(filename = concat_file_name(mount_dir, file->name), filename); + TEST(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); - if (block_cnt > prs_size) - block_cnt = prs_size; - expected_read_block_cnt = block_cnt; + if (block_count > prs_size) + block_count = prs_size; + expected_read_count = block_count; - for (i = 0; i < block_cnt; i++) { - res = pread(fd, data, sizeof(data), - INCFS_DATA_FILE_BLOCK_SIZE * i); + 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 (i % 10 == 0) + if (block_index % 100 == 10) usleep(20000); /* Skip some blocks to make logs of type SAME_FILE */ - if (i % 10 == 5) { - ++i; - --expected_read_block_cnt; + if (block_index % 10 == 5) { + ++block_index; + --expected_read_count; } - if (res <= 0) - goto failure; + if (expect_data) + TESTCOND(result > 0); + + if (!expect_data) + TESTEQUAL(result, -1); } - read_count = wait_for_pending_reads( - log_fd, expected_log == NO_LOG ? 10 : 0, prs, prs_size); - if (expected_log == NO_LOG) { - if (read_count == 0) - goto success; - if (read_count < 0) - ksft_print_msg("Error reading logged reads %s.\n", - strerror(-read_count)); - else - ksft_print_msg("Somehow read empty logs.\n"); - goto failure; + 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; } - if (read_count < 0) { - ksft_print_msg("Error reading logged reads %s.\n", - strerror(-read_count)); - goto failure; - } - - i = 0; - if (expected_log == PARTIAL_LOG) { - if (read_count == 0) { - ksft_print_msg("No logs %s.\n", file->name); - goto failure; - } - - for (i = 0, j = 0; j < expected_read_block_cnt - read_count; - i++, j++) - if (i % 10 == 5) - ++i; - - } else if (read_count != expected_read_block_cnt) { - ksft_print_msg("Bad log read count %s %d %d.\n", file->name, - read_count, expected_read_block_cnt); - goto failure; - } - - for (j = 0; j < read_count; i++, j++) { - struct incfs_pending_read_info *read = &prs[j]; - - if (!same_id(&read->file_id, &file->id)) { - ksft_print_msg("Bad log read ino %s\n", file->name); - goto failure; - } - - if (read->block_index != i) { - ksft_print_msg("Bad log read ino %s %d %d.\n", - file->name, read->block_index, i); - goto failure; - } - - if (j != 0) { - unsigned long psn = prs[j - 1].serial_number; - - if (read->serial_number != psn + 1) { - ksft_print_msg("Bad log read sn %s %d %d.\n", - file->name, read->serial_number, - psn); - goto failure; - } - } - - if (read->timestamp_us == 0) { - ksft_print_msg("Bad log read timestamp %s.\n", - file->name); - goto failure; - } - - if (i % 10 == 5) - ++i; - } - -success: + result = TEST_SUCCESS; +out: close(fd); - return TEST_SUCCESS; - -failure: - close(fd); - return TEST_FAILURE; + free(filename); + return result; } -static int read_log_test(char *mount_dir) +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, drop_caches = -1; - char *backing_dir; + int cmd_fd = -1, log_fd = -1; + char *backing_dir = NULL; - 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; - - log_fd = open_log_file(mount_dir); - if (log_fd < 0) - ksft_print_msg("Can't open log file.\n"); - - /* Write data. */ + /* 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]; - 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; + TESTEQUAL(emit_file(cmd_fd, NULL, file->name, &file->id, + file->size, NULL), 0); } - - /* Validate data */ - for (i = 0; i < file_num; i++) { - struct test_file *file = &test.files[i]; - - if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) - goto failure; - } - - /* Unmount and mount again, to see that logs work after remount. */ close(cmd_fd); - close(log_fd); cmd_fd = -1; - if (umount(mount_dir) != 0) { - print_error("Can't unmout FS"); - goto failure; - } - if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) - goto failure; + /* 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); - cmd_fd = open_commands_file(mount_dir); - if (cmd_fd < 0) - goto failure; - - log_fd = open_log_file(mount_dir); - if (log_fd < 0) - ksft_print_msg("Can't open log file.\n"); - - /* Validate data again */ - for (i = 0; i < file_num; i++) { - struct test_file *file = &test.files[i]; - - if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) - goto failure; - } - - /* - * Unmount and mount again with no read log to make sure poll - * doesn't crash - */ - close(cmd_fd); + /* Unmount and mount again without report_uid */ close(log_fd); - if (umount(mount_dir) != 0) { - print_error("Can't unmout FS"); - goto failure; - } + log_fd = -1; + TESTEQUAL(umount(mount_dir), 0); + TESTEQUAL(mount_fs_opt(mount_dir, backing_dir, + "readahead=0,read_timeout_ms=0", false), 0); - if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=0", - false) != 0) - goto failure; + 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); - log_fd = open_log_file(mount_dir); - if (log_fd < 0) - ksft_print_msg("Can't open log file.\n"); + /* 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); - /* Validate data again - note should fail this time */ + 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++) { - struct test_file *file = &test.files[i]; - - if (validate_logs(mount_dir, log_fd, file, NO_LOG)) - goto failure; - } - - /* - * Remount and check that logs start working again - */ - drop_caches = open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); - if (drop_caches == -1) - goto failure; - i = write(drop_caches, "3", 1); - close(drop_caches); - if (i != 1) - goto failure; - - if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=1", - true) != 0) - goto failure; - - /* Validate data again */ - for (i = 0; i < file_num; i++) { - struct test_file *file = &test.files[i]; - - if (validate_logs(mount_dir, log_fd, file, PARTIAL_LOG)) - goto failure; - } - - /* - * Remount and check that logs start working again - */ - drop_caches = open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); - if (drop_caches == -1) - goto failure; - i = write(drop_caches, "3", 1); - close(drop_caches); - if (i != 1) - goto failure; - - if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=4", - true) != 0) - goto failure; - - /* Validate data again */ - for (i = 0; i < file_num; i++) { - struct test_file *file = &test.files[i]; - - if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) - goto failure; + 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); - free(backing_dir); - if (umount(mount_dir) != 0) { - print_error("Can't unmout FS"); - goto failure; - } + log_fd = -1; + TESTEQUAL(umount(mount_dir), 0); - return TEST_SUCCESS; - -failure: + result = TEST_SUCCESS; +out: close(cmd_fd); close(log_fd); free(backing_dir); umount(mount_dir); - return TEST_FAILURE; + return result; } -static int emit_partial_test_file_data(char *mount_dir, struct test_file *file) +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)); @@ -2248,9 +2416,29 @@ 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; } @@ -2384,7 +2572,7 @@ return error; } -static int get_blocks_test(char *mount_dir) +static int get_blocks_test(const char *mount_dir) { char *backing_dir; int cmd_fd = -1; @@ -2441,7 +2629,8 @@ return TEST_FAILURE; } -static int emit_partial_test_file_hash(char *mount_dir, struct test_file *file) +static int emit_partial_test_file_hash(const char *mount_dir, + struct test_file *file) { int err; int fd; @@ -2453,9 +2642,6 @@ uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; if (file->size <= 4096 / 32 * 4096) - return 0; - - if (fill_blocks.count == 0) return 0; if (!fill_block_array) @@ -2559,7 +2745,7 @@ return error; } -static int get_hash_blocks_test(char *mount_dir) +static int get_hash_blocks_test(const char *mount_dir) { char *backing_dir; int cmd_fd = -1; @@ -2609,14 +2795,16 @@ return TEST_FAILURE; } -static int large_file(char *mount_dir) +#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; + int result = TEST_FAILURE, ret; uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; - int block_count = 3LL * 1024 * 1024 * 1024 / 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 = { @@ -2624,7 +2812,23 @@ .fill_blocks = ptr_to_u64(block_buf), }; incfs_uuid_t id; - int fd; + 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) @@ -2665,6 +2869,1765 @@ 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; } @@ -2691,12 +4654,59 @@ 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 fails = 0; 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 @@ -2721,10 +4731,7 @@ { \ test, #test \ } - struct { - int (*pfunc)(char *dir); - const char *name; - } cases[] = { + struct test_case cases[] = { MAKE_TEST(basic_file_ops_test), MAKE_TEST(cant_touch_index_test), MAKE_TEST(dynamic_files_and_data_test), @@ -2737,29 +4744,36 @@ MAKE_TEST(read_log_test), MAKE_TEST(get_blocks_test), MAKE_TEST(get_hash_blocks_test), - MAKE_TEST(large_file), + 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), }; #undef MAKE_TEST - /* Bring back for kernel 5.x */ - /* ksft_set_plan(ARRAY_SIZE(cases)); */ + if (options.test) { + if (options.test <= 0 || options.test > ARRAY_SIZE(cases)) + ksft_exit_fail_msg("Invalid test\n"); - for (i = 0; i < ARRAY_SIZE(cases); ++i) { - ksft_print_msg("Running %s\n", cases[i].name); - if (cases[i].pfunc(mount_dir) == TEST_SUCCESS) - ksft_test_result_pass("%s\n", cases[i].name); - else { - ksft_test_result_fail("%s\n", cases[i].name); - fails++; - } + 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); - - if (fails > 0) - ksft_exit_fail(); - else - ksft_exit_pass(); - return 0; + return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); } -- Gitblit v1.6.2