summaryrefslogtreecommitdiff
path: root/src/fsync-err.c
blob: bd05dcc0a898ba639a52107913f38f4511783a7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
 * 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;
}