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