320 lines · c
1// SPDX-License-Identifier: GPL-2.02#define _GNU_SOURCE3#define __SANE_USERSPACE_TYPES__ // Use ll644 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 922 23static const char * const dev_files[] = {24 "/dev/zero", "/dev/null", "/dev/urandom",25 "/proc/version", "/proc"26};27 28void 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 36bool 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;89out_free_data:90 free(data);91close_random_fd:92 close(random_fd);93out: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 */101static 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 it113 * (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 dirty116 * pages.117 */118static 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(__NR_cachestat, 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(__NR_cachestat, 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 196out1:197 close(fd);198 199 if (create)200 remove(filename);201out:202 return ret;203}204 205bool 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(__NR_cachestat, 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 251close_fd:252 shm_unlink(filename);253out:254 return ret;255}256 257int 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