1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * MADV_POPULATE_READ and MADV_POPULATE_WRITE tests
4 *
5 * Copyright 2021, Red Hat, Inc.
6 *
7 * Author(s): David Hildenbrand <david@redhat.com>
8 */
9 #define _GNU_SOURCE
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdbool.h>
13 #include <stdint.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <linux/mman.h>
18 #include <sys/mman.h>
19
20 #include "../kselftest.h"
21 #include "vm_util.h"
22
23 /*
24 * For now, we're using 2 MiB of private anonymous memory for all tests.
25 */
26 #define SIZE (2 * 1024 * 1024)
27
28 static size_t pagesize;
29
sense_support(void)30 static void sense_support(void)
31 {
32 char *addr;
33 int ret;
34
35 addr = mmap(0, pagesize, PROT_READ | PROT_WRITE,
36 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
37 if (!addr)
38 ksft_exit_fail_msg("mmap failed\n");
39
40 ret = madvise(addr, pagesize, MADV_POPULATE_READ);
41 if (ret)
42 ksft_exit_skip("MADV_POPULATE_READ is not available\n");
43
44 ret = madvise(addr, pagesize, MADV_POPULATE_WRITE);
45 if (ret)
46 ksft_exit_skip("MADV_POPULATE_WRITE is not available\n");
47
48 munmap(addr, pagesize);
49 }
50
test_prot_read(void)51 static void test_prot_read(void)
52 {
53 char *addr;
54 int ret;
55
56 ksft_print_msg("[RUN] %s\n", __func__);
57
58 addr = mmap(0, SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
59 if (addr == MAP_FAILED)
60 ksft_exit_fail_msg("mmap failed\n");
61
62 ret = madvise(addr, SIZE, MADV_POPULATE_READ);
63 ksft_test_result(!ret, "MADV_POPULATE_READ with PROT_READ\n");
64
65 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
66 ksft_test_result(ret == -1 && errno == EINVAL,
67 "MADV_POPULATE_WRITE with PROT_READ\n");
68
69 munmap(addr, SIZE);
70 }
71
test_prot_write(void)72 static void test_prot_write(void)
73 {
74 char *addr;
75 int ret;
76
77 ksft_print_msg("[RUN] %s\n", __func__);
78
79 addr = mmap(0, SIZE, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
80 if (addr == MAP_FAILED)
81 ksft_exit_fail_msg("mmap failed\n");
82
83 ret = madvise(addr, SIZE, MADV_POPULATE_READ);
84 ksft_test_result(ret == -1 && errno == EINVAL,
85 "MADV_POPULATE_READ with PROT_WRITE\n");
86
87 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
88 ksft_test_result(!ret, "MADV_POPULATE_WRITE with PROT_WRITE\n");
89
90 munmap(addr, SIZE);
91 }
92
test_holes(void)93 static void test_holes(void)
94 {
95 char *addr;
96 int ret;
97
98 ksft_print_msg("[RUN] %s\n", __func__);
99
100 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
101 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
102 if (addr == MAP_FAILED)
103 ksft_exit_fail_msg("mmap failed\n");
104 ret = munmap(addr + pagesize, pagesize);
105 if (ret)
106 ksft_exit_fail_msg("munmap failed\n");
107
108 /* Hole in the middle */
109 ret = madvise(addr, SIZE, MADV_POPULATE_READ);
110 ksft_test_result(ret == -1 && errno == ENOMEM,
111 "MADV_POPULATE_READ with holes in the middle\n");
112 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
113 ksft_test_result(ret == -1 && errno == ENOMEM,
114 "MADV_POPULATE_WRITE with holes in the middle\n");
115
116 /* Hole at end */
117 ret = madvise(addr, 2 * pagesize, MADV_POPULATE_READ);
118 ksft_test_result(ret == -1 && errno == ENOMEM,
119 "MADV_POPULATE_READ with holes at the end\n");
120 ret = madvise(addr, 2 * pagesize, MADV_POPULATE_WRITE);
121 ksft_test_result(ret == -1 && errno == ENOMEM,
122 "MADV_POPULATE_WRITE with holes at the end\n");
123
124 /* Hole at beginning */
125 ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_READ);
126 ksft_test_result(ret == -1 && errno == ENOMEM,
127 "MADV_POPULATE_READ with holes at the beginning\n");
128 ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_WRITE);
129 ksft_test_result(ret == -1 && errno == ENOMEM,
130 "MADV_POPULATE_WRITE with holes at the beginning\n");
131
132 munmap(addr, SIZE);
133 }
134
range_is_populated(char * start,ssize_t size)135 static bool range_is_populated(char *start, ssize_t size)
136 {
137 int fd = open("/proc/self/pagemap", O_RDONLY);
138 bool ret = true;
139
140 if (fd < 0)
141 ksft_exit_fail_msg("opening pagemap failed\n");
142 for (; size > 0 && ret; size -= pagesize, start += pagesize)
143 if (!pagemap_is_populated(fd, start))
144 ret = false;
145 close(fd);
146 return ret;
147 }
148
range_is_not_populated(char * start,ssize_t size)149 static bool range_is_not_populated(char *start, ssize_t size)
150 {
151 int fd = open("/proc/self/pagemap", O_RDONLY);
152 bool ret = true;
153
154 if (fd < 0)
155 ksft_exit_fail_msg("opening pagemap failed\n");
156 for (; size > 0 && ret; size -= pagesize, start += pagesize)
157 if (pagemap_is_populated(fd, start))
158 ret = false;
159 close(fd);
160 return ret;
161 }
162
test_populate_read(void)163 static void test_populate_read(void)
164 {
165 char *addr;
166 int ret;
167
168 ksft_print_msg("[RUN] %s\n", __func__);
169
170 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
171 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
172 if (addr == MAP_FAILED)
173 ksft_exit_fail_msg("mmap failed\n");
174 ksft_test_result(range_is_not_populated(addr, SIZE),
175 "range initially not populated\n");
176
177 ret = madvise(addr, SIZE, MADV_POPULATE_READ);
178 ksft_test_result(!ret, "MADV_POPULATE_READ\n");
179 ksft_test_result(range_is_populated(addr, SIZE),
180 "range is populated\n");
181
182 munmap(addr, SIZE);
183 }
184
test_populate_write(void)185 static void test_populate_write(void)
186 {
187 char *addr;
188 int ret;
189
190 ksft_print_msg("[RUN] %s\n", __func__);
191
192 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
193 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
194 if (addr == MAP_FAILED)
195 ksft_exit_fail_msg("mmap failed\n");
196 ksft_test_result(range_is_not_populated(addr, SIZE),
197 "range initially not populated\n");
198
199 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
200 ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
201 ksft_test_result(range_is_populated(addr, SIZE),
202 "range is populated\n");
203
204 munmap(addr, SIZE);
205 }
206
range_is_softdirty(char * start,ssize_t size)207 static bool range_is_softdirty(char *start, ssize_t size)
208 {
209 int fd = open("/proc/self/pagemap", O_RDONLY);
210 bool ret = true;
211
212 if (fd < 0)
213 ksft_exit_fail_msg("opening pagemap failed\n");
214 for (; size > 0 && ret; size -= pagesize, start += pagesize)
215 if (!pagemap_is_softdirty(fd, start))
216 ret = false;
217 close(fd);
218 return ret;
219 }
220
range_is_not_softdirty(char * start,ssize_t size)221 static bool range_is_not_softdirty(char *start, ssize_t size)
222 {
223 int fd = open("/proc/self/pagemap", O_RDONLY);
224 bool ret = true;
225
226 if (fd < 0)
227 ksft_exit_fail_msg("opening pagemap failed\n");
228 for (; size > 0 && ret; size -= pagesize, start += pagesize)
229 if (pagemap_is_softdirty(fd, start))
230 ret = false;
231 close(fd);
232 return ret;
233 }
234
test_softdirty(void)235 static void test_softdirty(void)
236 {
237 char *addr;
238 int ret;
239
240 ksft_print_msg("[RUN] %s\n", __func__);
241
242 addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
243 MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
244 if (addr == MAP_FAILED)
245 ksft_exit_fail_msg("mmap failed\n");
246
247 /* Clear any softdirty bits. */
248 clear_softdirty();
249 ksft_test_result(range_is_not_softdirty(addr, SIZE),
250 "range is not softdirty\n");
251
252 /* Populating READ should set softdirty. */
253 ret = madvise(addr, SIZE, MADV_POPULATE_READ);
254 ksft_test_result(!ret, "MADV_POPULATE_READ\n");
255 ksft_test_result(range_is_not_softdirty(addr, SIZE),
256 "range is not softdirty\n");
257
258 /* Populating WRITE should set softdirty. */
259 ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
260 ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
261 ksft_test_result(range_is_softdirty(addr, SIZE),
262 "range is softdirty\n");
263
264 munmap(addr, SIZE);
265 }
266
system_has_softdirty(void)267 static int system_has_softdirty(void)
268 {
269 /*
270 * There is no way to check if the kernel supports soft-dirty, other
271 * than by writing to a page and seeing if the bit was set. But the
272 * tests are intended to check that the bit gets set when it should, so
273 * doing that check would turn a potentially legitimate fail into a
274 * skip. Fortunately, we know for sure that arm64 does not support
275 * soft-dirty. So for now, let's just use the arch as a corse guide.
276 */
277 #if defined(__aarch64__)
278 return 0;
279 #else
280 return 1;
281 #endif
282 }
283
main(int argc,char ** argv)284 int main(int argc, char **argv)
285 {
286 int nr_tests = 16;
287 int err;
288
289 pagesize = getpagesize();
290
291 if (system_has_softdirty())
292 nr_tests += 5;
293
294 ksft_print_header();
295 ksft_set_plan(nr_tests);
296
297 sense_support();
298 test_prot_read();
299 test_prot_write();
300 test_holes();
301 test_populate_read();
302 test_populate_write();
303 if (system_has_softdirty())
304 test_softdirty();
305
306 err = ksft_get_fail_cnt();
307 if (err)
308 ksft_exit_fail_msg("%d out of %d tests failed\n",
309 err, ksft_test_num());
310 ksft_exit_pass();
311 }
312