| /* |
| * fsync-err.c: test whether writeback errors are reported to all open fds |
| * and properly cleared as expected after being seen once on each |
| * |
| * Copyright (c) 2017: Jeff Layton <jlayton@redhat.com> |
| */ |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <stdbool.h> |
| |
| /* |
| * btrfs has a fixed stripewidth of 64k, so we need to write enough data to |
| * ensure that we hit both stripes by default. |
| */ |
| #define DEFAULT_BUFSIZE (65 * 1024) |
| |
| /* default number of fds to open */ |
| #define DEFAULT_NUM_FDS 10 |
| |
| static void usage() |
| { |
| printf("Usage: fsync-err [ -b bufsize ] [ -n num_fds ] [ -s ] -d dmerror path <filename>\n"); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int *fd, ret, i, numfds = DEFAULT_NUM_FDS; |
| char *fname, *buf; |
| char *dmerror_path = NULL; |
| char *cmdbuf; |
| size_t cmdsize, bufsize = DEFAULT_BUFSIZE; |
| bool simple_mode = false; |
| |
| while ((i = getopt(argc, argv, "b:d:n:s")) != -1) { |
| switch (i) { |
| case 'b': |
| bufsize = strtol(optarg, &buf, 0); |
| if (*buf != '\0') { |
| printf("bad string conversion: %s\n", optarg); |
| return 1; |
| } |
| break; |
| case 'd': |
| dmerror_path = optarg; |
| break; |
| case 'n': |
| numfds = strtol(optarg, &buf, 0); |
| if (*buf != '\0') { |
| printf("bad string conversion: %s\n", optarg); |
| return 1; |
| } |
| break; |
| case 's': |
| /* |
| * Many filesystems will continue to throw errors after |
| * fsync has already advanced to the current error, |
| * due to metadata writeback failures or other |
| * issues. Allow those fs' to opt out of more thorough |
| * testing. |
| */ |
| simple_mode = true; |
| } |
| } |
| |
| if (argc < 1) { |
| usage(); |
| return 1; |
| } |
| |
| if (!dmerror_path) { |
| printf("Must specify dmerror path with -d option!\n"); |
| return 1; |
| } |
| |
| /* Remaining argument is filename */ |
| fname = argv[optind]; |
| |
| fd = calloc(numfds, sizeof(*fd)); |
| if (!fd) { |
| printf("malloc failed: %m\n"); |
| return 1; |
| } |
| |
| for (i = 0; i < numfds; ++i) { |
| fd[i] = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
| if (fd[i] < 0) { |
| printf("open of fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| |
| buf = malloc(bufsize); |
| if (!buf) { |
| printf("malloc failed: %m\n"); |
| return 1; |
| } |
| |
| /* fill it with some junk */ |
| memset(buf, 0x7c, bufsize); |
| |
| for (i = 0; i < numfds; ++i) { |
| ret = pwrite(fd[i], buf, bufsize, i * bufsize); |
| if (ret < 0) { |
| printf("First write on fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| |
| for (i = 0; i < numfds; ++i) { |
| ret = fsync(fd[i]); |
| if (ret < 0) { |
| printf("First fsync on fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| |
| /* enough for path + dmerror command string (and then some) */ |
| cmdsize = strlen(dmerror_path) + 64; |
| |
| cmdbuf = malloc(cmdsize); |
| if (!cmdbuf) { |
| printf("malloc failed: %m\n"); |
| return 1; |
| } |
| |
| ret = snprintf(cmdbuf, cmdsize, "%s load_error_table", dmerror_path); |
| if (ret < 0 || ret >= cmdsize) { |
| printf("sprintf failure: %d\n", ret); |
| return 1; |
| } |
| |
| /* flip the device to non-working mode */ |
| ret = system(cmdbuf); |
| if (ret) { |
| if (WIFEXITED(ret)) |
| printf("system: program exited: %d\n", |
| WEXITSTATUS(ret)); |
| else |
| printf("system: 0x%x\n", (int)ret); |
| |
| return 1; |
| } |
| |
| for (i = 0; i < numfds; ++i) { |
| ret = pwrite(fd[i], buf, bufsize, i * bufsize); |
| if (ret < 0) { |
| printf("Second write on fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| |
| for (i = 0; i < numfds; ++i) { |
| ret = fsync(fd[i]); |
| /* Now, we EXPECT the error! */ |
| if (ret >= 0) { |
| printf("Success on second fsync on fd[%d]!\n", i); |
| return 1; |
| } |
| } |
| |
| if (!simple_mode) { |
| for (i = 0; i < numfds; ++i) { |
| ret = fsync(fd[i]); |
| if (ret < 0) { |
| /* |
| * We did a failed write and fsync on each fd |
| * before. Now the error should be clear since |
| * we've not done any writes since then. |
| */ |
| printf("Third fsync on fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| } |
| |
| /* flip the device to working mode */ |
| ret = snprintf(cmdbuf, cmdsize, "%s load_working_table", dmerror_path); |
| if (ret < 0 || ret >= cmdsize) { |
| printf("sprintf failure: %d\n", ret); |
| return 1; |
| } |
| |
| ret = system(cmdbuf); |
| if (ret) { |
| if (WIFEXITED(ret)) |
| printf("system: program exited: %d\n", |
| WEXITSTATUS(ret)); |
| else |
| printf("system: 0x%x\n", (int)ret); |
| |
| return 1; |
| } |
| |
| if (!simple_mode) { |
| for (i = 0; i < numfds; ++i) { |
| ret = fsync(fd[i]); |
| if (ret < 0) { |
| /* The error should still be clear */ |
| printf("fsync after healing device on fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| } |
| |
| /* |
| * reopen each file one at a time to ensure the same inode stays |
| * in core. fsync each one to make sure we see no errors on a fresh |
| * open of the inode. |
| */ |
| for (i = 0; i < numfds; ++i) { |
| ret = close(fd[i]); |
| if (ret < 0) { |
| printf("Close of fd[%d] returned unexpected error: %m\n", i); |
| return 1; |
| } |
| fd[i] = open(fname, O_WRONLY, 0644); |
| if (fd[i] < 0) { |
| printf("Second open of fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| ret = fsync(fd[i]); |
| if (ret < 0) { |
| /* New opens should not return an error */ |
| printf("First fsync after reopen of fd[%d] failed: %m\n", i); |
| return 1; |
| } |
| } |
| |
| printf("Test passed!\n"); |
| return 0; |
| } |