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 "Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\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("Unable to access urandom.\n"); 44 ret = false; 45 goto out; 46 } 47 48 data = malloc(filesize); 49 if (!data) { 50 ksft_print_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("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("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("Unable to create/open file.\n"); 132 ret = KSFT_FAIL; 133 goto out; 134 } else { 135 ksft_print_msg("Create/open %s\n", filename); 136 } 137 138 if (write_random) { 139 if (!write_exactly(fd, filesize)) { 140 ksft_print_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("Cachestat call returned %ld\n", syscall_ret); 149 150 if (syscall_ret) { 151 ksft_print_msg("Cachestat returned non-zero.\n"); 152 ret = KSFT_FAIL; 153 goto out1; 154 155 } else { 156 print_cachestat(&cs); 157 158 if (write_random) { 159 if (cs.nr_cache + cs.nr_evicted != num_pages) { 160 ksft_print_msg( 161 "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("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("Cachestat call (after fsync) returned %ld\n", 177 syscall_ret); 178 179 if (!syscall_ret) { 180 print_cachestat(&cs); 181 182 if (cs.nr_dirty) { 183 ret = KSFT_FAIL; 184 ksft_print_msg( 185 "Number of dirty should be zero after fsync.\n"); 186 } 187 } else { 188 ksft_print_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("Unable to create shmem file.\n"); 219 ret = false; 220 goto out; 221 } 222 223 if (ftruncate(fd, filesize)) { 224 ksft_print_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("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("Cachestat returned non-zero.\n"); 239 ret = false; 240 goto close_fd; 241 } else { 242 print_cachestat(&cs); 243 if (cs.nr_cache + cs.nr_evicted != num_pages) { 244 ksft_print_msg( 245 "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("cachestat syscall not available\n"); 265 266 ksft_set_plan(NR_TESTS); 267 268 if (ret == -1 && errno == EBADF) { 269 ksft_test_result_pass("bad file descriptor recognized\n"); 270 ret = 0; 271 } else { 272 ksft_test_result_fail("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(dev_filename, false, false, false, 280 4, O_RDONLY, 0400) == KSFT_PASS) 281 ksft_test_result_pass("cachestat works with %s\n", dev_filename); 282 else { 283 ksft_test_result_fail("cachestat fails with %s\n", dev_filename); 284 ret = 1; 285 } 286 } 287 288 if (test_cachestat("tmpfilecachestat", true, true, 289 false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS) 290 ksft_test_result_pass("cachestat works with a normal file\n"); 291 else { 292 ksft_test_result_fail("cachestat fails with normal file\n"); 293 ret = 1; 294 } 295 296 switch (test_cachestat("tmpfilecachestat", true, true, 297 true, 4, O_CREAT | O_RDWR, 0600)) { 298 case KSFT_FAIL: 299 ksft_test_result_fail("cachestat fsync fails with normal file\n"); 300 ret = KSFT_FAIL; 301 break; 302 case KSFT_PASS: 303 ksft_test_result_pass("cachestat fsync works with a normal file\n"); 304 break; 305 case KSFT_SKIP: 306 ksft_test_result_skip("tmpfilecachestat is on tmpfs\n"); 307 break; 308 } 309 310 if (test_cachestat_shmem()) 311 ksft_test_result_pass("cachestat works with a shmem file\n"); 312 else { 313 ksft_test_result_fail("cachestat fails with a shmem file\n"); 314 ret = 1; 315 } 316 317 return ret; 318 } 319