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