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