| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2018 Google LLC |
| */ |
| #include <alloca.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <lz4.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/xattr.h> |
| |
| #include <linux/random.h> |
| #include <linux/unistd.h> |
| |
| #include <kselftest.h> |
| |
| #include "utils.h" |
| |
| #define TEST_FAILURE 1 |
| #define TEST_SUCCESS 0 |
| #define INCFS_MAX_MTREE_LEVELS 8 |
| |
| #define INCFS_ROOT_INODE 0 |
| |
| 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; |
| }; |
| |
| 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 } |
| }; |
| 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 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); |
| } |
| |
| 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 || |
| errno != EPERM) { |
| 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(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)); |
| } |
| |
| static int emit_test_blocks(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 = (file->index + block_index) % 2 == 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) { |
| 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 (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(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(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(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(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(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(char *mount_dir, struct test_file *file) |
| { |
| return validate_test_file_content_with_seed(mount_dir, file, 0); |
| } |
| |
| static int data_producer(char *mount_dir, struct test_files_set *test_set) |
| { |
| 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 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; |
| free(file->mtree); |
| } |
| |
| failure: |
| free(fill_block_array); |
| return err; |
| } |
| |
| static int cant_touch_index_test(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 = 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; |
| } |
| |
| 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(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}, |
| {".index", 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(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(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; |
| |
| build_mtree(file); |
| 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(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(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]; |
| |
| build_mtree(file); |
| 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) != 0) { |
| 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(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(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(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(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]; |
| |
| 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_producer(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 hash_tree_test(char *mount_dir) |
| { |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| /* Final unmount */ |
| 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); |
| free(backing_dir); |
| umount(mount_dir); |
| return TEST_FAILURE; |
| } |
| |
| enum expected_log { FULL_LOG, NO_LOG, PARTIAL_LOG }; |
| |
| static int validate_logs(char *mount_dir, int log_fd, struct test_file *file, |
| enum expected_log expected_log) |
| { |
| uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE]; |
| struct incfs_pending_read_info prs[2048] = {}; |
| int prs_size = ARRAY_SIZE(prs); |
| int block_cnt = 1 + (file->size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| int expected_read_block_cnt; |
| int res; |
| int read_count; |
| int i, j; |
| char *filename = concat_file_name(mount_dir, file->name); |
| int fd; |
| |
| fd = open(filename, O_RDONLY | O_CLOEXEC); |
| free(filename); |
| if (fd <= 0) |
| return TEST_FAILURE; |
| |
| if (block_cnt > prs_size) |
| block_cnt = prs_size; |
| expected_read_block_cnt = block_cnt; |
| |
| for (i = 0; i < block_cnt; i++) { |
| res = pread(fd, data, sizeof(data), |
| INCFS_DATA_FILE_BLOCK_SIZE * i); |
| |
| /* Make some read logs of type SAME_FILE_NEXT_BLOCK */ |
| if (i % 10 == 0) |
| usleep(20000); |
| |
| /* Skip some blocks to make logs of type SAME_FILE */ |
| if (i % 10 == 5) { |
| ++i; |
| --expected_read_block_cnt; |
| } |
| |
| if (res <= 0) |
| goto failure; |
| } |
| |
| read_count = wait_for_pending_reads( |
| log_fd, expected_log == NO_LOG ? 10 : 0, prs, prs_size); |
| if (expected_log == NO_LOG) { |
| if (read_count == 0) |
| goto success; |
| if (read_count < 0) |
| ksft_print_msg("Error reading logged reads %s.\n", |
| strerror(-read_count)); |
| else |
| ksft_print_msg("Somehow read empty logs.\n"); |
| goto failure; |
| } |
| |
| if (read_count < 0) { |
| ksft_print_msg("Error reading logged reads %s.\n", |
| strerror(-read_count)); |
| goto failure; |
| } |
| |
| i = 0; |
| if (expected_log == PARTIAL_LOG) { |
| if (read_count == 0) { |
| ksft_print_msg("No logs %s.\n", file->name); |
| goto failure; |
| } |
| |
| for (i = 0, j = 0; j < expected_read_block_cnt - read_count; |
| i++, j++) |
| if (i % 10 == 5) |
| ++i; |
| |
| } else if (read_count != expected_read_block_cnt) { |
| ksft_print_msg("Bad log read count %s %d %d.\n", file->name, |
| read_count, expected_read_block_cnt); |
| goto failure; |
| } |
| |
| for (j = 0; j < read_count; i++, j++) { |
| struct incfs_pending_read_info *read = &prs[j]; |
| |
| if (!same_id(&read->file_id, &file->id)) { |
| ksft_print_msg("Bad log read ino %s\n", file->name); |
| goto failure; |
| } |
| |
| if (read->block_index != i) { |
| ksft_print_msg("Bad log read ino %s %d %d.\n", |
| file->name, read->block_index, i); |
| goto failure; |
| } |
| |
| if (j != 0) { |
| unsigned long psn = prs[j - 1].serial_number; |
| |
| if (read->serial_number != psn + 1) { |
| ksft_print_msg("Bad log read sn %s %d %d.\n", |
| file->name, read->serial_number, |
| psn); |
| goto failure; |
| } |
| } |
| |
| if (read->timestamp_us == 0) { |
| ksft_print_msg("Bad log read timestamp %s.\n", |
| file->name); |
| goto failure; |
| } |
| |
| if (i % 10 == 5) |
| ++i; |
| } |
| |
| success: |
| close(fd); |
| return TEST_SUCCESS; |
| |
| failure: |
| close(fd); |
| return TEST_FAILURE; |
| } |
| |
| static int read_log_test(char *mount_dir) |
| { |
| struct test_files_set test = get_test_files_set(); |
| const int file_num = test.files_count; |
| int i = 0; |
| int cmd_fd = -1, log_fd = -1, drop_caches = -1; |
| char *backing_dir; |
| |
| backing_dir = create_backing_dir(mount_dir); |
| if (!backing_dir) |
| goto failure; |
| |
| if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| goto failure; |
| |
| cmd_fd = open_commands_file(mount_dir); |
| if (cmd_fd < 0) |
| goto failure; |
| |
| log_fd = open_log_file(mount_dir); |
| if (log_fd < 0) |
| ksft_print_msg("Can't open log file.\n"); |
| |
| /* Write data. */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (emit_file(cmd_fd, NULL, file->name, &file->id, |
| file->size, NULL)) |
| goto failure; |
| |
| if (emit_test_file_data(mount_dir, file)) |
| goto failure; |
| } |
| |
| /* Validate data */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) |
| goto failure; |
| } |
| |
| /* Unmount and mount again, to see that logs work after remount. */ |
| close(cmd_fd); |
| close(log_fd); |
| cmd_fd = -1; |
| if (umount(mount_dir) != 0) { |
| print_error("Can't unmout FS"); |
| goto failure; |
| } |
| |
| if (mount_fs_opt(mount_dir, backing_dir, "readahead=0", false) != 0) |
| goto failure; |
| |
| cmd_fd = open_commands_file(mount_dir); |
| if (cmd_fd < 0) |
| goto failure; |
| |
| log_fd = open_log_file(mount_dir); |
| if (log_fd < 0) |
| ksft_print_msg("Can't open log file.\n"); |
| |
| /* Validate data again */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) |
| goto failure; |
| } |
| |
| /* |
| * Unmount and mount again with no read log to make sure poll |
| * doesn't crash |
| */ |
| close(cmd_fd); |
| close(log_fd); |
| if (umount(mount_dir) != 0) { |
| print_error("Can't unmout FS"); |
| goto failure; |
| } |
| |
| if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=0", |
| false) != 0) |
| goto failure; |
| |
| log_fd = open_log_file(mount_dir); |
| if (log_fd < 0) |
| ksft_print_msg("Can't open log file.\n"); |
| |
| /* Validate data again - note should fail this time */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (validate_logs(mount_dir, log_fd, file, NO_LOG)) |
| goto failure; |
| } |
| |
| /* |
| * Remount and check that logs start working again |
| */ |
| drop_caches = open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); |
| if (drop_caches == -1) |
| goto failure; |
| i = write(drop_caches, "3", 1); |
| close(drop_caches); |
| if (i != 1) |
| goto failure; |
| |
| if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=1", |
| true) != 0) |
| goto failure; |
| |
| /* Validate data again */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (validate_logs(mount_dir, log_fd, file, PARTIAL_LOG)) |
| goto failure; |
| } |
| |
| /* |
| * Remount and check that logs start working again |
| */ |
| drop_caches = open("/proc/sys/vm/drop_caches", O_WRONLY | O_CLOEXEC); |
| if (drop_caches == -1) |
| goto failure; |
| i = write(drop_caches, "3", 1); |
| close(drop_caches); |
| if (i != 1) |
| goto failure; |
| |
| if (mount_fs_opt(mount_dir, backing_dir, "readahead=0,rlog_pages=4", |
| true) != 0) |
| goto failure; |
| |
| /* Validate data again */ |
| for (i = 0; i < file_num; i++) { |
| struct test_file *file = &test.files[i]; |
| |
| if (validate_logs(mount_dir, log_fd, file, FULL_LOG)) |
| goto failure; |
| } |
| |
| /* Final unmount */ |
| close(log_fd); |
| free(backing_dir); |
| if (umount(mount_dir) != 0) { |
| print_error("Can't unmout FS"); |
| goto failure; |
| } |
| |
| return TEST_SUCCESS; |
| |
| failure: |
| close(cmd_fd); |
| close(log_fd); |
| free(backing_dir); |
| umount(mount_dir); |
| return TEST_FAILURE; |
| } |
| |
| static int emit_partial_test_file_data(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; |
| |
| if (file->size == 0) |
| return 0; |
| |
| /* 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; |
| } |
| } |
| out: |
| free(block_indexes); |
| 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(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(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_blocks.count == 0) |
| 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(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; |
| } |
| |
| static int large_file(char *mount_dir) |
| { |
| char *backing_dir; |
| int cmd_fd = -1; |
| int i; |
| int result = TEST_FAILURE; |
| uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| int block_count = 3LL * 1024 * 1024 * 1024 / 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; |
| |
| 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); |
| 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 main(int argc, char *argv[]) |
| { |
| char *mount_dir = NULL; |
| int fails = 0; |
| int i; |
| int fd, count; |
| |
| // 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 { |
| int (*pfunc)(char *dir); |
| const char *name; |
| } 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), |
| }; |
| #undef MAKE_TEST |
| |
| /* Bring back for kernel 5.x */ |
| /* ksft_set_plan(ARRAY_SIZE(cases)); */ |
| |
| for (i = 0; i < ARRAY_SIZE(cases); ++i) { |
| ksft_print_msg("Running %s\n", cases[i].name); |
| if (cases[i].pfunc(mount_dir) == TEST_SUCCESS) |
| ksft_test_result_pass("%s\n", cases[i].name); |
| else { |
| ksft_test_result_fail("%s\n", cases[i].name); |
| fails++; |
| } |
| } |
| |
| umount2(mount_dir, MNT_FORCE); |
| rmdir(mount_dir); |
| |
| if (fails > 0) |
| ksft_exit_fail(); |
| else |
| ksft_exit_pass(); |
| return 0; |
| } |