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 static const int cachestat_nr = 451; 27 28 void print_cachestat(struct cachestat *cs) 29 { 30 ksft_print_msg( 31 "Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n", 32 cs->nr_cache, cs->nr_dirty, cs->nr_writeback, 33 cs->nr_evicted, cs->nr_recently_evicted); 34 } 35 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 */ 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 */ 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(cachestat_nr, 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(cachestat_nr, 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 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(cachestat_nr, 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 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