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

source code of linux/tools/testing/selftests/cachestat/test_cachestat.c