6db4831e98
Android 14
4629 lines
114 KiB
C
4629 lines
114 KiB
C
// 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 <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_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;
|
|
};
|
|
|
|
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 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;
|
|
free(file->mtree);
|
|
}
|
|
|
|
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;
|
|
|
|
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(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];
|
|
|
|
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) != -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 hash_tree_test(const 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(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 = atol(buffer);
|
|
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 = atol(buffer);
|
|
|
|
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;
|
|
}
|
|
|
|
static int large_file_test(const 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 = -1;
|
|
|
|
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);
|
|
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), 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), 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;
|
|
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,
|
|
};
|
|
unsigned char *sig = NULL;
|
|
size_t sig_len = 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
|
|
};
|
|
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_len),
|
|
0);
|
|
|
|
fear.sig_size = sig_len;
|
|
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;
|
|
}
|
|
|
|
if (key)
|
|
TESTEQUAL(sign(key, cert, (void *)&fsverity_signed_digest,
|
|
sizeof(fsverity_signed_digest), &sig, &sig_len),
|
|
0);
|
|
|
|
if (use_signatures) {
|
|
fear.sig_size = sig_len;
|
|
fear.sig_ptr = ptr_to_u64(sig);
|
|
} 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 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 = {};
|
|
|
|
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);
|
|
|
|
result = TEST_SUCCESS;
|
|
out:
|
|
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(emit_partial_test_file_hash(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:
|
|
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, true), 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:
|
|
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];
|
|
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);
|
|
TEST(read(fd, NULL, 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, 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 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)
|
|
{
|
|
ksft_print_msg("Running %s\n", test_case->name);
|
|
if (test_case->pfunc(mount_dir) == TEST_SUCCESS)
|
|
ksft_test_result_pass("%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),
|
|
};
|
|
#undef MAKE_TEST
|
|
|
|
if (options.test) {
|
|
if (options.test <= 0 || options.test > ARRAY_SIZE(cases))
|
|
ksft_exit_fail_msg("Invalid test\n");
|
|
|
|
run_one_test(mount_dir, &cases[options.test - 1]);
|
|
} else
|
|
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();
|
|
}
|