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