xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/mmap_read_truncate.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
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