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 | |
22 | static const char * const dev_files[] = { |
23 | "/dev/zero" , "/dev/null" , "/dev/urandom" , |
24 | "/proc/version" , "/proc" |
25 | }; |
26 | |
27 | void 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 | |
35 | bool 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; |
88 | out_free_data: |
89 | free(data); |
90 | close_random_fd: |
91 | close(random_fd); |
92 | out: |
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 | */ |
100 | static 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 | */ |
117 | static 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 | |
195 | out1: |
196 | close(fd); |
197 | |
198 | if (create) |
199 | remove(filename); |
200 | out: |
201 | return ret; |
202 | } |
203 | |
204 | bool 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 | |
250 | close_fd: |
251 | shm_unlink(filename); |
252 | out: |
253 | return ret; |
254 | } |
255 | |
256 | int 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 | |