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
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