1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * kselftest suite for mincore().
4 *
5 * Copyright (C) 2020 Collabora, Ltd.
6 */
7
8#define _GNU_SOURCE
9
10#include <stdio.h>
11#include <errno.h>
12#include <unistd.h>
13#include <stdlib.h>
14#include <sys/mman.h>
15#include <string.h>
16#include <fcntl.h>
17
18#include "../kselftest.h"
19#include "../kselftest_harness.h"
20
21/* Default test file size: 4MB */
22#define MB (1UL << 20)
23#define FILE_SIZE (4 * MB)
24
25
26/*
27 * Tests the user interface. This test triggers most of the documented
28 * error conditions in mincore().
29 */
30TEST(basic_interface)
31{
32	int retval;
33	int page_size;
34	unsigned char vec[1];
35	char *addr;
36
37	page_size = sysconf(_SC_PAGESIZE);
38
39	/* Query a 0 byte sized range */
40	retval = mincore(0, 0, vec);
41	EXPECT_EQ(0, retval);
42
43	/* Addresses in the specified range are invalid or unmapped */
44	errno = 0;
45	retval = mincore(NULL, page_size, vec);
46	EXPECT_EQ(-1, retval);
47	EXPECT_EQ(ENOMEM, errno);
48
49	errno = 0;
50	addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
51		MAP_SHARED | MAP_ANONYMOUS, -1, 0);
52	ASSERT_NE(MAP_FAILED, addr) {
53		TH_LOG("mmap error: %s", strerror(errno));
54	}
55
56	/* <addr> argument is not page-aligned */
57	errno = 0;
58	retval = mincore(addr + 1, page_size, vec);
59	EXPECT_EQ(-1, retval);
60	EXPECT_EQ(EINVAL, errno);
61
62	/* <length> argument is too large */
63	errno = 0;
64	retval = mincore(addr, -1, vec);
65	EXPECT_EQ(-1, retval);
66	EXPECT_EQ(ENOMEM, errno);
67
68	/* <vec> argument points to an illegal address */
69	errno = 0;
70	retval = mincore(addr, page_size, NULL);
71	EXPECT_EQ(-1, retval);
72	EXPECT_EQ(EFAULT, errno);
73	munmap(addr, page_size);
74}
75
76
77/*
78 * Test mincore() behavior on a private anonymous page mapping.
79 * Check that the page is not loaded into memory right after the mapping
80 * but after accessing it (on-demand allocation).
81 * Then free the page and check that it's not memory-resident.
82 */
83TEST(check_anonymous_locked_pages)
84{
85	unsigned char vec[1];
86	char *addr;
87	int retval;
88	int page_size;
89
90	page_size = sysconf(_SC_PAGESIZE);
91
92	/* Map one page and check it's not memory-resident */
93	errno = 0;
94	addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
95			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
96	ASSERT_NE(MAP_FAILED, addr) {
97		TH_LOG("mmap error: %s", strerror(errno));
98	}
99	retval = mincore(addr, page_size, vec);
100	ASSERT_EQ(0, retval);
101	ASSERT_EQ(0, vec[0]) {
102		TH_LOG("Page found in memory before use");
103	}
104
105	/* Touch the page and check again. It should now be in memory */
106	addr[0] = 1;
107	mlock(addr, page_size);
108	retval = mincore(addr, page_size, vec);
109	ASSERT_EQ(0, retval);
110	ASSERT_EQ(1, vec[0]) {
111		TH_LOG("Page not found in memory after use");
112	}
113
114	/*
115	 * It shouldn't be memory-resident after unlocking it and
116	 * marking it as unneeded.
117	 */
118	munlock(addr, page_size);
119	madvise(addr, page_size, MADV_DONTNEED);
120	retval = mincore(addr, page_size, vec);
121	ASSERT_EQ(0, retval);
122	ASSERT_EQ(0, vec[0]) {
123		TH_LOG("Page in memory after being zapped");
124	}
125	munmap(addr, page_size);
126}
127
128
129/*
130 * Check mincore() behavior on huge pages.
131 * This test will be skipped if the mapping fails (ie. if there are no
132 * huge pages available).
133 *
134 * Make sure the system has at least one free huge page, check
135 * "HugePages_Free" in /proc/meminfo.
136 * Increment /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages if
137 * needed.
138 */
139TEST(check_huge_pages)
140{
141	unsigned char vec[1];
142	char *addr;
143	int retval;
144	int page_size;
145
146	page_size = sysconf(_SC_PAGESIZE);
147
148	errno = 0;
149	addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
150		MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
151		-1, 0);
152	if (addr == MAP_FAILED) {
153		if (errno == ENOMEM || errno == EINVAL)
154			SKIP(return, "No huge pages available or CONFIG_HUGETLB_PAGE disabled.");
155		else
156			TH_LOG("mmap error: %s", strerror(errno));
157	}
158	retval = mincore(addr, page_size, vec);
159	ASSERT_EQ(0, retval);
160	ASSERT_EQ(0, vec[0]) {
161		TH_LOG("Page found in memory before use");
162	}
163
164	addr[0] = 1;
165	mlock(addr, page_size);
166	retval = mincore(addr, page_size, vec);
167	ASSERT_EQ(0, retval);
168	ASSERT_EQ(1, vec[0]) {
169		TH_LOG("Page not found in memory after use");
170	}
171
172	munlock(addr, page_size);
173	munmap(addr, page_size);
174}
175
176
177/*
178 * Test mincore() behavior on a file-backed page.
179 * No pages should be loaded into memory right after the mapping. Then,
180 * accessing any address in the mapping range should load the page
181 * containing the address and a number of subsequent pages (readahead).
182 *
183 * The actual readahead settings depend on the test environment, so we
184 * can't make a lot of assumptions about that. This test covers the most
185 * general cases.
186 */
187TEST(check_file_mmap)
188{
189	unsigned char *vec;
190	int vec_size;
191	char *addr;
192	int retval;
193	int page_size;
194	int fd;
195	int i;
196	int ra_pages = 0;
197
198	page_size = sysconf(_SC_PAGESIZE);
199	vec_size = FILE_SIZE / page_size;
200	if (FILE_SIZE % page_size)
201		vec_size++;
202
203	vec = calloc(vec_size, sizeof(unsigned char));
204	ASSERT_NE(NULL, vec) {
205		TH_LOG("Can't allocate array");
206	}
207
208	errno = 0;
209	fd = open(".", O_TMPFILE | O_RDWR, 0600);
210	if (fd < 0) {
211		ASSERT_EQ(errno, EOPNOTSUPP) {
212			TH_LOG("Can't create temporary file: %s",
213			       strerror(errno));
214		}
215		SKIP(goto out_free, "O_TMPFILE not supported by filesystem.");
216	}
217	errno = 0;
218	retval = fallocate(fd, 0, 0, FILE_SIZE);
219	if (retval) {
220		ASSERT_EQ(errno, EOPNOTSUPP) {
221			TH_LOG("Error allocating space for the temporary file: %s",
222			       strerror(errno));
223		}
224		SKIP(goto out_close, "fallocate not supported by filesystem.");
225	}
226
227	/*
228	 * Map the whole file, the pages shouldn't be fetched yet.
229	 */
230	errno = 0;
231	addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE,
232			MAP_SHARED, fd, 0);
233	ASSERT_NE(MAP_FAILED, addr) {
234		TH_LOG("mmap error: %s", strerror(errno));
235	}
236	retval = mincore(addr, FILE_SIZE, vec);
237	ASSERT_EQ(0, retval);
238	for (i = 0; i < vec_size; i++) {
239		ASSERT_EQ(0, vec[i]) {
240			TH_LOG("Unexpected page in memory");
241		}
242	}
243
244	/*
245	 * Touch a page in the middle of the mapping. We expect the next
246	 * few pages (the readahead window) to be populated too.
247	 */
248	addr[FILE_SIZE / 2] = 1;
249	retval = mincore(addr, FILE_SIZE, vec);
250	ASSERT_EQ(0, retval);
251	ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) {
252		TH_LOG("Page not found in memory after use");
253	}
254
255	i = FILE_SIZE / 2 / page_size + 1;
256	while (i < vec_size && vec[i]) {
257		ra_pages++;
258		i++;
259	}
260	EXPECT_GT(ra_pages, 0) {
261		TH_LOG("No read-ahead pages found in memory");
262	}
263
264	EXPECT_LT(i, vec_size) {
265		TH_LOG("Read-ahead pages reached the end of the file");
266	}
267	/*
268	 * End of the readahead window. The rest of the pages shouldn't
269	 * be in memory.
270	 */
271	if (i < vec_size) {
272		while (i < vec_size && !vec[i])
273			i++;
274		EXPECT_EQ(vec_size, i) {
275			TH_LOG("Unexpected page in memory beyond readahead window");
276		}
277	}
278
279	munmap(addr, FILE_SIZE);
280out_close:
281	close(fd);
282out_free:
283	free(vec);
284}
285
286
287/*
288 * Test mincore() behavior on a page backed by a tmpfs file.  This test
289 * performs the same steps as the previous one. However, we don't expect
290 * any readahead in this case.
291 */
292TEST(check_tmpfs_mmap)
293{
294	unsigned char *vec;
295	int vec_size;
296	char *addr;
297	int retval;
298	int page_size;
299	int fd;
300	int i;
301	int ra_pages = 0;
302
303	page_size = sysconf(_SC_PAGESIZE);
304	vec_size = FILE_SIZE / page_size;
305	if (FILE_SIZE % page_size)
306		vec_size++;
307
308	vec = calloc(vec_size, sizeof(unsigned char));
309	ASSERT_NE(NULL, vec) {
310		TH_LOG("Can't allocate array");
311	}
312
313	errno = 0;
314	fd = open("/dev/shm", O_TMPFILE | O_RDWR, 0600);
315	ASSERT_NE(-1, fd) {
316		TH_LOG("Can't create temporary file: %s",
317			strerror(errno));
318	}
319	errno = 0;
320	retval = fallocate(fd, 0, 0, FILE_SIZE);
321	ASSERT_EQ(0, retval) {
322		TH_LOG("Error allocating space for the temporary file: %s",
323			strerror(errno));
324	}
325
326	/*
327	 * Map the whole file, the pages shouldn't be fetched yet.
328	 */
329	errno = 0;
330	addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE,
331			MAP_SHARED, fd, 0);
332	ASSERT_NE(MAP_FAILED, addr) {
333		TH_LOG("mmap error: %s", strerror(errno));
334	}
335	retval = mincore(addr, FILE_SIZE, vec);
336	ASSERT_EQ(0, retval);
337	for (i = 0; i < vec_size; i++) {
338		ASSERT_EQ(0, vec[i]) {
339			TH_LOG("Unexpected page in memory");
340		}
341	}
342
343	/*
344	 * Touch a page in the middle of the mapping. We expect only
345	 * that page to be fetched into memory.
346	 */
347	addr[FILE_SIZE / 2] = 1;
348	retval = mincore(addr, FILE_SIZE, vec);
349	ASSERT_EQ(0, retval);
350	ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) {
351		TH_LOG("Page not found in memory after use");
352	}
353
354	i = FILE_SIZE / 2 / page_size + 1;
355	while (i < vec_size && vec[i]) {
356		ra_pages++;
357		i++;
358	}
359	ASSERT_EQ(ra_pages, 0) {
360		TH_LOG("Read-ahead pages found in memory");
361	}
362
363	munmap(addr, FILE_SIZE);
364	close(fd);
365	free(vec);
366}
367
368TEST_HARNESS_MAIN
369