1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdbool.h>
5 #include <fcntl.h>
6 #include <stdint.h>
7 #include <malloc.h>
8 #include <sys/mman.h>
9 #include "../kselftest.h"
10 #include "vm_util.h"
11 
12 #define PAGEMAP_FILE_PATH "/proc/self/pagemap"
13 #define TEST_ITERATIONS 10000
14 
15 static void test_simple(int pagemap_fd, int pagesize)
16 {
17 	int i;
18 	char *map;
19 
20 	map = aligned_alloc(pagesize, pagesize);
21 	if (!map)
22 		ksft_exit_fail_msg("mmap failed\n");
23 
24 	clear_softdirty();
25 
26 	for (i = 0 ; i < TEST_ITERATIONS; i++) {
27 		if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
28 			ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
29 			break;
30 		}
31 
32 		clear_softdirty();
33 		// Write something to the page to get the dirty bit enabled on the page
34 		map[0]++;
35 
36 		if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
37 			ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
38 			break;
39 		}
40 
41 		clear_softdirty();
42 	}
43 	free(map);
44 
45 	ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
46 }
47 
48 static void test_vma_reuse(int pagemap_fd, int pagesize)
49 {
50 	char *map, *map2;
51 
52 	map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
53 	if (map == MAP_FAILED)
54 		ksft_exit_fail_msg("mmap failed");
55 
56 	// The kernel always marks new regions as soft dirty
57 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
58 			 "Test %s dirty bit of allocated page\n", __func__);
59 
60 	clear_softdirty();
61 	munmap(map, pagesize);
62 
63 	map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
64 	if (map2 == MAP_FAILED)
65 		ksft_exit_fail_msg("mmap failed");
66 
67 	// Dirty bit is set for new regions even if they are reused
68 	if (map == map2)
69 		ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
70 				 "Test %s dirty bit of reused address page\n", __func__);
71 	else
72 		ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
73 
74 	munmap(map2, pagesize);
75 }
76 
77 static void test_hugepage(int pagemap_fd, int pagesize)
78 {
79 	char *map;
80 	int i, ret;
81 	size_t hpage_len = read_pmd_pagesize();
82 
83 	if (!hpage_len)
84 		ksft_exit_fail_msg("Reading PMD pagesize failed");
85 
86 	map = memalign(hpage_len, hpage_len);
87 	if (!map)
88 		ksft_exit_fail_msg("memalign failed\n");
89 
90 	ret = madvise(map, hpage_len, MADV_HUGEPAGE);
91 	if (ret)
92 		ksft_exit_fail_msg("madvise failed %d\n", ret);
93 
94 	for (i = 0; i < hpage_len; i++)
95 		map[i] = (char)i;
96 
97 	if (check_huge_anon(map, 1, hpage_len)) {
98 		ksft_test_result_pass("Test %s huge page allocation\n", __func__);
99 
100 		clear_softdirty();
101 		for (i = 0 ; i < TEST_ITERATIONS ; i++) {
102 			if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
103 				ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
104 				break;
105 			}
106 
107 			clear_softdirty();
108 			// Write something to the page to get the dirty bit enabled on the page
109 			map[0]++;
110 
111 			if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
112 				ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
113 				break;
114 			}
115 			clear_softdirty();
116 		}
117 
118 		ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
119 	} else {
120 		// hugepage allocation failed. skip these tests
121 		ksft_test_result_skip("Test %s huge page allocation\n", __func__);
122 		ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
123 	}
124 	free(map);
125 }
126 
127 static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
128 {
129 	const char *type[] = {"file", "anon"};
130 	const char *fname = "./soft-dirty-test-file";
131 	int test_fd;
132 	char *map;
133 
134 	if (anon) {
135 		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
136 			   MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
137 		if (!map)
138 			ksft_exit_fail_msg("anon mmap failed\n");
139 	} else {
140 		test_fd = open(fname, O_RDWR | O_CREAT, 0664);
141 		if (test_fd < 0) {
142 			ksft_test_result_skip("Test %s open() file failed\n", __func__);
143 			return;
144 		}
145 		unlink(fname);
146 		ftruncate(test_fd, pagesize);
147 		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
148 			   MAP_SHARED, test_fd, 0);
149 		if (!map)
150 			ksft_exit_fail_msg("file mmap failed\n");
151 	}
152 
153 	*map = 1;
154 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
155 			 "Test %s-%s dirty bit of new written page\n",
156 			 __func__, type[anon]);
157 	clear_softdirty();
158 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
159 			 "Test %s-%s soft-dirty clear after clear_refs\n",
160 			 __func__, type[anon]);
161 	mprotect(map, pagesize, PROT_READ);
162 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
163 			 "Test %s-%s soft-dirty clear after marking RO\n",
164 			 __func__, type[anon]);
165 	mprotect(map, pagesize, PROT_READ|PROT_WRITE);
166 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
167 			 "Test %s-%s soft-dirty clear after marking RW\n",
168 			 __func__, type[anon]);
169 	*map = 2;
170 	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
171 			 "Test %s-%s soft-dirty after rewritten\n",
172 			 __func__, type[anon]);
173 
174 	munmap(map, pagesize);
175 
176 	if (!anon)
177 		close(test_fd);
178 }
179 
180 static void test_mprotect_anon(int pagemap_fd, int pagesize)
181 {
182 	test_mprotect(pagemap_fd, pagesize, true);
183 }
184 
185 static void test_mprotect_file(int pagemap_fd, int pagesize)
186 {
187 	test_mprotect(pagemap_fd, pagesize, false);
188 }
189 
190 int main(int argc, char **argv)
191 {
192 	int pagemap_fd;
193 	int pagesize;
194 
195 	ksft_print_header();
196 	ksft_set_plan(15);
197 
198 	pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
199 	if (pagemap_fd < 0)
200 		ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
201 
202 	pagesize = getpagesize();
203 
204 	test_simple(pagemap_fd, pagesize);
205 	test_vma_reuse(pagemap_fd, pagesize);
206 	test_hugepage(pagemap_fd, pagesize);
207 	test_mprotect_anon(pagemap_fd, pagesize);
208 	test_mprotect_file(pagemap_fd, pagesize);
209 
210 	close(pagemap_fd);
211 
212 	return ksft_exit_pass();
213 }
214