diff options
Diffstat (limited to 'tests/kernel/file-concurrent/test.c')
-rw-r--r-- | tests/kernel/file-concurrent/test.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/tests/kernel/file-concurrent/test.c b/tests/kernel/file-concurrent/test.c new file mode 100644 index 0000000..deb028b --- /dev/null +++ b/tests/kernel/file-concurrent/test.c @@ -0,0 +1,331 @@ +/* + * Author: Colin King <colin.king@canonical.com> + * + * Copyright (C) 2012 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/select.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> + +#define TEST_PASSED (0) +#define TEST_FAILED (1) +#define TEST_ERROR (2) + +#define MAX_FILES (16) /* number of files to create per iteration */ +#define THREADS_PER_CPU (8) /* number of child processes per CPU */ + +#define TIMEOUT (30) /* system hang timeout in seconds */ + +#define TEST_DURATION (60) /* duration of test (seconds) */ +#define MIN_DURATION (1) /* minimum test duration (seconds) */ + +#define CREAT (0) +#define TRUNCATE (1) +#define UNLINK (2) +#define UNKNOWN (3) + +#define DIE_SIGINT (1) /* Kill test threads because of SIGINT */ +#define DIE_COMPLETE (2) /* Kill test threads because end of test duration */ + +static volatile int die = 0; + +static char *options[] = { + "creat()", + "unlink()", + "truncate()", + "<unknown>" +}; + +/* + * Create many threads that try and create create/truncate/unlink aces + */ + +/* + * Run creat/tuncate/unlink as a child and detect any timeouts. This + * is a little heavy handed, but allows us to detect kernel + * hangs on the syscalls if we lock up. + * + */ +int hang_check(int option, const char *filename) +{ + pid_t pid; + struct timeval tv; + int ret; + int status; + int pipefd[2]; + int fd; + fd_set readfds; + + tv.tv_sec = TIMEOUT; + tv.tv_usec = 0; + + if (pipe(pipefd) < 0) { + fprintf(stderr, "pipe error\n"); + return TEST_ERROR; + } + + pid = fork(); + switch (pid) { + case -1: + fprintf(stderr, "failed to fork child\n"); + return TEST_ERROR; + + case 0: + /* Child */ + close(pipefd[0]); + + switch (option) { + case CREAT: + fd = creat(filename, 0700); + if (fd >= 0) + close(fd); + break; + + case TRUNCATE: + /* + * We want to try and force a lock up - + * we don't care about the return since + * the file may not exit, but we assign + * ret to keep gcc happy + */ + ret = truncate(filename, 64 * 1024); + ret = truncate(filename, 128 * 1024); + ret = truncate(filename, 64 * 1024); + ret = truncate(filename, 0); + ret = truncate(filename, 64 * 1024); + break; + + case UNLINK: + unlink(filename); + break; + + default: + break; + } + if (write(pipefd[1], "EXIT", 4) < 0) + fprintf(stderr, "pipe write failed\n"); + + close(pipefd[1]); + _exit(0); + break; + + default: + /* Parent */ + close(pipefd[1]); + + FD_ZERO(&readfds); + FD_SET(pipefd[0], &readfds); + + /* parent, sleep until we get a message from the child */ + ret = select(pipefd[0]+1, &readfds, NULL, NULL, &tv); + close(pipefd[0]); + + switch (ret) { + case 0: + /* Timed out on select, no signal from child! */ + fprintf(stderr, "Timed out after %d seconds doing %s - possible eCryptfs hang\n", + TIMEOUT, options[option > TRUNCATE ? UNKNOWN : option]); + /* Vainly attempt to kill child */ + kill(pid, SIGINT); + return TEST_FAILED; + case -1: + if (errno != EINTR) { + fprintf(stderr, "Unexpected return from select(): %d %s\n", errno, strerror(errno)); + waitpid(pid, &status, 0); + return TEST_ERROR; + } + else { + /* + * We got sent a signal from controlling process to + * tell us to stop, so return TEST_PASSED since we have + * not detected any failures from our child + */ + waitpid(pid, &status, 0); + return TEST_PASSED; + } + default: + /* Child completed the required operation and wrote down the pipe, lets reap */ + waitpid(pid, &status, 0); + return TEST_PASSED; + } + } +} + +int test_files(const char *path, const int max_files) +{ + int i; + char *filename; + size_t len = strlen(path) + 32; + int ret = TEST_PASSED; + + if ((filename = malloc(len)) == NULL) { + fprintf(stderr, "failed to malloc filename\n"); + return TEST_ERROR; + } + + for (;;) { + for (i = 0; i < max_files; i++) { + snprintf(filename, len, "%s/%d", path, i); + if ((ret = hang_check(CREAT, filename)) != TEST_PASSED) { + free(filename); + return ret; + } + if (die) + goto cleanup; + } + + for (i = 0; i < max_files; i++) { + snprintf(filename, len, "%s/%d", path, i); + if ((ret = hang_check(TRUNCATE, filename)) != TEST_PASSED) { + free(filename); + return ret; + } + if (die) + goto cleanup; + } + +cleanup: + for (i = 0; i < max_files; i++) { + snprintf(filename, len, "%s/%d", path, i); + if ((ret = hang_check(UNLINK, filename)) != TEST_PASSED) { + free(filename); + return ret; + } + } + + if (die) + break; + } + + free(filename); + + if (die & DIE_SIGINT) + ret = TEST_ERROR; /* Got aborted */ + + return ret; +} + +void sigint_handler(int dummy) +{ + die = DIE_SIGINT; +} + +void sigusr1_handler(int dummy) +{ + die = DIE_COMPLETE; +} + +int test_exercise(const char *path, const int max_files, const int duration) +{ + int i; + long threads = sysconf(_SC_NPROCESSORS_CONF) * THREADS_PER_CPU; + pid_t *pids; + int ret = TEST_PASSED; + + if ((pids = calloc(threads, sizeof(pid_t))) == NULL) { + fprintf(stderr, "failed to calloc pids\n"); + return TEST_ERROR; + } + + /* Go forth and multiply.. */ + for (i = 0; i < threads; i++) { + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: + fprintf(stderr, "failed to fork child %d of %ld\n", i+1, threads); + break; + case 0: + exit(test_files(path, max_files)); + default: + pids[i] = pid; + break; + } + } + + sleep(duration); + + for (i = 0; i < threads; i++) + kill(pids[i], SIGUSR1); + + for (i = 0; i < threads; i++) { + int status; + waitpid(pids[i], &status, 0); + + if (WEXITSTATUS(status) != TEST_PASSED) + ret = WEXITSTATUS(status); + } + + free(pids); + + return ret; +} + +void show_usage(char *name) +{ + fprintf(stderr, "Syntax: %s [-d duration] pathname\n", name); + fprintf(stderr, "\t-d duration of test (in seconds)\n"); + exit(TEST_ERROR); +} + +int main(int argc, char **argv) +{ + int opt; + int duration = TEST_DURATION; + + while ((opt = getopt(argc, argv, "d:")) != -1) { + switch (opt) { + case 'd': + duration = atoi(optarg); + break; + default: + show_usage(argv[0]); + break; + } + } + + if (optind >= argc) + show_usage(argv[0]); + + if (duration < MIN_DURATION) { + fprintf(stderr, + "Test duration must be %d or more seconds long.\n", + MIN_DURATION); + exit(TEST_ERROR); + } + + if (access(argv[optind], R_OK | W_OK) < 0) { + fprintf(stderr, "Cannot access %s\n", argv[1]); + exit(TEST_ERROR); + } + + signal(SIGINT, sigint_handler); + signal(SIGUSR1, sigusr1_handler); + + exit(test_exercise(argv[optind], MAX_FILES, duration)); +} |