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 | |
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(msg: "mmap failed\n" ); |
39 | |
40 | ret = madvise(addr, pagesize, MADV_POPULATE_READ); |
41 | if (ret) |
42 | ksft_exit_skip(msg: "MADV_POPULATE_READ is not available\n" ); |
43 | |
44 | ret = madvise(addr, pagesize, MADV_POPULATE_WRITE); |
45 | if (ret) |
46 | ksft_exit_skip(msg: "MADV_POPULATE_WRITE is not available\n" ); |
47 | |
48 | munmap(addr, pagesize); |
49 | } |
50 | |
51 | static void test_prot_read(void) |
52 | { |
53 | char *addr; |
54 | int ret; |
55 | |
56 | ksft_print_msg(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(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 | |
72 | static void test_prot_write(void) |
73 | { |
74 | char *addr; |
75 | int ret; |
76 | |
77 | ksft_print_msg(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(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 | |
93 | static void test_holes(void) |
94 | { |
95 | char *addr; |
96 | int ret; |
97 | |
98 | ksft_print_msg(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(msg: "mmap failed\n" ); |
104 | ret = munmap(addr + pagesize, pagesize); |
105 | if (ret) |
106 | ksft_exit_fail_msg(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 | |
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(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 | |
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(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 | |
163 | static void test_populate_read(void) |
164 | { |
165 | char *addr; |
166 | int ret; |
167 | |
168 | ksft_print_msg(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(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 | |
185 | static void test_populate_write(void) |
186 | { |
187 | char *addr; |
188 | int ret; |
189 | |
190 | ksft_print_msg(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(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 | |
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(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 | |
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(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 | |
235 | static void test_softdirty(void) |
236 | { |
237 | char *addr; |
238 | int ret; |
239 | |
240 | ksft_print_msg(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(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 | |
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 | |
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(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(msg: "%d out of %d tests failed\n" , |
309 | err, ksft_test_num()); |
310 | return ksft_exit_pass(); |
311 | } |
312 | |