718 lines
16 KiB
C
718 lines
16 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright 2020 Google LLC
|
||
|
*/
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <getopt.h>
|
||
|
#include <lz4.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/mount.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <time.h>
|
||
|
#include <ctype.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include "utils.h"
|
||
|
|
||
|
#define err_msg(...) \
|
||
|
do { \
|
||
|
fprintf(stderr, "%s: (%d) ", TAG, __LINE__); \
|
||
|
fprintf(stderr, __VA_ARGS__); \
|
||
|
fprintf(stderr, " (%s)\n", strerror(errno)); \
|
||
|
} while (false)
|
||
|
|
||
|
#define TAG "incfs_perf"
|
||
|
|
||
|
struct options {
|
||
|
int blocks; /* -b number of diff block sizes */
|
||
|
bool no_cleanup; /* -c don't clean up after */
|
||
|
const char *test_dir; /* -d working directory */
|
||
|
const char *file_types; /* -f sScCvV */
|
||
|
bool no_native; /* -n don't test native files */
|
||
|
bool no_random; /* -r don't do random reads*/
|
||
|
bool no_linear; /* -R random reads only */
|
||
|
size_t size; /* -s file size as power of 2 */
|
||
|
int tries; /* -t times to run test*/
|
||
|
};
|
||
|
|
||
|
enum flags {
|
||
|
SHUFFLE = 1,
|
||
|
COMPRESS = 2,
|
||
|
VERIFY = 4,
|
||
|
LAST_FLAG = 8,
|
||
|
};
|
||
|
|
||
|
void print_help(void)
|
||
|
{
|
||
|
puts(
|
||
|
"incfs_perf. Performance test tool for incfs\n"
|
||
|
"\tTests read performance of incfs by creating files of various types\n"
|
||
|
"\tflushing caches and then reading them back.\n"
|
||
|
"\tEach file is read with different block sizes and average\n"
|
||
|
"\tthroughput in megabytes/second and memory usage are reported for\n"
|
||
|
"\teach block size\n"
|
||
|
"\tNative files are tested for comparison\n"
|
||
|
"\tNative files are created in native folder, incfs files are created\n"
|
||
|
"\tin src folder which is mounted on dst folder\n"
|
||
|
"\n"
|
||
|
"\t-bn (default 8) number of different block sizes, starting at 4096\n"
|
||
|
"\t and doubling\n"
|
||
|
"\t-c don't Clean up - leave files and mount point\n"
|
||
|
"\t-d dir create directories in dir\n"
|
||
|
"\t-fs|Sc|Cv|V restrict which files are created.\n"
|
||
|
"\t s blocks not shuffled, S blocks shuffled\n"
|
||
|
"\t c blocks not compress, C blocks compressed\n"
|
||
|
"\t v files not verified, V files verified\n"
|
||
|
"\t If a letter is omitted, both options are tested\n"
|
||
|
"\t If no letter are given, incfs is not tested\n"
|
||
|
"\t-n Don't test native files\n"
|
||
|
"\t-r No random reads (sequential only)\n"
|
||
|
"\t-R Random reads only (no sequential)\n"
|
||
|
"\t-sn (default 30)File size as power of 2\n"
|
||
|
"\t-tn (default 5) Number of tries per file. Results are averaged\n"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
int parse_options(int argc, char *const *argv, struct options *options)
|
||
|
{
|
||
|
signed char c;
|
||
|
|
||
|
/* Set defaults here */
|
||
|
*options = (struct options){
|
||
|
.blocks = 8,
|
||
|
.test_dir = ".",
|
||
|
.tries = 5,
|
||
|
.size = 30,
|
||
|
};
|
||
|
|
||
|
/* Load options from command line here */
|
||
|
while ((c = getopt(argc, argv, "b:cd:f::hnrRs:t:")) != -1) {
|
||
|
switch (c) {
|
||
|
case 'b':
|
||
|
options->blocks = strtol(optarg, NULL, 10);
|
||
|
break;
|
||
|
|
||
|
case 'c':
|
||
|
options->no_cleanup = true;
|
||
|
break;
|
||
|
|
||
|
case 'd':
|
||
|
options->test_dir = optarg;
|
||
|
break;
|
||
|
|
||
|
case 'f':
|
||
|
if (optarg)
|
||
|
options->file_types = optarg;
|
||
|
else
|
||
|
options->file_types = "sS";
|
||
|
break;
|
||
|
|
||
|
case 'h':
|
||
|
print_help();
|
||
|
exit(0);
|
||
|
|
||
|
case 'n':
|
||
|
options->no_native = true;
|
||
|
break;
|
||
|
|
||
|
case 'r':
|
||
|
options->no_random = true;
|
||
|
break;
|
||
|
|
||
|
case 'R':
|
||
|
options->no_linear = true;
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
options->size = strtol(optarg, NULL, 10);
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
options->tries = strtol(optarg, NULL, 10);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
print_help();
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
options->size = 1L << options->size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void shuffle(size_t *buffer, size_t size)
|
||
|
{
|
||
|
size_t i;
|
||
|
|
||
|
for (i = 0; i < size; ++i) {
|
||
|
size_t j = random() * (size - i - 1) / RAND_MAX;
|
||
|
size_t temp = buffer[i];
|
||
|
|
||
|
buffer[i] = buffer[j];
|
||
|
buffer[j] = temp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int get_free_memory(void)
|
||
|
{
|
||
|
FILE *meminfo = fopen("/proc/meminfo", "re");
|
||
|
char field[256];
|
||
|
char value[256] = {};
|
||
|
|
||
|
if (!meminfo)
|
||
|
return -ENOENT;
|
||
|
|
||
|
while (fscanf(meminfo, "%[^:]: %s kB\n", field, value) == 2) {
|
||
|
if (!strcmp(field, "MemFree"))
|
||
|
break;
|
||
|
*value = 0;
|
||
|
}
|
||
|
|
||
|
fclose(meminfo);
|
||
|
|
||
|
if (!*value)
|
||
|
return -ENOENT;
|
||
|
|
||
|
return strtol(value, NULL, 10);
|
||
|
}
|
||
|
|
||
|
int write_data(int cmd_fd, int dir_fd, const char *name, size_t size, int flags)
|
||
|
{
|
||
|
int fd = openat(dir_fd, name, O_RDWR | O_CLOEXEC);
|
||
|
struct incfs_permit_fill permit_fill = {
|
||
|
.file_descriptor = fd,
|
||
|
};
|
||
|
int block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
|
||
|
size_t *blocks = malloc(sizeof(size_t) * block_count);
|
||
|
int error = 0;
|
||
|
size_t i;
|
||
|
uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {};
|
||
|
uint8_t compressed_data[INCFS_DATA_FILE_BLOCK_SIZE] = {};
|
||
|
struct incfs_fill_block fill_block = {
|
||
|
.compression = COMPRESSION_NONE,
|
||
|
.data_len = sizeof(data),
|
||
|
.data = ptr_to_u64(data),
|
||
|
};
|
||
|
|
||
|
if (!blocks) {
|
||
|
err_msg("Out of memory");
|
||
|
error = -errno;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (fd == -1) {
|
||
|
err_msg("Could not open file for writing %s", name);
|
||
|
error = -errno;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) {
|
||
|
err_msg("Failed to call PERMIT_FILL");
|
||
|
error = -errno;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < block_count; ++i)
|
||
|
blocks[i] = i;
|
||
|
|
||
|
if (flags & SHUFFLE)
|
||
|
shuffle(blocks, block_count);
|
||
|
|
||
|
if (flags & COMPRESS) {
|
||
|
size_t comp_size = LZ4_compress_default(
|
||
|
(char *)data, (char *)compressed_data, sizeof(data),
|
||
|
ARRAY_SIZE(compressed_data));
|
||
|
|
||
|
if (comp_size <= 0) {
|
||
|
error = -EBADMSG;
|
||
|
goto out;
|
||
|
}
|
||
|
fill_block.compression = COMPRESSION_LZ4;
|
||
|
fill_block.data = ptr_to_u64(compressed_data);
|
||
|
fill_block.data_len = comp_size;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < block_count; ++i) {
|
||
|
struct incfs_fill_blocks fill_blocks = {
|
||
|
.count = 1,
|
||
|
.fill_blocks = ptr_to_u64(&fill_block),
|
||
|
};
|
||
|
|
||
|
fill_block.block_index = blocks[i];
|
||
|
int written = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks);
|
||
|
|
||
|
if (written != 1) {
|
||
|
error = -errno;
|
||
|
err_msg("Failed to write block %lu in file %s", i,
|
||
|
name);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
free(blocks);
|
||
|
close(fd);
|
||
|
sync();
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
int measure_read_throughput_internal(const char *tag, int dir, const char *name,
|
||
|
const struct options *options, bool random)
|
||
|
{
|
||
|
int block;
|
||
|
|
||
|
if (random)
|
||
|
printf("%32s(random)", tag);
|
||
|
else
|
||
|
printf("%40s", tag);
|
||
|
|
||
|
for (block = 0; block < options->blocks; ++block) {
|
||
|
size_t buffer_size;
|
||
|
char *buffer;
|
||
|
int try;
|
||
|
double time = 0;
|
||
|
double throughput;
|
||
|
int memory = 0;
|
||
|
|
||
|
buffer_size = 1 << (block + 12);
|
||
|
buffer = malloc(buffer_size);
|
||
|
|
||
|
for (try = 0; try < options->tries; ++try) {
|
||
|
int err;
|
||
|
struct timespec start_time, end_time;
|
||
|
off_t i;
|
||
|
int fd;
|
||
|
size_t offsets_size = options->size / buffer_size;
|
||
|
size_t *offsets =
|
||
|
malloc(offsets_size * sizeof(*offsets));
|
||
|
int start_memory, end_memory;
|
||
|
|
||
|
if (!offsets) {
|
||
|
err_msg("Not enough memory");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < offsets_size; ++i)
|
||
|
offsets[i] = i * buffer_size;
|
||
|
|
||
|
if (random)
|
||
|
shuffle(offsets, offsets_size);
|
||
|
|
||
|
err = drop_caches();
|
||
|
if (err) {
|
||
|
err_msg("Failed to drop caches");
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
start_memory = get_free_memory();
|
||
|
if (start_memory < 0) {
|
||
|
err_msg("Failed to get start memory");
|
||
|
return start_memory;
|
||
|
}
|
||
|
|
||
|
fd = openat(dir, name, O_RDONLY | O_CLOEXEC);
|
||
|
if (fd == -1) {
|
||
|
err_msg("Failed to open file");
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
err = clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||
|
if (err) {
|
||
|
err_msg("Failed to get start time");
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < offsets_size; ++i)
|
||
|
if (pread(fd, buffer, buffer_size,
|
||
|
offsets[i]) != buffer_size) {
|
||
|
err_msg("Failed to read file");
|
||
|
err = -errno;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
err = clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||
|
if (err) {
|
||
|
err_msg("Failed to get start time");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
end_memory = get_free_memory();
|
||
|
if (end_memory < 0) {
|
||
|
err_msg("Failed to get end memory");
|
||
|
return end_memory;
|
||
|
}
|
||
|
|
||
|
time += end_time.tv_sec - start_time.tv_sec;
|
||
|
time += (end_time.tv_nsec - start_time.tv_nsec) / 1e9;
|
||
|
|
||
|
close(fd);
|
||
|
fd = -1;
|
||
|
memory += start_memory - end_memory;
|
||
|
|
||
|
fail:
|
||
|
free(offsets);
|
||
|
close(fd);
|
||
|
if (err)
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
throughput = options->size * options->tries / time;
|
||
|
printf("%10.3e %10d", throughput, memory / options->tries);
|
||
|
free(buffer);
|
||
|
}
|
||
|
|
||
|
printf("\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int measure_read_throughput(const char *tag, int dir, const char *name,
|
||
|
const struct options *options)
|
||
|
{
|
||
|
int err = 0;
|
||
|
|
||
|
if (!options->no_linear)
|
||
|
err = measure_read_throughput_internal(tag, dir, name, options,
|
||
|
false);
|
||
|
|
||
|
if (!err && !options->no_random)
|
||
|
err = measure_read_throughput_internal(tag, dir, name, options,
|
||
|
true);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int test_native_file(int dir, const struct options *options)
|
||
|
{
|
||
|
const char *name = "file";
|
||
|
int fd;
|
||
|
char buffer[4096] = {};
|
||
|
off_t i;
|
||
|
int err;
|
||
|
|
||
|
fd = openat(dir, name, O_CREAT | O_WRONLY | O_CLOEXEC, 0600);
|
||
|
if (fd == -1) {
|
||
|
err_msg("Could not open native file");
|
||
|
return -errno;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < options->size; i += sizeof(buffer))
|
||
|
if (pwrite(fd, buffer, sizeof(buffer), i) != sizeof(buffer)) {
|
||
|
err_msg("Failed to write file");
|
||
|
err = -errno;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
close(fd);
|
||
|
sync();
|
||
|
fd = -1;
|
||
|
|
||
|
err = measure_read_throughput("native", dir, name, options);
|
||
|
|
||
|
fail:
|
||
|
close(fd);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
struct hash_block {
|
||
|
char data[INCFS_DATA_FILE_BLOCK_SIZE];
|
||
|
};
|
||
|
|
||
|
static struct hash_block *build_mtree(size_t size, char *root_hash,
|
||
|
int *mtree_block_count)
|
||
|
{
|
||
|
char data[INCFS_DATA_FILE_BLOCK_SIZE] = {};
|
||
|
const int digest_size = SHA256_DIGEST_SIZE;
|
||
|
const int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size;
|
||
|
int block_count = 0;
|
||
|
int hash_block_count = 0;
|
||
|
int total_tree_block_count = 0;
|
||
|
int tree_lvl_index[INCFS_MAX_MTREE_LEVELS] = {};
|
||
|
int tree_lvl_count[INCFS_MAX_MTREE_LEVELS] = {};
|
||
|
int levels_count = 0;
|
||
|
int i, level;
|
||
|
struct hash_block *mtree;
|
||
|
|
||
|
if (size == 0)
|
||
|
return 0;
|
||
|
|
||
|
block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
|
||
|
hash_block_count = block_count;
|
||
|
for (i = 0; hash_block_count > 1; i++) {
|
||
|
hash_block_count = (hash_block_count + hash_per_block - 1) /
|
||
|
hash_per_block;
|
||
|
tree_lvl_count[i] = hash_block_count;
|
||
|
total_tree_block_count += hash_block_count;
|
||
|
}
|
||
|
levels_count = i;
|
||
|
|
||
|
for (i = 0; i < levels_count; i++) {
|
||
|
int prev_lvl_base = (i == 0) ? total_tree_block_count :
|
||
|
tree_lvl_index[i - 1];
|
||
|
|
||
|
tree_lvl_index[i] = prev_lvl_base - tree_lvl_count[i];
|
||
|
}
|
||
|
|
||
|
*mtree_block_count = total_tree_block_count;
|
||
|
mtree = calloc(total_tree_block_count, sizeof(*mtree));
|
||
|
/* Build level 0 hashes. */
|
||
|
for (i = 0; i < block_count; i++) {
|
||
|
int block_index = tree_lvl_index[0] + i / hash_per_block;
|
||
|
int block_off = (i % hash_per_block) * digest_size;
|
||
|
char *hash_ptr = mtree[block_index].data + block_off;
|
||
|
|
||
|
sha256(data, INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr);
|
||
|
}
|
||
|
|
||
|
/* Build higher levels of hash tree. */
|
||
|
for (level = 1; level < levels_count; level++) {
|
||
|
int prev_lvl_base = tree_lvl_index[level - 1];
|
||
|
int prev_lvl_count = tree_lvl_count[level - 1];
|
||
|
|
||
|
for (i = 0; i < prev_lvl_count; i++) {
|
||
|
int block_index =
|
||
|
i / hash_per_block + tree_lvl_index[level];
|
||
|
int block_off = (i % hash_per_block) * digest_size;
|
||
|
char *hash_ptr = mtree[block_index].data + block_off;
|
||
|
|
||
|
sha256(mtree[i + prev_lvl_base].data,
|
||
|
INCFS_DATA_FILE_BLOCK_SIZE, hash_ptr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Calculate root hash from the top block */
|
||
|
sha256(mtree[0].data, INCFS_DATA_FILE_BLOCK_SIZE, root_hash);
|
||
|
|
||
|
return mtree;
|
||
|
}
|
||
|
|
||
|
static int load_hash_tree(int cmd_fd, int dir, const char *name,
|
||
|
struct hash_block *mtree, int mtree_block_count)
|
||
|
{
|
||
|
int err;
|
||
|
int i;
|
||
|
int fd;
|
||
|
struct incfs_fill_block *fill_block_array =
|
||
|
calloc(mtree_block_count, sizeof(struct incfs_fill_block));
|
||
|
struct incfs_fill_blocks fill_blocks = {
|
||
|
.count = mtree_block_count,
|
||
|
.fill_blocks = ptr_to_u64(fill_block_array),
|
||
|
};
|
||
|
struct incfs_permit_fill permit_fill;
|
||
|
|
||
|
if (!fill_block_array)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
for (i = 0; i < fill_blocks.count; i++) {
|
||
|
fill_block_array[i] = (struct incfs_fill_block){
|
||
|
.block_index = i,
|
||
|
.data_len = INCFS_DATA_FILE_BLOCK_SIZE,
|
||
|
.data = ptr_to_u64(mtree[i].data),
|
||
|
.flags = INCFS_BLOCK_FLAGS_HASH
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fd = openat(dir, name, O_RDONLY | O_CLOEXEC);
|
||
|
if (fd < 0) {
|
||
|
err = errno;
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
permit_fill.file_descriptor = fd;
|
||
|
if (ioctl(cmd_fd, INCFS_IOC_PERMIT_FILL, &permit_fill)) {
|
||
|
err_msg("Failed to call PERMIT_FILL");
|
||
|
err = -errno;
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
err = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks);
|
||
|
close(fd);
|
||
|
if (err < fill_blocks.count)
|
||
|
err = errno;
|
||
|
else
|
||
|
err = 0;
|
||
|
|
||
|
failure:
|
||
|
free(fill_block_array);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
int test_incfs_file(int dst_dir, const struct options *options, int flags)
|
||
|
{
|
||
|
int cmd_file = openat(dst_dir, INCFS_PENDING_READS_FILENAME,
|
||
|
O_RDONLY | O_CLOEXEC);
|
||
|
int err;
|
||
|
char name[4];
|
||
|
incfs_uuid_t id;
|
||
|
char tag[256];
|
||
|
|
||
|
snprintf(name, sizeof(name), "%c%c%c",
|
||
|
flags & SHUFFLE ? 'S' : 's',
|
||
|
flags & COMPRESS ? 'C' : 'c',
|
||
|
flags & VERIFY ? 'V' : 'v');
|
||
|
|
||
|
if (cmd_file == -1) {
|
||
|
err_msg("Could not open command file");
|
||
|
return -errno;
|
||
|
}
|
||
|
|
||
|
if (flags & VERIFY) {
|
||
|
char root_hash[INCFS_MAX_HASH_SIZE];
|
||
|
int mtree_block_count;
|
||
|
struct hash_block *mtree = build_mtree(options->size, root_hash,
|
||
|
&mtree_block_count);
|
||
|
|
||
|
if (!mtree) {
|
||
|
err_msg("Failed to build hash tree");
|
||
|
err = -ENOMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
err = crypto_emit_file(cmd_file, NULL, name, &id, options->size,
|
||
|
root_hash, "add_data");
|
||
|
|
||
|
if (!err)
|
||
|
err = load_hash_tree(cmd_file, dst_dir, name, mtree,
|
||
|
mtree_block_count);
|
||
|
|
||
|
free(mtree);
|
||
|
} else
|
||
|
err = emit_file(cmd_file, NULL, name, &id, options->size, NULL);
|
||
|
|
||
|
if (err) {
|
||
|
err_msg("Failed to create file %s", name);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
if (write_data(cmd_file, dst_dir, name, options->size, flags))
|
||
|
goto fail;
|
||
|
|
||
|
snprintf(tag, sizeof(tag), "incfs%s%s%s",
|
||
|
flags & SHUFFLE ? "(shuffle)" : "",
|
||
|
flags & COMPRESS ? "(compress)" : "",
|
||
|
flags & VERIFY ? "(verify)" : "");
|
||
|
|
||
|
err = measure_read_throughput(tag, dst_dir, name, options);
|
||
|
|
||
|
fail:
|
||
|
close(cmd_file);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
bool skip(struct options const *options, int flag, char c)
|
||
|
{
|
||
|
if (!options->file_types)
|
||
|
return false;
|
||
|
|
||
|
if (flag && strchr(options->file_types, tolower(c)))
|
||
|
return true;
|
||
|
|
||
|
if (!flag && strchr(options->file_types, toupper(c)))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *const *argv)
|
||
|
{
|
||
|
struct options options;
|
||
|
int err;
|
||
|
const char *native_dir = "native";
|
||
|
const char *src_dir = "src";
|
||
|
const char *dst_dir = "dst";
|
||
|
int native_dir_fd = -1;
|
||
|
int src_dir_fd = -1;
|
||
|
int dst_dir_fd = -1;
|
||
|
int block;
|
||
|
int flags;
|
||
|
|
||
|
err = parse_options(argc, argv, &options);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
err = chdir(options.test_dir);
|
||
|
if (err) {
|
||
|
err_msg("Failed to change to %s", options.test_dir);
|
||
|
return -errno;
|
||
|
}
|
||
|
|
||
|
/* Clean up any interrupted previous runs */
|
||
|
while (!umount(dst_dir))
|
||
|
;
|
||
|
|
||
|
err = remove_dir(native_dir) || remove_dir(src_dir) ||
|
||
|
remove_dir(dst_dir);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
err = mkdir(native_dir, 0700);
|
||
|
if (err) {
|
||
|
err_msg("Failed to make directory %s", src_dir);
|
||
|
err = -errno;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = mkdir(src_dir, 0700);
|
||
|
if (err) {
|
||
|
err_msg("Failed to make directory %s", src_dir);
|
||
|
err = -errno;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = mkdir(dst_dir, 0700);
|
||
|
if (err) {
|
||
|
err_msg("Failed to make directory %s", src_dir);
|
||
|
err = -errno;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
err = mount_fs_opt(dst_dir, src_dir, "readahead=0,rlog_pages=0", 0);
|
||
|
if (err) {
|
||
|
err_msg("Failed to mount incfs");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
native_dir_fd = open(native_dir, O_RDONLY | O_CLOEXEC);
|
||
|
src_dir_fd = open(src_dir, O_RDONLY | O_CLOEXEC);
|
||
|
dst_dir_fd = open(dst_dir, O_RDONLY | O_CLOEXEC);
|
||
|
if (native_dir_fd == -1 || src_dir_fd == -1 || dst_dir_fd == -1) {
|
||
|
err_msg("Failed to open native, src or dst dir");
|
||
|
err = -errno;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
printf("%40s", "");
|
||
|
for (block = 0; block < options.blocks; ++block)
|
||
|
printf("%21d", 1 << (block + 12));
|
||
|
printf("\n");
|
||
|
|
||
|
if (!err && !options.no_native)
|
||
|
err = test_native_file(native_dir_fd, &options);
|
||
|
|
||
|
for (flags = 0; flags < LAST_FLAG && !err; ++flags) {
|
||
|
if (skip(&options, flags & SHUFFLE, 's') ||
|
||
|
skip(&options, flags & COMPRESS, 'c') ||
|
||
|
skip(&options, flags & VERIFY, 'v'))
|
||
|
continue;
|
||
|
err = test_incfs_file(dst_dir_fd, &options, flags);
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
close(native_dir_fd);
|
||
|
close(src_dir_fd);
|
||
|
close(dst_dir_fd);
|
||
|
if (!options.no_cleanup) {
|
||
|
umount(dst_dir);
|
||
|
remove_dir(native_dir);
|
||
|
remove_dir(dst_dir);
|
||
|
remove_dir(src_dir);
|
||
|
}
|
||
|
|
||
|
return err;
|
||
|
}
|