| // 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, 50) != 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(); |
| } |