log-writes: Add support to output human readable flags
[xfstests-dev.git] / src / holetest.c
1 /*
2  * holetest -- test simultaneous page faults on hole-backed pages
3  * Copyright (C) 2015  Hewlett Packard Enterprise Development LP
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20
21 /*
22  * holetest
23  *
24  * gcc -Wall -pthread -o holetest holetest.c
25  *
26  * This test tool exercises page faults on hole-y portions of an mmapped
27  * file.  The file is created, sized using various methods, mmapped, and
28  * then two threads race to write a marker to different offsets within
29  * each mapped page.  Once the threads have finished marking each page,
30  * the pages are checked for the presence of the markers.
31  *
32  * The file is sized four different ways: explicitly zero-filled by the
33  * test, posix_fallocate(), fallocate(), and ftruncate().  The explicit
34  * zero-fill does not really test simultaneous page faults on hole-backed
35  * pages, but rather serves as control of sorts.
36  *
37  * Usage:
38  *
39  *   holetest [-f] FILENAME FILESIZEinMB
40  *
41  * Where:
42  *
43  *   FILENAME is the name of a non-existent test file to create
44  *
45  *   FILESIZEinMB is the desired size of the test file in MiB
46  *
47  * If the test is successful, FILENAME will be unlinked.  By default,
48  * if the test detects an error in the page markers, then the test exits
49  * immediately and FILENAME is left.  If -f is given, then the test
50  * continues after a marker error and FILENAME is unlinked, but will
51  * still exit with a non-0 status.
52  */
53
54 #include <stdlib.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <inttypes.h>
58 #include <sys/mman.h>
59 #include <fcntl.h>
60 #include <unistd.h>
61 #include <pthread.h>
62 #include <string.h>
63 #include <getopt.h>
64 #include <sys/wait.h>
65
66 #define THREADS 2
67
68 long page_size;
69 long page_offs[THREADS];
70 int use_wr[THREADS];
71 int prefault = 0;
72 int use_fork = 0;
73 int use_private = 0;
74
75 uint64_t get_id(void)
76 {
77         if (!use_fork)
78                 return (uint64_t) pthread_self();
79         return getpid();
80 }
81
82 void prefault_mapping(char *addr, long npages)
83 {
84         long i;
85
86         for (i = 0; i < npages; i++) {
87                 if (addr[i * page_size] != 0) {
88                         fprintf(stderr, "Prefaulting found non-zero value in "
89                                 "page %ld: %d\n", i, (int)addr[i * page_size]);
90                 }
91         }
92 }
93
94 int verify_mapping(char *vastart, long npages, uint64_t *expect)
95 {
96         int errcnt = 0;
97         int i;
98         char *va;
99
100         for (va = vastart; npages > 0; va += page_size, npages--) {
101                 for (i = 0; i < THREADS; i++) {
102                         if (*(uint64_t*)(va + page_offs[i]) != expect[i]) {
103                                 printf("ERROR: thread %d, "
104                                        "offset %08llx, %08llx != %08llx\n", i,
105                                        (unsigned long long) (va + page_offs[i] - vastart),
106                                        (unsigned long long) *(uint64_t*)(va + page_offs[i]),
107                                        (unsigned long long) expect[i]);
108                                 errcnt++;
109                         }
110                 }
111         }
112         return errcnt;
113 }
114
115 void *pt_page_marker(void *args)
116 {
117         void **a = args;
118         char *va = (char *)a[1];
119         long npages = (long)a[2];
120         long i;
121         long pgoff = page_offs[(long)a[3]];
122         uint64_t tid = get_id();
123         long errors = 0;
124
125         if (prefault && use_fork)
126                 prefault_mapping(va, npages);
127
128         /* mark pages */
129         for (i = 0; i < npages; i++)
130                 *(uint64_t *)(va + pgoff + i * page_size) = tid;
131
132         if (use_private && use_fork) {
133                 uint64_t expect[THREADS] = {};
134
135                 expect[(long)a[3]] = tid;
136                 errors = verify_mapping(va, npages, expect);
137         }
138
139         return (void *)errors;
140 }  /* pt_page_marker() */
141
142 void *pt_write_marker(void *args)
143 {
144         void **a = args;
145         int fd = (long)a[0];
146         long npages = (long)a[2];
147         long pgoff = page_offs[(long)a[3]];
148         uint64_t tid = get_id();
149         long i;
150
151         /* mark pages */
152         for (i = 0; i < npages; i++)
153                 pwrite(fd, &tid, sizeof(tid), i * page_size + pgoff);
154
155         return NULL;
156 }
157
158 int test_this(int fd, loff_t sz)
159 {
160         long npages;
161         char *vastart;
162         void *targs[THREADS][4];
163         pthread_t t[THREADS];
164         uint64_t tid[THREADS];
165         int errcnt = 0;
166         int i;
167
168         npages = sz / page_size;
169         printf("INFO: sz = %llu\n", (unsigned long long)sz);
170
171         /* mmap it */
172         vastart = mmap(NULL, sz, PROT_READ | PROT_WRITE,
173                        use_private ? MAP_PRIVATE : MAP_SHARED, fd, 0);
174         if (MAP_FAILED == vastart) {
175                 perror("mmap()");
176                 exit(20);
177         }
178
179         if (prefault && !use_fork)
180                 prefault_mapping(vastart, npages);
181
182         /* prepare the thread args */
183         for (i = 0; i < THREADS; i++) {
184                 targs[i][0] = (void *)(long)fd;
185                 targs[i][1] = vastart;
186                 targs[i][2] = (void *)npages;
187                 targs[i][3] = (void *)(long)i;
188         }
189
190         for (i = 0; i < THREADS; i++) {
191                 if (!use_fork) {
192                         /* start two threads */
193                         if (pthread_create(&t[i], NULL,
194                                    use_wr[i] ? pt_write_marker : pt_page_marker,
195                                    &targs[i])) {
196                                 perror("pthread_create");
197                                 exit(21);
198                         }
199                         tid[i] = (uint64_t)t[i];
200                         printf("INFO: thread %d created\n", i);
201                 } else {
202                         pid_t pid;
203                         /*
204                          * Flush stdout before fork, otherwise some lines get
205                          * duplicated... ?!?!?
206                          */
207                         fflush(stdout);
208                         pid = fork();
209                         if (pid < 0) {
210                                 int j;
211
212                                 perror("fork");
213                                 for (j = 0; j < i; j++)
214                                         waitpid(tid[j], NULL, 0);
215                                 exit(21);
216                         } else if (!pid) {
217                                 /* Child? */
218                                 void *ret;
219
220                                 if (use_wr[i])
221                                         ret = pt_write_marker(&targs[i]);
222                                 else
223                                         ret = pt_page_marker(&targs[i]);
224                                 exit(ret ? 1 : 0);
225                         }
226                         tid[i] = pid;
227                         printf("INFO: process %d created\n", i);
228                 }
229         }
230
231         /* wait for them to finish */
232         for (i = 0; i < THREADS; i++) {
233                 if (!use_fork) {
234                         void *status;
235
236                         pthread_join(t[i], &status);
237                         if (status)
238                                 errcnt++;
239                 } else {
240                         int status;
241
242                         waitpid(tid[i], &status, 0);
243                         if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
244                                 errcnt++;
245                 }
246         }
247
248         /* check markers on each page */
249         /* For private mappings & fork we should see no writes happen */
250         if (use_private && use_fork)
251                 for (i = 0; i < THREADS; i++)
252                         tid[i] = 0;
253         errcnt = verify_mapping(vastart, npages, tid);
254         munmap(vastart, sz);
255
256         if (use_private) {
257                 /* Check that no writes propagated into original file */
258                 for (i = 0; i < THREADS; i++)
259                         tid[i] = 0;
260                 vastart = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
261                 if (vastart == MAP_FAILED) {
262                         perror("mmap()");
263                         exit(20);
264                 }
265                 errcnt += verify_mapping(vastart, npages, tid);
266                 munmap(vastart, sz);
267         }
268
269         printf("INFO: %d error(s) detected\n", errcnt);
270
271
272         return errcnt;
273 }
274
275 int main(int argc, char **argv)
276 {
277         int stoponerror = 1;
278         char *path;
279         loff_t sz;
280         int fd;
281         int errcnt;
282         int toterr = 0;
283         int i, step;
284         char *endch;
285         int opt;
286
287         page_size = getpagesize();
288         step = page_size / THREADS;
289         page_offs[0] = step / 2;
290         for (i = 1; i < THREADS; i++)
291                 page_offs[i] = page_offs[i-1] + step;
292
293         while ((opt = getopt(argc, argv, "fwrFp")) > 0) {
294                 switch (opt) {
295                 case 'f':
296                         /* ignore errors */
297                         stoponerror = 0;
298                         break;
299                 case 'w':
300                         /* use writes instead of mmap for one thread */
301                         use_wr[0] = 1;
302                         break;
303                 case 'r':
304                         /* prefault mmapped area by reading it */
305                         prefault = 1;
306                         break;
307                 case 'F':
308                         /* create processes instead of threads */
309                         use_fork = 1;
310                         break;
311                 case 'p':
312                         /* Use private mappings for testing */
313                         use_private = 1;
314                         break;
315                 default:
316                         fprintf(stderr, "ERROR: Unknown option character.\n");
317                         exit(1);
318                 }
319         }
320
321         if (optind != argc - 2) {
322                 fprintf(stderr, "ERROR: usage: holetest [-fwrFp] "
323                         "FILENAME FILESIZEinMB\n");
324                 exit(1);
325         }
326         if (use_private && use_wr[0]) {
327                 fprintf(stderr, "ERROR: Combinations of writes and private"
328                         "mappings not supported.\n");
329                 exit(1);
330         }
331
332         path = argv[optind];
333         sz = strtol(argv[optind + 1], &endch, 10);
334         if (*endch || sz < 1) {
335                 fprintf(stderr, "ERROR: bad FILESIZEinMB\n");
336                 exit(1);
337         }
338         sz <<= 20;
339
340         /*
341          * we're going to run our test in several different ways:
342          *
343          * 1. explictly zero-filled
344          * 2. posix_fallocated
345          * 3. ftruncated
346          */
347
348
349         /*
350          * explicitly zero-filled
351          */
352         printf("\nINFO: zero-filled test...\n");
353
354         /* create the file */
355         fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
356         if (fd < 0) {
357                 perror(path);
358                 exit(2);
359         }
360
361         /* truncate it to size */
362         if (ftruncate(fd, sz)) {
363                 perror("ftruncate()");
364                 exit(3);
365         }
366
367         /* explicitly zero-fill */
368         {
369                 char*   va = mmap(NULL, sz, PROT_READ | PROT_WRITE,
370                                   MAP_SHARED, fd, 0);
371                 if (MAP_FAILED == va) {
372                         perror("mmap()");
373                         exit(4);
374                 }
375                 memset(va, 0, sz);
376                 munmap(va, sz);
377         }
378
379         /* test it */
380         errcnt = test_this(fd, sz);
381         toterr += errcnt;
382         close(fd);
383         if (stoponerror && errcnt > 0)
384                 exit(5);
385
386         /* cleanup */
387         if (unlink(path)) {
388                 perror("unlink()");
389                 exit(6);
390         }
391
392
393         /*
394          * posix_fallocated
395          */
396         printf("\nINFO: posix_fallocate test...\n");
397
398         /* create the file */
399         fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
400         if (fd < 0) {
401                 perror(path);
402                 exit(7);
403         }
404
405         /* fill it to size */
406         if (posix_fallocate(fd, 0, sz)) {
407                 perror("posix_fallocate()");
408                 exit(8);
409         }
410
411         /* test it */
412         errcnt = test_this(fd, sz);
413         toterr += errcnt;
414         close(fd);
415         if (stoponerror && errcnt > 0)
416                 exit(9);
417
418         /* cleanup */
419         if (unlink(path)) {
420                 perror("unlink()");
421                 exit(10);
422         }
423
424         /*
425          * ftruncated
426          */
427         printf("\nINFO: ftruncate test...\n");
428
429         /* create the file */
430         fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644);
431         if (fd < 0) {
432                 perror(path);
433                 exit(15);
434         }
435
436         /* truncate it to size */
437         if (ftruncate(fd, sz)) {
438                 perror("ftruncate()");
439                 exit(16);
440         }
441
442         /* test it */
443         errcnt = test_this(fd, sz);
444         toterr += errcnt;
445         close(fd);
446         if (stoponerror && errcnt > 0)
447                 exit(17);
448
449         /* cleanup */
450         if (unlink(path)) {
451                 perror("unlink()");
452                 exit(18);
453         }
454
455         /* done */
456         if (toterr > 0)
457                 exit(19);
458         return 0;
459 }