generic: test pagecache invalidation after direct write
[xfstests-dev.git] / src / dio-invalidate-cache.c
1 /*
2  * Copyright (c) 2017 Red Hat Inc. All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it would be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write the Free Software Foundation,
15  * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16  */
17
18 /*
19  * Fork N children, each child writes to and reads from its own region of the
20  * same test file, and check if what it reads is what it writes. The test
21  * region is determined by N * blksz. Write and read operation can be either
22  * direct or buffered.
23  */
24
25 #ifndef _GNU_SOURCE
26 #define _GNU_SOURCE
27 #endif
28 #include <sys/file.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 #define DEF_BLKSZ 4096
40
41 int verbose = 0;
42
43 static void usage(const char *prog)
44 {
45         fprintf(stderr, "Usage: %s [-Fhptrwv] [-b blksz] [-n nr_child] [-i iterations] [-o offset] <-f filename>\n", prog);
46         fprintf(stderr, "\t-F\tPreallocate all blocks by writing them before test\n");
47         fprintf(stderr, "\t-p\tPreallocate all blocks using fallocate(2) before test\n");
48         fprintf(stderr, "\t-t\tTruncate test file to largest size before test\n");
49         fprintf(stderr, "\t-r\tDo direct read\n");
50         fprintf(stderr, "\t-w\tDo direct write\n");
51         fprintf(stderr, "\t-v\tBe verbose\n");
52         fprintf(stderr, "\t-h\tshow this help message\n");
53         exit(EXIT_FAILURE);
54 }
55
56 static int cmpbuf(char *b1, char *b2, int bsize)
57 {
58         int i;
59
60         for (i = 0; i < bsize; i++) {
61                 if (b1[i] != b2[i]) {
62                         fprintf(stderr, "cmpbuf: offset %d: Expected: 0x%x,"
63                                 " got 0x%x\n", i, b1[i], b2[i]);
64                         return 1;
65                 }
66         }
67         return 0;
68 }
69
70 static void kill_children(pid_t *pids, int nr_child)
71 {
72         int i;
73         pid_t pid;
74
75         for (i = 0; i < nr_child; i++) {
76                 pid = pids[i];
77                 if (pid == 0)
78                         continue;
79                 kill(pid, SIGTERM);
80         }
81 }
82
83 static int wait_children(pid_t *pids, int nr_child)
84 {
85         int i, status, ret = 0;
86         pid_t pid;
87
88         for (i = 0; i < nr_child; i++) {
89                 pid = pids[i];
90                 if (pid == 0)
91                         continue;
92                 waitpid(pid, &status, 0);
93                 ret += WEXITSTATUS(status);
94         }
95         return ret;
96 }
97
98 static void dumpbuf(char *buf, int size, int blksz)
99 {
100         int i;
101
102         printf("dumping buffer content\n");
103         for (i = 0; i < size; i++) {
104                 if (((i % blksz) == 0) || ((i % 64) == 0))
105                         putchar('\n');
106                 printf("%x", buf[i]);
107         }
108         putchar('\n');
109 }
110
111 static int run_test(const char *filename, int n_child, int blksz, off_t offset,
112                     int nr_iter, int flag_rd, int flag_wr)
113 {
114         char *buf_rd;
115         char *buf_wr;
116         off_t seekoff;
117         int fd_rd, fd_wr;
118         int i, ret;
119         long page_size;
120
121         seekoff = offset + blksz * n_child;
122
123         page_size = sysconf(_SC_PAGESIZE);
124         ret = posix_memalign((void **)&buf_rd, (size_t)page_size,
125                 blksz > page_size ? blksz : (size_t)page_size);
126         if (ret) {
127                 fprintf(stderr, "posix_memalign(buf_rd, %d, %d) failed: %d\n",
128                         blksz, blksz, ret);
129                 exit(EXIT_FAILURE);
130         }
131         memset(buf_rd, 0, blksz);
132         ret = posix_memalign((void **)&buf_wr, (size_t)page_size,
133                 blksz > page_size ? blksz : (size_t)page_size);
134         if (ret) {
135                 fprintf(stderr, "posix_memalign(buf_wr, %d, %d) failed: %d\n",
136                         blksz, blksz, ret);
137                 exit(EXIT_FAILURE);
138         }
139         memset(buf_wr, 0, blksz);
140
141         fd_rd = open(filename, flag_rd);
142         if (fd_rd < 0) {
143                 perror("open readonly for read");
144                 exit(EXIT_FAILURE);
145         }
146
147         fd_wr = open(filename, flag_wr);
148         if (fd_wr < 0) {
149                 perror("open writeonly for direct write");
150                 exit(EXIT_FAILURE);
151         }
152
153 #define log(format, ...)                        \
154         if (verbose) {                          \
155                 printf("[%d:%d] ", n_child, i); \
156                 printf(format, __VA_ARGS__);    \
157         }
158
159
160         /* seek, write, read and verify */
161         for (i = 0; i < nr_iter; i++) {
162                 memset(buf_wr, i + 1, blksz);
163                 log("pwrite(fd_wr, %p, %d, %lu)\n", buf_wr, blksz, seekoff);
164                 if (pwrite(fd_wr, buf_wr, blksz, seekoff) != blksz) {
165                         perror("direct write");
166                         exit(EXIT_FAILURE);
167                 }
168
169                 /* make sure buffer write hits disk before direct read */
170                 if (!(flag_wr & O_DIRECT)) {
171                         if (fsync(fd_wr) < 0) {
172                                 perror("fsync(fd_wr)");
173                                 exit(EXIT_FAILURE);
174                         }
175                 }
176
177                 log("pread(fd_rd, %p, %d, %lu)\n", buf_rd, blksz, seekoff);
178                 if (pread(fd_rd, buf_rd, blksz, seekoff) != blksz) {
179                         perror("buffer read");
180                         exit(EXIT_FAILURE);
181                 }
182                 if (cmpbuf(buf_wr, buf_rd, blksz) != 0) {
183                         fprintf(stderr, "[%d:%d] FAIL - comparison failed, "
184                                 "offset %d\n", n_child, i, (int)seekoff);
185                         if (verbose)
186                                 dumpbuf(buf_rd, blksz, blksz);
187                         exit(EXIT_FAILURE);
188                 }
189         }
190         exit(EXIT_SUCCESS);
191 }
192
193 int main(int argc, char *argv[])
194 {
195         int nr_iter = 1;
196         int nr_child = 1;
197         int blksz = DEF_BLKSZ;
198         int fd, i, ret = 0;
199         int flag_rd = O_RDONLY;
200         int flag_wr = O_WRONLY;
201         int do_trunc = 0;
202         int pre_fill = 0;
203         int pre_alloc = 0;
204         pid_t pid;
205         pid_t *pids;
206         off_t offset = 0;
207         char *filename = NULL;
208
209         while ((i = getopt(argc, argv, "b:i:n:f:Fpo:tvrw")) != -1) {
210                 switch (i) {
211                 case 'b':
212                         if ((blksz = atoi(optarg)) <= 0) {
213                                 fprintf(stderr, "blksz must be > 0\n");
214                                 exit(EXIT_FAILURE);
215                         }
216                         if (blksz % 512 != 0) {
217                                 fprintf(stderr, "blksz must be multiple of 512\n");
218                                 exit(EXIT_FAILURE);
219                         }
220                         break;
221                 case 'i':
222                         if ((nr_iter = atoi(optarg)) <= 0) {
223                                 fprintf(stderr, "iterations must be > 0\n");
224                                 exit(EXIT_FAILURE);
225                         }
226                         break;
227                 case 'n':
228                         if ((nr_child = atoi(optarg)) <= 0) {
229                                 fprintf(stderr, "no of children must be > 0\n");
230                                 exit(EXIT_FAILURE);
231                         }
232                         break;
233                 case 'f':
234                         filename = optarg;
235                         break;
236                 case 'F':
237                         pre_fill = 1;
238                         break;
239                 case 'p':
240                         pre_alloc = 1;
241                         break;
242                 case 'r':
243                         flag_rd |= O_DIRECT;
244                         break;
245                 case 'w':
246                         flag_wr |= O_DIRECT;
247                         break;
248                 case 't':
249                         do_trunc = 1;
250                         break;
251                 case 'o':
252                         if ((offset = atol(optarg)) < 0) {
253                                 fprintf(stderr, "offset must be >= 0\n");
254                                 exit(EXIT_FAILURE);
255                         }
256                         break;
257                 case 'v':
258                         verbose = 1;
259                         break;
260                 case 'h':       /* fall through */
261                 default:
262                         usage(argv[0]);
263                 }
264         }
265
266         if (filename == NULL)
267                 usage(argv[0]);
268         if (pre_fill && pre_alloc) {
269                 fprintf(stderr, "Error: -F and -p are both specified\n");
270                 exit(EXIT_FAILURE);
271         }
272
273         pids = malloc(nr_child * sizeof(pid_t));
274         if (!pids) {
275                 fprintf(stderr, "failed to malloc memory for pids\n");
276                 exit(EXIT_FAILURE);
277         }
278         memset(pids, 0, nr_child * sizeof(pid_t));
279
280         /* create & truncate testfile first */
281         fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0600);
282         if (fd < 0) {
283                 perror("create & truncate testfile");
284                 free(pids);
285                 exit(EXIT_FAILURE);
286         }
287         if (do_trunc && (ftruncate(fd, blksz * nr_child) < 0)) {
288                 perror("ftruncate failed");
289                 free(pids);
290                 exit(EXIT_FAILURE);
291         }
292         if (pre_fill) {
293                 char *buf;
294                 buf = malloc(blksz * nr_child);
295                 memset(buf, 's', blksz * nr_child);
296                 write(fd, buf, blksz * nr_child);
297                 free(buf);
298         }
299         if (pre_alloc) {
300                 fallocate(fd, 0, 0, blksz * nr_child);
301         }
302         fsync(fd);
303         close(fd);
304
305         /* fork workers */
306         for (i = 0; i < nr_child; i++) {
307                 pid = fork();
308                 if (pid < 0) {
309                         perror("fork");
310                         kill_children(pids, nr_child);
311                         free(pids);
312                         exit(EXIT_FAILURE);
313                 } else if (pid == 0) {
314                         /* never returns */
315                         run_test(filename, i, blksz, offset, nr_iter,
316                                  flag_rd, flag_wr);
317                 } else {
318                         pids[i] = pid;
319                 }
320         }
321
322         ret = wait_children(pids, nr_child);
323         free(pids);
324         exit(ret);
325 }