mirror of
https://github.com/linux-apfs/apfstests.git
synced 2026-05-01 15:01:44 -07:00
702421f528
I'm working on a set of kernel patches to change how writeback errors are handled and reported in the kernel. Instead of reporting a writeback error to only the first fsync caller on the file, it has the the kernel report them once on every file description that was open at the time of the error. This patch adds a test for this new behavior. Basically, open many fds to the same file, turn on dm_error, write to each of the fds, and then fsync them all to ensure that they all get an error back. To do that, I'm adding a new tools/dmerror script that the C program can use to load the error table from the script. It's also suitable for setting up, frobbing and tearing down a dmerror device for by-hand testing. For now, only ext2/3/4 and xfs are whitelisted on this test, since those filesystems are included in the initial patchset. We can add to that as we convert filesystems, and eventually make it a more general test. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Eryu Guan <eguan@redhat.com> Signed-off-by: Eryu Guan <eguan@redhat.com>
224 lines
4.7 KiB
C
224 lines
4.7 KiB
C
/*
|
|
* 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>
|
|
|
|
/*
|
|
* 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 ] -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;
|
|
|
|
while ((i = getopt(argc, argv, "b:d:n:")) != -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;
|
|
}
|
|
}
|
|
|
|
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 = write(fd[i], buf, 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 = write(fd[i], buf, 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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 | O_CREAT | O_TRUNC, 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;
|
|
}
|