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