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 
pagemap_is_populated(int fd,char * start)30 static bool pagemap_is_populated(int fd, char *start)
31 {
32 	uint64_t entry = pagemap_get_entry(fd, start);
33 
34 	/* Present or swapped. */
35 	return entry & 0xc000000000000000ull;
36 }
37 
sense_support(void)38 static void sense_support(void)
39 {
40 	char *addr;
41 	int ret;
42 
43 	addr = mmap(0, pagesize, PROT_READ | PROT_WRITE,
44 		    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
45 	if (!addr)
46 		ksft_exit_fail_msg("mmap failed\n");
47 
48 	ret = madvise(addr, pagesize, MADV_POPULATE_READ);
49 	if (ret)
50 		ksft_exit_skip("MADV_POPULATE_READ is not available\n");
51 
52 	ret = madvise(addr, pagesize, MADV_POPULATE_WRITE);
53 	if (ret)
54 		ksft_exit_skip("MADV_POPULATE_WRITE is not available\n");
55 
56 	munmap(addr, pagesize);
57 }
58 
test_prot_read(void)59 static void test_prot_read(void)
60 {
61 	char *addr;
62 	int ret;
63 
64 	ksft_print_msg("[RUN] %s\n", __func__);
65 
66 	addr = mmap(0, SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
67 	if (addr == MAP_FAILED)
68 		ksft_exit_fail_msg("mmap failed\n");
69 
70 	ret = madvise(addr, SIZE, MADV_POPULATE_READ);
71 	ksft_test_result(!ret, "MADV_POPULATE_READ with PROT_READ\n");
72 
73 	ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
74 	ksft_test_result(ret == -1 && errno == EINVAL,
75 			 "MADV_POPULATE_WRITE with PROT_READ\n");
76 
77 	munmap(addr, SIZE);
78 }
79 
test_prot_write(void)80 static void test_prot_write(void)
81 {
82 	char *addr;
83 	int ret;
84 
85 	ksft_print_msg("[RUN] %s\n", __func__);
86 
87 	addr = mmap(0, SIZE, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
88 	if (addr == MAP_FAILED)
89 		ksft_exit_fail_msg("mmap failed\n");
90 
91 	ret = madvise(addr, SIZE, MADV_POPULATE_READ);
92 	ksft_test_result(ret == -1 && errno == EINVAL,
93 			 "MADV_POPULATE_READ with PROT_WRITE\n");
94 
95 	ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
96 	ksft_test_result(!ret, "MADV_POPULATE_WRITE with PROT_WRITE\n");
97 
98 	munmap(addr, SIZE);
99 }
100 
test_holes(void)101 static void test_holes(void)
102 {
103 	char *addr;
104 	int ret;
105 
106 	ksft_print_msg("[RUN] %s\n", __func__);
107 
108 	addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
109 		    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
110 	if (addr == MAP_FAILED)
111 		ksft_exit_fail_msg("mmap failed\n");
112 	ret = munmap(addr + pagesize, pagesize);
113 	if (ret)
114 		ksft_exit_fail_msg("munmap failed\n");
115 
116 	/* Hole in the middle */
117 	ret = madvise(addr, SIZE, MADV_POPULATE_READ);
118 	ksft_test_result(ret == -1 && errno == ENOMEM,
119 			 "MADV_POPULATE_READ with holes in the middle\n");
120 	ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
121 	ksft_test_result(ret == -1 && errno == ENOMEM,
122 			 "MADV_POPULATE_WRITE with holes in the middle\n");
123 
124 	/* Hole at end */
125 	ret = madvise(addr, 2 * pagesize, MADV_POPULATE_READ);
126 	ksft_test_result(ret == -1 && errno == ENOMEM,
127 			 "MADV_POPULATE_READ with holes at the end\n");
128 	ret = madvise(addr, 2 * pagesize, MADV_POPULATE_WRITE);
129 	ksft_test_result(ret == -1 && errno == ENOMEM,
130 			 "MADV_POPULATE_WRITE with holes at the end\n");
131 
132 	/* Hole at beginning */
133 	ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_READ);
134 	ksft_test_result(ret == -1 && errno == ENOMEM,
135 			 "MADV_POPULATE_READ with holes at the beginning\n");
136 	ret = madvise(addr + pagesize, pagesize, MADV_POPULATE_WRITE);
137 	ksft_test_result(ret == -1 && errno == ENOMEM,
138 			 "MADV_POPULATE_WRITE with holes at the beginning\n");
139 
140 	munmap(addr, SIZE);
141 }
142 
range_is_populated(char * start,ssize_t size)143 static bool range_is_populated(char *start, ssize_t size)
144 {
145 	int fd = open("/proc/self/pagemap", O_RDONLY);
146 	bool ret = true;
147 
148 	if (fd < 0)
149 		ksft_exit_fail_msg("opening pagemap failed\n");
150 	for (; size > 0 && ret; size -= pagesize, start += pagesize)
151 		if (!pagemap_is_populated(fd, start))
152 			ret = false;
153 	close(fd);
154 	return ret;
155 }
156 
range_is_not_populated(char * start,ssize_t size)157 static bool range_is_not_populated(char *start, ssize_t size)
158 {
159 	int fd = open("/proc/self/pagemap", O_RDONLY);
160 	bool ret = true;
161 
162 	if (fd < 0)
163 		ksft_exit_fail_msg("opening pagemap failed\n");
164 	for (; size > 0 && ret; size -= pagesize, start += pagesize)
165 		if (pagemap_is_populated(fd, start))
166 			ret = false;
167 	close(fd);
168 	return ret;
169 }
170 
test_populate_read(void)171 static void test_populate_read(void)
172 {
173 	char *addr;
174 	int ret;
175 
176 	ksft_print_msg("[RUN] %s\n", __func__);
177 
178 	addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
179 		    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
180 	if (addr == MAP_FAILED)
181 		ksft_exit_fail_msg("mmap failed\n");
182 	ksft_test_result(range_is_not_populated(addr, SIZE),
183 			 "range initially not populated\n");
184 
185 	ret = madvise(addr, SIZE, MADV_POPULATE_READ);
186 	ksft_test_result(!ret, "MADV_POPULATE_READ\n");
187 	ksft_test_result(range_is_populated(addr, SIZE),
188 			 "range is populated\n");
189 
190 	munmap(addr, SIZE);
191 }
192 
test_populate_write(void)193 static void test_populate_write(void)
194 {
195 	char *addr;
196 	int ret;
197 
198 	ksft_print_msg("[RUN] %s\n", __func__);
199 
200 	addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
201 		    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
202 	if (addr == MAP_FAILED)
203 		ksft_exit_fail_msg("mmap failed\n");
204 	ksft_test_result(range_is_not_populated(addr, SIZE),
205 			 "range initially not populated\n");
206 
207 	ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
208 	ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
209 	ksft_test_result(range_is_populated(addr, SIZE),
210 			 "range is populated\n");
211 
212 	munmap(addr, SIZE);
213 }
214 
range_is_softdirty(char * start,ssize_t size)215 static bool range_is_softdirty(char *start, ssize_t size)
216 {
217 	int fd = open("/proc/self/pagemap", O_RDONLY);
218 	bool ret = true;
219 
220 	if (fd < 0)
221 		ksft_exit_fail_msg("opening pagemap failed\n");
222 	for (; size > 0 && ret; size -= pagesize, start += pagesize)
223 		if (!pagemap_is_softdirty(fd, start))
224 			ret = false;
225 	close(fd);
226 	return ret;
227 }
228 
range_is_not_softdirty(char * start,ssize_t size)229 static bool range_is_not_softdirty(char *start, ssize_t size)
230 {
231 	int fd = open("/proc/self/pagemap", O_RDONLY);
232 	bool ret = true;
233 
234 	if (fd < 0)
235 		ksft_exit_fail_msg("opening pagemap failed\n");
236 	for (; size > 0 && ret; size -= pagesize, start += pagesize)
237 		if (pagemap_is_softdirty(fd, start))
238 			ret = false;
239 	close(fd);
240 	return ret;
241 }
242 
test_softdirty(void)243 static void test_softdirty(void)
244 {
245 	char *addr;
246 	int ret;
247 
248 	ksft_print_msg("[RUN] %s\n", __func__);
249 
250 	addr = mmap(0, SIZE, PROT_READ | PROT_WRITE,
251 		    MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
252 	if (addr == MAP_FAILED)
253 		ksft_exit_fail_msg("mmap failed\n");
254 
255 	/* Clear any softdirty bits. */
256 	clear_softdirty();
257 	ksft_test_result(range_is_not_softdirty(addr, SIZE),
258 			 "range is not softdirty\n");
259 
260 	/* Populating READ should set softdirty. */
261 	ret = madvise(addr, SIZE, MADV_POPULATE_READ);
262 	ksft_test_result(!ret, "MADV_POPULATE_READ\n");
263 	ksft_test_result(range_is_not_softdirty(addr, SIZE),
264 			 "range is not softdirty\n");
265 
266 	/* Populating WRITE should set softdirty. */
267 	ret = madvise(addr, SIZE, MADV_POPULATE_WRITE);
268 	ksft_test_result(!ret, "MADV_POPULATE_WRITE\n");
269 	ksft_test_result(range_is_softdirty(addr, SIZE),
270 			 "range is softdirty\n");
271 
272 	munmap(addr, SIZE);
273 }
274 
main(int argc,char ** argv)275 int main(int argc, char **argv)
276 {
277 	int err;
278 
279 	pagesize = getpagesize();
280 
281 	ksft_print_header();
282 	ksft_set_plan(21);
283 
284 	sense_support();
285 	test_prot_read();
286 	test_prot_write();
287 	test_holes();
288 	test_populate_read();
289 	test_populate_write();
290 	test_softdirty();
291 
292 	err = ksft_get_fail_cnt();
293 	if (err)
294 		ksft_exit_fail_msg("%d out of %d tests failed\n",
295 				   err, ksft_test_num());
296 	return ksft_exit_pass();
297 }
298