| #include <sys/time.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| static int test_fd; | |
| static char *buf; | |
| static char *fname; | |
| static int openflags = O_RDWR; | |
| /* | |
| * Just creates a random file, overwriting the file in a random number of loops | |
| * and fsyncing between each loop. | |
| */ | |
| static int test_one(int *max_blocks) | |
| { | |
| int loops = (random() % 20) + 5; | |
| lseek(test_fd, 0, SEEK_SET); | |
| while (loops--) { | |
| int character = (random() % 126) + 33; /* printable character */ | |
| int blocks = (random() % 100) + 1; | |
| if (blocks > *max_blocks) | |
| *max_blocks = blocks; | |
| lseek(test_fd, 0, SEEK_SET); | |
| memset(buf, character, 4096); | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed, test results will be " | |
| "invalid: %d\n", errno); | |
| return 1; | |
| } | |
| while (blocks--) { | |
| if (write(test_fd, buf, 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Preallocate a randomly sized file and then overwrite the entire thing and | |
| * then fsync. | |
| */ | |
| static int test_two(int *max_blocks) | |
| { | |
| int blocks = (random() % 1024) + 1; | |
| int character = (random() % 126) + 33; | |
| *max_blocks = blocks; | |
| if (fallocate(test_fd, 0, 0, blocks * 4096)) { | |
| fprintf(stderr, "Error fallocating %d (%s)\n", errno, | |
| strerror(errno)); | |
| return 1; | |
| } | |
| lseek(test_fd, 0, SEEK_SET); | |
| memset(buf, character, 4096); | |
| while (blocks--) { | |
| if (write(test_fd, buf, 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void drop_all_caches() | |
| { | |
| char value[] = "3\n"; | |
| int fd; | |
| if ((fd = open("/proc/sys/vm/drop_caches", O_WRONLY)) < 0) { | |
| fprintf(stderr, "Error opening drop caches: %d\n", errno); | |
| return; | |
| } | |
| write(fd, value, sizeof(value)-1); | |
| close(fd); | |
| } | |
| /* | |
| * Randomly write inside of a file, either creating a sparse file or prealloc | |
| * the file and randomly write within it, depending on the prealloc flag | |
| */ | |
| static int test_three(int *max_blocks, int prealloc, int rand_fsync, | |
| int do_sync, int drop_caches) | |
| { | |
| int size = (random() % 2048) + 4; | |
| int blocks = size / 2; | |
| int sync_block = blocks / 2; | |
| int rand_sync_interval = (random() % blocks) + 1; | |
| int character = (random() % 126) + 33; | |
| if (prealloc && fallocate(test_fd, 0, 0, size * 4096)) { | |
| fprintf(stderr, "Error fallocating %d (%s)\n", errno, | |
| strerror(errno)); | |
| return 1; | |
| } | |
| if (prealloc) | |
| *max_blocks = size; | |
| memset(buf, character, 4096); | |
| while (blocks--) { | |
| int block = (random() % size); | |
| if ((block + 1) > *max_blocks) | |
| *max_blocks = block + 1; | |
| if (rand_fsync && !(blocks % rand_sync_interval)) { | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed, test results " | |
| "will be invalid: %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| /* Force a transaction commit in between just for fun */ | |
| if (blocks == sync_block && (do_sync || drop_caches)) { | |
| if (do_sync) | |
| sync(); | |
| else | |
| sync_file_range(test_fd, 0, 0, | |
| SYNC_FILE_RANGE_WRITE| | |
| SYNC_FILE_RANGE_WAIT_AFTER); | |
| if (drop_caches) { | |
| close(test_fd); | |
| drop_all_caches(); | |
| test_fd = open(fname, openflags); | |
| if (test_fd < 0) { | |
| test_fd = 0; | |
| fprintf(stderr, "Error re-opening file: %d\n", | |
| errno); | |
| return 1; | |
| } | |
| } | |
| } | |
| if (pwrite(test_fd, buf, 4096, block * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void timeval_subtract(struct timeval *result,struct timeval *x, | |
| struct timeval *y) | |
| { | |
| if (x->tv_usec < y->tv_usec) { | |
| int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; | |
| y->tv_usec -= 1000000 * nsec; | |
| y->tv_sec += nsec; | |
| } | |
| if (x->tv_usec - y->tv_usec > 1000000) { | |
| int nsec = (x->tv_usec - y->tv_usec) / 1000000; | |
| y->tv_usec += 1000000 * nsec; | |
| y->tv_sec -= nsec; | |
| } | |
| result->tv_sec = x->tv_sec - y->tv_sec; | |
| result->tv_usec = x->tv_usec - y->tv_usec; | |
| } | |
| static int test_four(int *max_blocks) | |
| { | |
| size_t size = 2621440; /* 10 gigabytes */ | |
| size_t blocks = size / 2; | |
| size_t sync_block = blocks / 8; /* fsync 8 times */ | |
| int character = (random() % 126) + 33; | |
| struct timeval start, end, diff; | |
| memset(buf, character, 4096); | |
| while (blocks--) { | |
| off_t block = (random() % size); | |
| if ((block + 1) > *max_blocks) | |
| *max_blocks = block + 1; | |
| if ((blocks % sync_block) == 0) { | |
| if (gettimeofday(&start, NULL)) { | |
| fprintf(stderr, "Error getting time: %d\n", | |
| errno); | |
| return 1; | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed, test results " | |
| "will be invalid: %d\n", errno); | |
| return 1; | |
| } | |
| if (gettimeofday(&end, NULL)) { | |
| fprintf(stderr, "Error getting time: %d\n", | |
| errno); | |
| return 1; | |
| } | |
| timeval_subtract(&diff, &end, &start); | |
| printf("Fsync time was %ds and %dus\n", | |
| (int)diff.tv_sec, (int)diff.tv_usec); | |
| } | |
| if (pwrite(test_fd, buf, 4096, block * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int test_five() | |
| { | |
| int character = (random() % 126) + 33; | |
| int runs = (random() % 100) + 1; | |
| int i; | |
| memset(buf, character, 3072); | |
| for (i = 0; i < runs; i++) { | |
| ssize_t write_size = (random() % 3072) + 1; | |
| if (pwrite(test_fd, buf, write_size, 0) < write_size) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| if ((i % 8) == 0) { | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed, test results " | |
| "will be invalid: %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Reproducer for something like this | |
| * | |
| * [data][prealloc][data] | |
| * | |
| * and then in the [prealloc] section we have | |
| * | |
| * [ pre ][pre][ pre ] | |
| * [d][pp][dd][ppp][d][ppp][d] | |
| * | |
| * where each letter represents on block of either data or prealloc. | |
| * | |
| * This explains all the weirdly specific numbers. | |
| */ | |
| static int test_six() | |
| { | |
| int character = (random() % 126) + 33; | |
| int i; | |
| memset(buf, character, 4096); | |
| /* Write on either side of the file, leaving a hole in the middle */ | |
| for (i = 0; i < 10; i++) { | |
| if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| /* | |
| * The test fs I had the prealloc extent was 13 4k blocks long so I'm | |
| * just using that to give myself the best chances of reproducing. | |
| */ | |
| for (i = 23; i < 33; i++) { | |
| if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| if (fallocate(test_fd, 0, 10 * 4096, 4 * 4096)) { | |
| fprintf(stderr, "Error fallocating %d\n", errno); | |
| return 1; | |
| } | |
| if (fallocate(test_fd, 0, 14 * 4096, 5 * 4096)) { | |
| fprintf(stderr, "Error fallocating %d\n", errno); | |
| return 1; | |
| } | |
| if (fallocate(test_fd, 0, 19 * 4096, 4 * 4096)) { | |
| fprintf(stderr, "Error fallocating %d\n", errno); | |
| return 1; | |
| } | |
| if (pwrite(test_fd, buf, 4096, 10 * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| for (i = 13; i < 15; i++) { | |
| if (pwrite(test_fd, buf, 4096, i * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| if (pwrite(test_fd, buf, 4096, 18 * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| if (pwrite(test_fd, buf, 4096, 22 * 4096) < 4096) { | |
| fprintf(stderr, "Short write %d\n", errno); | |
| return 1; | |
| } | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed %d\n", errno); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static void usage() | |
| { | |
| printf("Usage fsync-tester [-s <seed>] [-r] [-d] -t <test-num> <filename>\n"); | |
| printf(" -s seed : seed for teh random map generator (defaults to reading /dev/urandom)\n"); | |
| printf(" -r : don't reboot the box immediately\n"); | |
| printf(" -d : use O_DIRECT\n"); | |
| printf(" -t test : test nr to run, required\n"); | |
| exit(1); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int opt; | |
| int fd; | |
| int max_blocks = 0; | |
| char *endptr; | |
| unsigned int seed = 123; | |
| int reboot = 0; | |
| int direct_io = 0; | |
| long int test = 1; | |
| long int tmp; | |
| int ret = 0; | |
| if (argc < 2) | |
| usage(); | |
| fd = open("/dev/urandom", O_RDONLY); | |
| if (fd >= 0) { | |
| read(fd, &seed, sizeof(seed)); | |
| close(fd); | |
| } | |
| while ((opt = getopt(argc, argv, "s:rdt:")) != -1) { | |
| switch (opt) { | |
| case 's': | |
| tmp = strtol(optarg, &endptr, 10); | |
| if (tmp == LONG_MAX || endptr == optarg) | |
| usage(); | |
| seed = tmp; | |
| break; | |
| case 'r': | |
| reboot = 1; | |
| break; | |
| case 'd': | |
| direct_io = 1; | |
| break; | |
| case 't': | |
| test = strtol(optarg, &endptr, 10); | |
| if (test == LONG_MAX || endptr == optarg) | |
| usage(); | |
| break; | |
| default: | |
| usage(); | |
| } | |
| } | |
| if (optind >= argc) | |
| usage(); | |
| /* | |
| * test 19 is for smaller than blocksize writes to test btrfs's inline | |
| * extent fsyncing, so direct_io doesn't make sense and in fact doesn't | |
| * work for other file systems, so just disable direct io for this test. | |
| */ | |
| if (test == 19) | |
| direct_io = 0; | |
| fname = argv[optind]; | |
| if (!fname) | |
| usage(); | |
| printf("Random seed is %u\n", seed); | |
| srandom(seed); | |
| if (direct_io) { | |
| openflags |= O_DIRECT; | |
| ret = posix_memalign((void **)&buf, getpagesize(), 4096); | |
| if (ret) { | |
| fprintf(stderr, "Error allocating buf: %d\n", ret); | |
| return 1; | |
| } | |
| } else { | |
| buf = malloc(4096); | |
| if (!buf) { | |
| fprintf(stderr, "Error allocating buf: %d\n", errno); | |
| return 1; | |
| } | |
| } | |
| test_fd = open(fname, openflags | O_CREAT | O_TRUNC, 0644); | |
| if (test_fd < 0) { | |
| fprintf(stderr, "Error opening file %d (%s)\n", errno, | |
| strerror(errno)); | |
| return 1; | |
| } | |
| switch (test) { | |
| case 1: | |
| ret = test_one(&max_blocks); | |
| break; | |
| case 2: | |
| ret = test_two(&max_blocks); | |
| break; | |
| case 3: | |
| ret = test_three(&max_blocks, 0, 0, 0, 0); | |
| break; | |
| case 4: | |
| ret = test_three(&max_blocks, 1, 0, 0, 0); | |
| break; | |
| case 5: | |
| ret = test_three(&max_blocks, 0, 1, 0, 0); | |
| break; | |
| case 6: | |
| ret = test_three(&max_blocks, 1, 1, 0, 0); | |
| break; | |
| case 7: | |
| ret = test_three(&max_blocks, 0, 0, 1, 0); | |
| break; | |
| case 8: | |
| ret = test_three(&max_blocks, 1, 0, 1, 0); | |
| break; | |
| case 9: | |
| ret = test_three(&max_blocks, 0, 1, 1, 0); | |
| break; | |
| case 10: | |
| ret = test_three(&max_blocks, 1, 1, 1, 0); | |
| break; | |
| case 11: | |
| ret = test_three(&max_blocks, 0, 0, 0, 1); | |
| break; | |
| case 12: | |
| ret = test_three(&max_blocks, 0, 1, 0, 1); | |
| break; | |
| case 13: | |
| ret = test_three(&max_blocks, 0, 0, 1, 1); | |
| break; | |
| case 14: | |
| ret = test_three(&max_blocks, 0, 1, 1, 1); | |
| break; | |
| case 15: | |
| ret = test_three(&max_blocks, 1, 0, 0, 1); | |
| break; | |
| case 16: | |
| ret = test_three(&max_blocks, 1, 1, 0, 1); | |
| break; | |
| case 17: | |
| ret = test_three(&max_blocks, 1, 0, 1, 1); | |
| break; | |
| case 18: | |
| ret = test_three(&max_blocks, 1, 1, 1, 1); | |
| break; | |
| case 19: | |
| ret = test_five(); | |
| break; | |
| case 20: | |
| ret = test_six(); | |
| break; | |
| case 21: | |
| /* | |
| * This is just a perf test, keep moving it down so it's always | |
| * the last test option. | |
| */ | |
| reboot = 0; | |
| ret = test_four(&max_blocks); | |
| goto out; | |
| default: | |
| usage(); | |
| } | |
| if (ret) | |
| goto out; | |
| if (fsync(test_fd)) { | |
| fprintf(stderr, "Fsync failed, test results will be invalid: " | |
| "%d\n", errno); | |
| return 1; | |
| } | |
| if (reboot) | |
| system("reboot -fn"); | |
| out: | |
| free(buf); | |
| close(test_fd); | |
| return ret; | |
| } |