1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright (c) 2026, Michael Heller.
15 */
16
17 /*
18 * Regression exerciser for a mmap read racing ftruncate (openzfs #18715).
19 *
20 * When a page is faulted in for read after the file has been truncated below
21 * that page, zfs_fillpage() sees io_off >= i_size. On an unfixed build the
22 * unsigned io_len = i_size - io_off underflows and dmu_read() zero-fills far
23 * past the single page, trampling memory (a physical-page sweep). The fix
24 * simply zero-fills the page and returns.
25 *
26 * Several reader processes repeatedly mmap() the file and fault every page,
27 * while a truncator process churns the file size between 0 and <size>. A read
28 * that lands entirely beyond EOF legitimately raises SIGBUS; the reader
29 * tolerates that and keeps going. The test just has to survive the race for
30 * the configured duration -- on an unfixed module the underflow corrupts
31 * memory and takes the run (or the kernel) down.
32 *
33 * usage: mmap_read_truncate <file> <size> <seconds> [nreaders]
34 */
35
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <sys/stat.h>
39 #include <sys/mman.h>
40 #include <sys/wait.h>
41 #include <setjmp.h>
42 #include <signal.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <time.h>
47
48 #define _pdfail(f, l, s) \
49 do { perror("[" f "#" #l "] " s); exit(2); } while (0)
50 #define pdfail(str) _pdfail(__FILE__, __LINE__, str)
51
52 static sigjmp_buf jb;
53 static volatile sig_atomic_t in_probe;
54
55 static void
on_bus(int sig)56 on_bus(int sig)
57 {
58 (void) sig;
59 if (in_probe)
60 siglongjmp(jb, 1);
61 _exit(4);
62 }
63
64 static void
reader_loop(const char * file,off_t sz,time_t end)65 reader_loop(const char *file, off_t sz, time_t end)
66 {
67 long pg = sysconf(_SC_PAGESIZE);
68 int fd = open(file, O_RDONLY);
69 if (fd < 0)
70 pdfail("reader open");
71
72 struct sigaction sa;
73 memset(&sa, 0, sizeof (sa));
74 sa.sa_handler = on_bus;
75 sigaction(SIGBUS, &sa, NULL);
76
77 volatile unsigned long acc = 0;
78 while (time(NULL) < end) {
79 char *p = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
80 if (p == MAP_FAILED)
81 continue;
82 /* volatile: must survive the SIGBUS siglongjmp */
83 for (volatile off_t off = 0; off < sz; off += pg) {
84 in_probe = 1;
85 if (sigsetjmp(jb, 1) == 0)
86 acc += (unsigned char)p[off];
87 in_probe = 0;
88 }
89 (void) munmap(p, sz);
90 }
91 close(fd);
92 (void) acc;
93 _exit(0);
94 }
95
96 int
main(int argc,char ** argv)97 main(int argc, char **argv)
98 {
99 if (argc < 4 || argc > 5) {
100 fprintf(stderr, "usage: mmap_read_truncate "
101 "<file> <size> <secs> [nreaders]\n");
102 exit(2);
103 }
104 const char *file = argv[1];
105 off_t sz = (off_t)strtoull(argv[2], NULL, 0);
106 long secs = strtol(argv[3], NULL, 0);
107 int nreaders = (argc == 5) ? atoi(argv[4]) : 4;
108 if (sz <= 0 || secs <= 0 || nreaders <= 0) {
109 fprintf(stderr, "E: invalid args\n");
110 exit(2);
111 }
112
113 int fd = open(file, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
114 if (fd < 0)
115 pdfail("open");
116 if (ftruncate(fd, sz) < 0)
117 pdfail("ftruncate init");
118 close(fd);
119
120 time_t end = time(NULL) + secs;
121
122 pid_t kids[64];
123 int nk = 0;
124
125 for (int i = 0; i < nreaders; i++) {
126 pid_t c = fork();
127 if (c < 0)
128 pdfail("fork reader");
129 if (c == 0)
130 reader_loop(file, sz, end);
131 kids[nk++] = c;
132 }
133
134 pid_t t = fork();
135 if (t < 0)
136 pdfail("fork truncator");
137 if (t == 0) {
138 int tfd = open(file, O_RDWR);
139 if (tfd < 0)
140 pdfail("truncator open");
141 while (time(NULL) < end) {
142 if (ftruncate(tfd, 0) < 0)
143 _exit(3);
144 if (ftruncate(tfd, sz) < 0)
145 _exit(3);
146 }
147 _exit(0);
148 }
149 kids[nk++] = t;
150
151 int rc = 0;
152 for (int i = 0; i < nk; i++) {
153 int status;
154 if (waitpid(kids[i], &status, 0) < 0)
155 pdfail("waitpid");
156 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
157 fprintf(stderr, "child %d abnormal (status=0x%x)\n",
158 kids[i], status);
159 rc = 1;
160 }
161 }
162 return (rc);
163 }
164