1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 
4 #include <stdio.h>
5 #include <stdbool.h>
6 #include <linux/kernel.h>
7 #include <linux/magic.h>
8 #include <linux/mman.h>
9 #include <sys/mman.h>
10 #include <sys/shm.h>
11 #include <sys/syscall.h>
12 #include <sys/vfs.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 
18 #include "../kselftest.h"
19 
20 #define NR_TESTS	9
21 
22 static const char * const dev_files[] = {
23 	"/dev/zero", "/dev/null", "/dev/urandom",
24 	"/proc/version", "/proc"
25 };
26 
27 void print_cachestat(struct cachestat *cs)
28 {
29 	ksft_print_msg(
30 	"Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n",
31 	cs->nr_cache, cs->nr_dirty, cs->nr_writeback,
32 	cs->nr_evicted, cs->nr_recently_evicted);
33 }
34 
35 bool write_exactly(int fd, size_t filesize)
36 {
37 	int random_fd = open("/dev/urandom", O_RDONLY);
38 	char *cursor, *data;
39 	int remained;
40 	bool ret;
41 
42 	if (random_fd < 0) {
43 		ksft_print_msg("Unable to access urandom.\n");
44 		ret = false;
45 		goto out;
46 	}
47 
48 	data = malloc(filesize);
49 	if (!data) {
50 		ksft_print_msg("Unable to allocate data.\n");
51 		ret = false;
52 		goto close_random_fd;
53 	}
54 
55 	remained = filesize;
56 	cursor = data;
57 
58 	while (remained) {
59 		ssize_t read_len = read(random_fd, cursor, remained);
60 
61 		if (read_len <= 0) {
62 			ksft_print_msg("Unable to read from urandom.\n");
63 			ret = false;
64 			goto out_free_data;
65 		}
66 
67 		remained -= read_len;
68 		cursor += read_len;
69 	}
70 
71 	/* write random data to fd */
72 	remained = filesize;
73 	cursor = data;
74 	while (remained) {
75 		ssize_t write_len = write(fd, cursor, remained);
76 
77 		if (write_len <= 0) {
78 			ksft_print_msg("Unable write random data to file.\n");
79 			ret = false;
80 			goto out_free_data;
81 		}
82 
83 		remained -= write_len;
84 		cursor += write_len;
85 	}
86 
87 	ret = true;
88 out_free_data:
89 	free(data);
90 close_random_fd:
91 	close(random_fd);
92 out:
93 	return ret;
94 }
95 
96 /*
97  * fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync()
98  * test fail below, so we need to check for test file living on a tmpfs.
99  */
100 static bool is_on_tmpfs(int fd)
101 {
102 	struct statfs statfs_buf;
103 
104 	if (fstatfs(fd, &statfs_buf))
105 		return false;
106 
107 	return statfs_buf.f_type == TMPFS_MAGIC;
108 }
109 
110 /*
111  * Open/create the file at filename, (optionally) write random data to it
112  * (exactly num_pages), then test the cachestat syscall on this file.
113  *
114  * If test_fsync == true, fsync the file, then check the number of dirty
115  * pages.
116  */
117 static int test_cachestat(const char *filename, bool write_random, bool create,
118 			  bool test_fsync, unsigned long num_pages,
119 			  int open_flags, mode_t open_mode)
120 {
121 	size_t PS = sysconf(_SC_PAGESIZE);
122 	int filesize = num_pages * PS;
123 	int ret = KSFT_PASS;
124 	long syscall_ret;
125 	struct cachestat cs;
126 	struct cachestat_range cs_range = { 0, filesize };
127 
128 	int fd = open(filename, open_flags, open_mode);
129 
130 	if (fd == -1) {
131 		ksft_print_msg("Unable to create/open file.\n");
132 		ret = KSFT_FAIL;
133 		goto out;
134 	} else {
135 		ksft_print_msg("Create/open %s\n", filename);
136 	}
137 
138 	if (write_random) {
139 		if (!write_exactly(fd, filesize)) {
140 			ksft_print_msg("Unable to access urandom.\n");
141 			ret = KSFT_FAIL;
142 			goto out1;
143 		}
144 	}
145 
146 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
147 
148 	ksft_print_msg("Cachestat call returned %ld\n", syscall_ret);
149 
150 	if (syscall_ret) {
151 		ksft_print_msg("Cachestat returned non-zero.\n");
152 		ret = KSFT_FAIL;
153 		goto out1;
154 
155 	} else {
156 		print_cachestat(&cs);
157 
158 		if (write_random) {
159 			if (cs.nr_cache + cs.nr_evicted != num_pages) {
160 				ksft_print_msg(
161 					"Total number of cached and evicted pages is off.\n");
162 				ret = KSFT_FAIL;
163 			}
164 		}
165 	}
166 
167 	if (test_fsync) {
168 		if (is_on_tmpfs(fd)) {
169 			ret = KSFT_SKIP;
170 		} else if (fsync(fd)) {
171 			ksft_print_msg("fsync fails.\n");
172 			ret = KSFT_FAIL;
173 		} else {
174 			syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
175 
176 			ksft_print_msg("Cachestat call (after fsync) returned %ld\n",
177 				syscall_ret);
178 
179 			if (!syscall_ret) {
180 				print_cachestat(&cs);
181 
182 				if (cs.nr_dirty) {
183 					ret = KSFT_FAIL;
184 					ksft_print_msg(
185 						"Number of dirty should be zero after fsync.\n");
186 				}
187 			} else {
188 				ksft_print_msg("Cachestat (after fsync) returned non-zero.\n");
189 				ret = KSFT_FAIL;
190 				goto out1;
191 			}
192 		}
193 	}
194 
195 out1:
196 	close(fd);
197 
198 	if (create)
199 		remove(filename);
200 out:
201 	return ret;
202 }
203 
204 bool test_cachestat_shmem(void)
205 {
206 	size_t PS = sysconf(_SC_PAGESIZE);
207 	size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */
208 	int syscall_ret;
209 	size_t compute_len = PS * 512;
210 	struct cachestat_range cs_range = { PS, compute_len };
211 	char *filename = "tmpshmcstat";
212 	struct cachestat cs;
213 	bool ret = true;
214 	unsigned long num_pages = compute_len / PS;
215 	int fd = shm_open(filename, O_CREAT | O_RDWR, 0600);
216 
217 	if (fd < 0) {
218 		ksft_print_msg("Unable to create shmem file.\n");
219 		ret = false;
220 		goto out;
221 	}
222 
223 	if (ftruncate(fd, filesize)) {
224 		ksft_print_msg("Unable to truncate shmem file.\n");
225 		ret = false;
226 		goto close_fd;
227 	}
228 
229 	if (!write_exactly(fd, filesize)) {
230 		ksft_print_msg("Unable to write to shmem file.\n");
231 		ret = false;
232 		goto close_fd;
233 	}
234 
235 	syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
236 
237 	if (syscall_ret) {
238 		ksft_print_msg("Cachestat returned non-zero.\n");
239 		ret = false;
240 		goto close_fd;
241 	} else {
242 		print_cachestat(&cs);
243 		if (cs.nr_cache + cs.nr_evicted != num_pages) {
244 			ksft_print_msg(
245 				"Total number of cached and evicted pages is off.\n");
246 			ret = false;
247 		}
248 	}
249 
250 close_fd:
251 	shm_unlink(filename);
252 out:
253 	return ret;
254 }
255 
256 int main(void)
257 {
258 	int ret;
259 
260 	ksft_print_header();
261 
262 	ret = syscall(__NR_cachestat, -1, NULL, NULL, 0);
263 	if (ret == -1 && errno == ENOSYS)
264 		ksft_exit_skip("cachestat syscall not available\n");
265 
266 	ksft_set_plan(NR_TESTS);
267 
268 	if (ret == -1 && errno == EBADF) {
269 		ksft_test_result_pass("bad file descriptor recognized\n");
270 		ret = 0;
271 	} else {
272 		ksft_test_result_fail("bad file descriptor ignored\n");
273 		ret = 1;
274 	}
275 
276 	for (int i = 0; i < 5; i++) {
277 		const char *dev_filename = dev_files[i];
278 
279 		if (test_cachestat(dev_filename, false, false, false,
280 			4, O_RDONLY, 0400) == KSFT_PASS)
281 			ksft_test_result_pass("cachestat works with %s\n", dev_filename);
282 		else {
283 			ksft_test_result_fail("cachestat fails with %s\n", dev_filename);
284 			ret = 1;
285 		}
286 	}
287 
288 	if (test_cachestat("tmpfilecachestat", true, true,
289 		false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS)
290 		ksft_test_result_pass("cachestat works with a normal file\n");
291 	else {
292 		ksft_test_result_fail("cachestat fails with normal file\n");
293 		ret = 1;
294 	}
295 
296 	switch (test_cachestat("tmpfilecachestat", true, true,
297 		true, 4, O_CREAT | O_RDWR, 0600)) {
298 	case KSFT_FAIL:
299 		ksft_test_result_fail("cachestat fsync fails with normal file\n");
300 		ret = KSFT_FAIL;
301 		break;
302 	case KSFT_PASS:
303 		ksft_test_result_pass("cachestat fsync works with a normal file\n");
304 		break;
305 	case KSFT_SKIP:
306 		ksft_test_result_skip("tmpfilecachestat is on tmpfs\n");
307 		break;
308 	}
309 
310 	if (test_cachestat_shmem())
311 		ksft_test_result_pass("cachestat works with a shmem file\n");
312 	else {
313 		ksft_test_result_fail("cachestat fails with a shmem file\n");
314 		ret = 1;
315 	}
316 
317 	return ret;
318 }
319