blob: bd05dcc0a898ba639a52107913f38f4511783a7e [file] [log] [blame]
/*
* 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;
}