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: %llu, Dirty: %llu, Writeback: %llu, Evicted: %llu, Recently Evicted: %llu\n",
32 cs->nr_cache, cs->nr_dirty, cs->nr_writeback,
33 cs->nr_evicted, cs->nr_recently_evicted);
34 }
35
36 enum file_type {
37 FILE_MMAP,
38 FILE_SHMEM
39 };
40
write_exactly(int fd,size_t filesize)41 bool write_exactly(int fd, size_t filesize)
42 {
43 int random_fd = open("/dev/urandom", O_RDONLY);
44 char *cursor, *data;
45 int remained;
46 bool ret;
47
48 if (random_fd < 0) {
49 ksft_print_msg("Unable to access urandom.\n");
50 ret = false;
51 goto out;
52 }
53
54 data = malloc(filesize);
55 if (!data) {
56 ksft_print_msg("Unable to allocate data.\n");
57 ret = false;
58 goto close_random_fd;
59 }
60
61 remained = filesize;
62 cursor = data;
63
64 while (remained) {
65 ssize_t read_len = read(random_fd, cursor, remained);
66
67 if (read_len <= 0) {
68 ksft_print_msg("Unable to read from urandom.\n");
69 ret = false;
70 goto out_free_data;
71 }
72
73 remained -= read_len;
74 cursor += read_len;
75 }
76
77 /* write random data to fd */
78 remained = filesize;
79 cursor = data;
80 while (remained) {
81 ssize_t write_len = write(fd, cursor, remained);
82
83 if (write_len <= 0) {
84 ksft_print_msg("Unable write random data to file.\n");
85 ret = false;
86 goto out_free_data;
87 }
88
89 remained -= write_len;
90 cursor += write_len;
91 }
92
93 ret = true;
94 out_free_data:
95 free(data);
96 close_random_fd:
97 close(random_fd);
98 out:
99 return ret;
100 }
101
102 /*
103 * fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync()
104 * test fail below, so we need to check for test file living on a tmpfs.
105 */
is_on_tmpfs(int fd)106 static bool is_on_tmpfs(int fd)
107 {
108 struct statfs statfs_buf;
109
110 if (fstatfs(fd, &statfs_buf))
111 return false;
112
113 return statfs_buf.f_type == TMPFS_MAGIC;
114 }
115
116 /*
117 * Open/create the file at filename, (optionally) write random data to it
118 * (exactly num_pages), then test the cachestat syscall on this file.
119 *
120 * If test_fsync == true, fsync the file, then check the number of dirty
121 * pages.
122 */
test_cachestat(const char * filename,bool write_random,bool create,bool test_fsync,unsigned long num_pages,int open_flags,mode_t open_mode)123 static int test_cachestat(const char *filename, bool write_random, bool create,
124 bool test_fsync, unsigned long num_pages,
125 int open_flags, mode_t open_mode)
126 {
127 size_t PS = sysconf(_SC_PAGESIZE);
128 int filesize = num_pages * PS;
129 int ret = KSFT_PASS;
130 long syscall_ret;
131 struct cachestat cs;
132 struct cachestat_range cs_range = { 0, filesize };
133
134 int fd = open(filename, open_flags, open_mode);
135
136 if (fd == -1) {
137 ksft_print_msg("Unable to create/open file.\n");
138 ret = KSFT_FAIL;
139 goto out;
140 } else {
141 ksft_print_msg("Create/open %s\n", filename);
142 }
143
144 if (write_random) {
145 if (!write_exactly(fd, filesize)) {
146 ksft_print_msg("Unable to access urandom.\n");
147 ret = KSFT_FAIL;
148 goto out1;
149 }
150 }
151
152 syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
153
154 ksft_print_msg("Cachestat call returned %ld\n", syscall_ret);
155
156 if (syscall_ret) {
157 ksft_print_msg("Cachestat returned non-zero.\n");
158 ret = KSFT_FAIL;
159 goto out1;
160
161 } else {
162 print_cachestat(&cs);
163
164 if (write_random) {
165 if (cs.nr_cache + cs.nr_evicted != num_pages) {
166 ksft_print_msg(
167 "Total number of cached and evicted pages is off.\n");
168 ret = KSFT_FAIL;
169 }
170 }
171 }
172
173 if (test_fsync) {
174 if (is_on_tmpfs(fd)) {
175 ret = KSFT_SKIP;
176 } else if (fsync(fd)) {
177 ksft_print_msg("fsync fails.\n");
178 ret = KSFT_FAIL;
179 } else {
180 syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
181
182 ksft_print_msg("Cachestat call (after fsync) returned %ld\n",
183 syscall_ret);
184
185 if (!syscall_ret) {
186 print_cachestat(&cs);
187
188 if (cs.nr_dirty) {
189 ret = KSFT_FAIL;
190 ksft_print_msg(
191 "Number of dirty should be zero after fsync.\n");
192 }
193 } else {
194 ksft_print_msg("Cachestat (after fsync) returned non-zero.\n");
195 ret = KSFT_FAIL;
196 goto out1;
197 }
198 }
199 }
200
201 out1:
202 close(fd);
203
204 if (create)
205 remove(filename);
206 out:
207 return ret;
208 }
file_type_str(enum file_type type)209 const char *file_type_str(enum file_type type)
210 {
211 switch (type) {
212 case FILE_SHMEM:
213 return "shmem";
214 case FILE_MMAP:
215 return "mmap";
216 default:
217 return "unknown";
218 }
219 }
220
221
run_cachestat_test(enum file_type type)222 bool run_cachestat_test(enum file_type type)
223 {
224 size_t PS = sysconf(_SC_PAGESIZE);
225 size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */
226 int syscall_ret;
227 size_t compute_len = PS * 512;
228 struct cachestat_range cs_range = { PS, compute_len };
229 char *filename = "tmpshmcstat";
230 struct cachestat cs;
231 bool ret = true;
232 int fd;
233 unsigned long num_pages = compute_len / PS;
234 if (type == FILE_SHMEM)
235 fd = shm_open(filename, O_CREAT | O_RDWR, 0600);
236 else
237 fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
238
239 if (fd < 0) {
240 ksft_print_msg("Unable to create %s file.\n",
241 file_type_str(type));
242 ret = false;
243 goto out;
244 }
245
246 if (ftruncate(fd, filesize)) {
247 ksft_print_msg("Unable to truncate %s file.\n",file_type_str(type));
248 ret = false;
249 goto close_fd;
250 }
251 switch (type) {
252 case FILE_SHMEM:
253 if (!write_exactly(fd, filesize)) {
254 ksft_print_msg("Unable to write to file.\n");
255 ret = false;
256 goto close_fd;
257 }
258 break;
259 case FILE_MMAP:
260 char *map = mmap(NULL, filesize, PROT_READ | PROT_WRITE,
261 MAP_SHARED, fd, 0);
262
263 if (map == MAP_FAILED) {
264 ksft_print_msg("mmap failed.\n");
265 ret = false;
266 goto close_fd;
267 }
268 for (int i = 0; i < filesize; i++)
269 map[i] = 'A';
270 break;
271 default:
272 ksft_print_msg("Unsupported file type.\n");
273 ret = false;
274 goto close_fd;
275 }
276 syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);
277
278 if (syscall_ret) {
279 ksft_print_msg("Cachestat returned non-zero.\n");
280 ret = false;
281 goto close_fd;
282 } else {
283 print_cachestat(&cs);
284 if (cs.nr_cache + cs.nr_evicted != num_pages) {
285 ksft_print_msg(
286 "Total number of cached and evicted pages is off.\n");
287 ret = false;
288 }
289 }
290
291 close_fd:
292 shm_unlink(filename);
293 out:
294 return ret;
295 }
296
main(void)297 int main(void)
298 {
299 int ret;
300
301 ksft_print_header();
302
303 ret = syscall(__NR_cachestat, -1, NULL, NULL, 0);
304 if (ret == -1 && errno == ENOSYS)
305 ksft_exit_skip("cachestat syscall not available\n");
306
307 ksft_set_plan(NR_TESTS);
308
309 if (ret == -1 && errno == EBADF) {
310 ksft_test_result_pass("bad file descriptor recognized\n");
311 ret = 0;
312 } else {
313 ksft_test_result_fail("bad file descriptor ignored\n");
314 ret = 1;
315 }
316
317 for (int i = 0; i < 5; i++) {
318 const char *dev_filename = dev_files[i];
319
320 if (test_cachestat(dev_filename, false, false, false,
321 4, O_RDONLY, 0400) == KSFT_PASS)
322 ksft_test_result_pass("cachestat works with %s\n", dev_filename);
323 else {
324 ksft_test_result_fail("cachestat fails with %s\n", dev_filename);
325 ret = 1;
326 }
327 }
328
329 if (test_cachestat("tmpfilecachestat", true, true,
330 false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS)
331 ksft_test_result_pass("cachestat works with a normal file\n");
332 else {
333 ksft_test_result_fail("cachestat fails with normal file\n");
334 ret = 1;
335 }
336
337 switch (test_cachestat("tmpfilecachestat", true, true,
338 true, 4, O_CREAT | O_RDWR, 0600)) {
339 case KSFT_FAIL:
340 ksft_test_result_fail("cachestat fsync fails with normal file\n");
341 ret = KSFT_FAIL;
342 break;
343 case KSFT_PASS:
344 ksft_test_result_pass("cachestat fsync works with a normal file\n");
345 break;
346 case KSFT_SKIP:
347 ksft_test_result_skip("tmpfilecachestat is on tmpfs\n");
348 break;
349 }
350
351 if (run_cachestat_test(FILE_SHMEM))
352 ksft_test_result_pass("cachestat works with a shmem file\n");
353 else {
354 ksft_test_result_fail("cachestat fails with a shmem file\n");
355 ret = 1;
356 }
357
358 if (run_cachestat_test(FILE_MMAP))
359 ksft_test_result_pass("cachestat works with a mmap file\n");
360 else {
361 ksft_test_result_fail("cachestat fails with a mmap file\n");
362 ret = 1;
363 }
364 return ret;
365 }
366