| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2020 Google LLC |
| */ |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <pthread.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 <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_stress" |
| |
| struct options { |
| bool no_cleanup; /* -c */ |
| const char *test_dir; /* -d */ |
| unsigned int rng_seed; /* -g */ |
| int num_reads; /* -n */ |
| int readers; /* -r */ |
| int size; /* -s */ |
| int timeout; /* -t */ |
| }; |
| |
| struct read_data { |
| const char *filename; |
| int dir_fd; |
| size_t filesize; |
| int num_reads; |
| unsigned int rng_seed; |
| }; |
| |
| int cancel_threads; |
| |
| int parse_options(int argc, char *const *argv, struct options *options) |
| { |
| signed char c; |
| |
| /* Set defaults here */ |
| *options = (struct options){ |
| .test_dir = ".", |
| .num_reads = 1000, |
| .readers = 10, |
| .size = 10, |
| }; |
| |
| /* Load options from command line here */ |
| while ((c = getopt(argc, argv, "cd:g:n:r:s:t:")) != -1) { |
| switch (c) { |
| case 'c': |
| options->no_cleanup = true; |
| break; |
| |
| case 'd': |
| options->test_dir = optarg; |
| break; |
| |
| case 'g': |
| options->rng_seed = strtol(optarg, NULL, 10); |
| break; |
| |
| case 'n': |
| options->num_reads = strtol(optarg, NULL, 10); |
| break; |
| |
| case 'r': |
| options->readers = strtol(optarg, NULL, 10); |
| break; |
| |
| case 's': |
| options->size = strtol(optarg, NULL, 10); |
| break; |
| |
| case 't': |
| options->timeout = strtol(optarg, NULL, 10); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void *reader(void *data) |
| { |
| struct read_data *read_data = (struct read_data *)data; |
| int i; |
| int fd = -1; |
| void *buffer = malloc(read_data->filesize); |
| |
| if (!buffer) { |
| err_msg("Failed to alloc read buffer"); |
| goto out; |
| } |
| |
| fd = openat(read_data->dir_fd, read_data->filename, |
| O_RDONLY | O_CLOEXEC); |
| if (fd == -1) { |
| err_msg("Failed to open file"); |
| goto out; |
| } |
| |
| for (i = 0; i < read_data->num_reads && !cancel_threads; ++i) { |
| off_t offset = rnd(read_data->filesize, &read_data->rng_seed); |
| size_t count = |
| rnd(read_data->filesize - offset, &read_data->rng_seed); |
| ssize_t err = pread(fd, buffer, count, offset); |
| |
| if (err != count) |
| err_msg("failed to read with value %lu", err); |
| } |
| |
| out: |
| close(fd); |
| free(read_data); |
| free(buffer); |
| return NULL; |
| } |
| |
| int write_data(int cmd_fd, int dir_fd, const char *name, size_t size) |
| { |
| int fd = openat(dir_fd, name, O_RDWR | O_CLOEXEC); |
| struct incfs_permit_fill permit_fill = { |
| .file_descriptor = fd, |
| }; |
| int error = 0; |
| int i; |
| int block_count = 1 + (size - 1) / INCFS_DATA_FILE_BLOCK_SIZE; |
| |
| if (fd == -1) { |
| err_msg("Could not open file for writing %s", name); |
| return -errno; |
| } |
| |
| 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) { |
| uint8_t data[INCFS_DATA_FILE_BLOCK_SIZE] = {}; |
| size_t block_size = |
| size > i * INCFS_DATA_FILE_BLOCK_SIZE ? |
| INCFS_DATA_FILE_BLOCK_SIZE : |
| size - (i * INCFS_DATA_FILE_BLOCK_SIZE); |
| struct incfs_fill_block fill_block = { |
| .compression = COMPRESSION_NONE, |
| .block_index = i, |
| .data_len = block_size, |
| .data = ptr_to_u64(data), |
| }; |
| struct incfs_fill_blocks fill_blocks = { |
| .count = 1, |
| .fill_blocks = ptr_to_u64(&fill_block), |
| }; |
| int written = ioctl(fd, INCFS_IOC_FILL_BLOCKS, &fill_blocks); |
| |
| if (written != 1) { |
| error = -errno; |
| err_msg("Failed to write block %d in file %s", i, name); |
| break; |
| } |
| } |
| out: |
| close(fd); |
| return error; |
| } |
| |
| int test_files(int src_dir, int dst_dir, struct options const *options) |
| { |
| unsigned int seed = options->rng_seed; |
| int cmd_file = openat(dst_dir, INCFS_PENDING_READS_FILENAME, |
| O_RDONLY | O_CLOEXEC); |
| int err; |
| const char *name = "001"; |
| incfs_uuid_t id; |
| size_t size; |
| int i; |
| pthread_t *threads = NULL; |
| |
| size = 1 << (rnd(options->size, &seed) + 12); |
| size += rnd(size, &seed); |
| |
| if (cmd_file == -1) { |
| err_msg("Could not open command file"); |
| return -errno; |
| } |
| |
| err = emit_file(cmd_file, NULL, name, &id, size, NULL); |
| if (err) { |
| err_msg("Failed to create file %s", name); |
| return err; |
| } |
| |
| threads = malloc(sizeof(pthread_t) * options->readers); |
| if (!threads) { |
| err_msg("Could not allocate memory for threads"); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < options->readers; ++i) { |
| struct read_data *read_data = malloc(sizeof(*read_data)); |
| |
| if (!read_data) { |
| err_msg("Failed to allocate read_data"); |
| err = -ENOMEM; |
| break; |
| } |
| |
| *read_data = (struct read_data){ |
| .filename = name, |
| .dir_fd = dst_dir, |
| .filesize = size, |
| .num_reads = options->num_reads, |
| .rng_seed = seed, |
| }; |
| |
| rnd(0, &seed); |
| |
| err = pthread_create(threads + i, 0, reader, read_data); |
| if (err) { |
| err_msg("Failed to create thread"); |
| free(read_data); |
| break; |
| } |
| } |
| |
| if (err) |
| cancel_threads = 1; |
| else |
| err = write_data(cmd_file, dst_dir, name, size); |
| |
| for (; i > 0; --i) { |
| if (pthread_join(threads[i - 1], NULL)) { |
| err_msg("FATAL: failed to join thread"); |
| exit(-errno); |
| } |
| } |
| |
| free(threads); |
| close(cmd_file); |
| return err; |
| } |
| |
| int main(int argc, char *const *argv) |
| { |
| struct options options; |
| int err; |
| const char *src_dir = "src"; |
| const char *dst_dir = "dst"; |
| int src_dir_fd = -1; |
| int dst_dir_fd = -1; |
| |
| 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; |
| } |
| |
| err = remove_dir(src_dir) || remove_dir(dst_dir); |
| if (err) |
| return err; |
| |
| 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(dst_dir, src_dir, options.timeout); |
| if (err) { |
| err_msg("Failed to mount incfs"); |
| goto cleanup; |
| } |
| |
| src_dir_fd = open(src_dir, O_RDONLY | O_CLOEXEC); |
| dst_dir_fd = open(dst_dir, O_RDONLY | O_CLOEXEC); |
| if (src_dir_fd == -1 || dst_dir_fd == -1) { |
| err_msg("Failed to open src or dst dir"); |
| err = -errno; |
| goto cleanup; |
| } |
| |
| err = test_files(src_dir_fd, dst_dir_fd, &options); |
| |
| cleanup: |
| close(src_dir_fd); |
| close(dst_dir_fd); |
| if (!options.no_cleanup) { |
| umount(dst_dir); |
| remove_dir(dst_dir); |
| remove_dir(src_dir); |
| } |
| |
| return err; |
| } |