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