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 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 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 */ 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 */ 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 } 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 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 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