162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#define _GNU_SOURCE 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <stdio.h> 562306a36Sopenharmony_ci#include <stdbool.h> 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/magic.h> 862306a36Sopenharmony_ci#include <linux/mman.h> 962306a36Sopenharmony_ci#include <sys/mman.h> 1062306a36Sopenharmony_ci#include <sys/shm.h> 1162306a36Sopenharmony_ci#include <sys/syscall.h> 1262306a36Sopenharmony_ci#include <sys/vfs.h> 1362306a36Sopenharmony_ci#include <unistd.h> 1462306a36Sopenharmony_ci#include <string.h> 1562306a36Sopenharmony_ci#include <fcntl.h> 1662306a36Sopenharmony_ci#include <errno.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include "../kselftest.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define NR_TESTS 9 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic const char * const dev_files[] = { 2362306a36Sopenharmony_ci "/dev/zero", "/dev/null", "/dev/urandom", 2462306a36Sopenharmony_ci "/proc/version", "/proc" 2562306a36Sopenharmony_ci}; 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_civoid print_cachestat(struct cachestat *cs) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci ksft_print_msg( 3062306a36Sopenharmony_ci "Using cachestat: Cached: %lu, Dirty: %lu, Writeback: %lu, Evicted: %lu, Recently Evicted: %lu\n", 3162306a36Sopenharmony_ci cs->nr_cache, cs->nr_dirty, cs->nr_writeback, 3262306a36Sopenharmony_ci cs->nr_evicted, cs->nr_recently_evicted); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cibool write_exactly(int fd, size_t filesize) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci int random_fd = open("/dev/urandom", O_RDONLY); 3862306a36Sopenharmony_ci char *cursor, *data; 3962306a36Sopenharmony_ci int remained; 4062306a36Sopenharmony_ci bool ret; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (random_fd < 0) { 4362306a36Sopenharmony_ci ksft_print_msg("Unable to access urandom.\n"); 4462306a36Sopenharmony_ci ret = false; 4562306a36Sopenharmony_ci goto out; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci data = malloc(filesize); 4962306a36Sopenharmony_ci if (!data) { 5062306a36Sopenharmony_ci ksft_print_msg("Unable to allocate data.\n"); 5162306a36Sopenharmony_ci ret = false; 5262306a36Sopenharmony_ci goto close_random_fd; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci remained = filesize; 5662306a36Sopenharmony_ci cursor = data; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci while (remained) { 5962306a36Sopenharmony_ci ssize_t read_len = read(random_fd, cursor, remained); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (read_len <= 0) { 6262306a36Sopenharmony_ci ksft_print_msg("Unable to read from urandom.\n"); 6362306a36Sopenharmony_ci ret = false; 6462306a36Sopenharmony_ci goto out_free_data; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci remained -= read_len; 6862306a36Sopenharmony_ci cursor += read_len; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* write random data to fd */ 7262306a36Sopenharmony_ci remained = filesize; 7362306a36Sopenharmony_ci cursor = data; 7462306a36Sopenharmony_ci while (remained) { 7562306a36Sopenharmony_ci ssize_t write_len = write(fd, cursor, remained); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (write_len <= 0) { 7862306a36Sopenharmony_ci ksft_print_msg("Unable write random data to file.\n"); 7962306a36Sopenharmony_ci ret = false; 8062306a36Sopenharmony_ci goto out_free_data; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci remained -= write_len; 8462306a36Sopenharmony_ci cursor += write_len; 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci ret = true; 8862306a36Sopenharmony_ciout_free_data: 8962306a36Sopenharmony_ci free(data); 9062306a36Sopenharmony_ciclose_random_fd: 9162306a36Sopenharmony_ci close(random_fd); 9262306a36Sopenharmony_ciout: 9362306a36Sopenharmony_ci return ret; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/* 9762306a36Sopenharmony_ci * fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync() 9862306a36Sopenharmony_ci * test fail below, so we need to check for test file living on a tmpfs. 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_cistatic bool is_on_tmpfs(int fd) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct statfs statfs_buf; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (fstatfs(fd, &statfs_buf)) 10562306a36Sopenharmony_ci return false; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci return statfs_buf.f_type == TMPFS_MAGIC; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci/* 11162306a36Sopenharmony_ci * Open/create the file at filename, (optionally) write random data to it 11262306a36Sopenharmony_ci * (exactly num_pages), then test the cachestat syscall on this file. 11362306a36Sopenharmony_ci * 11462306a36Sopenharmony_ci * If test_fsync == true, fsync the file, then check the number of dirty 11562306a36Sopenharmony_ci * pages. 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_cistatic int test_cachestat(const char *filename, bool write_random, bool create, 11862306a36Sopenharmony_ci bool test_fsync, unsigned long num_pages, 11962306a36Sopenharmony_ci int open_flags, mode_t open_mode) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci size_t PS = sysconf(_SC_PAGESIZE); 12262306a36Sopenharmony_ci int filesize = num_pages * PS; 12362306a36Sopenharmony_ci int ret = KSFT_PASS; 12462306a36Sopenharmony_ci long syscall_ret; 12562306a36Sopenharmony_ci struct cachestat cs; 12662306a36Sopenharmony_ci struct cachestat_range cs_range = { 0, filesize }; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci int fd = open(filename, open_flags, open_mode); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (fd == -1) { 13162306a36Sopenharmony_ci ksft_print_msg("Unable to create/open file.\n"); 13262306a36Sopenharmony_ci ret = KSFT_FAIL; 13362306a36Sopenharmony_ci goto out; 13462306a36Sopenharmony_ci } else { 13562306a36Sopenharmony_ci ksft_print_msg("Create/open %s\n", filename); 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (write_random) { 13962306a36Sopenharmony_ci if (!write_exactly(fd, filesize)) { 14062306a36Sopenharmony_ci ksft_print_msg("Unable to access urandom.\n"); 14162306a36Sopenharmony_ci ret = KSFT_FAIL; 14262306a36Sopenharmony_ci goto out1; 14362306a36Sopenharmony_ci } 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ksft_print_msg("Cachestat call returned %ld\n", syscall_ret); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (syscall_ret) { 15162306a36Sopenharmony_ci ksft_print_msg("Cachestat returned non-zero.\n"); 15262306a36Sopenharmony_ci ret = KSFT_FAIL; 15362306a36Sopenharmony_ci goto out1; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci } else { 15662306a36Sopenharmony_ci print_cachestat(&cs); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (write_random) { 15962306a36Sopenharmony_ci if (cs.nr_cache + cs.nr_evicted != num_pages) { 16062306a36Sopenharmony_ci ksft_print_msg( 16162306a36Sopenharmony_ci "Total number of cached and evicted pages is off.\n"); 16262306a36Sopenharmony_ci ret = KSFT_FAIL; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (test_fsync) { 16862306a36Sopenharmony_ci if (is_on_tmpfs(fd)) { 16962306a36Sopenharmony_ci ret = KSFT_SKIP; 17062306a36Sopenharmony_ci } else if (fsync(fd)) { 17162306a36Sopenharmony_ci ksft_print_msg("fsync fails.\n"); 17262306a36Sopenharmony_ci ret = KSFT_FAIL; 17362306a36Sopenharmony_ci } else { 17462306a36Sopenharmony_ci syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci ksft_print_msg("Cachestat call (after fsync) returned %ld\n", 17762306a36Sopenharmony_ci syscall_ret); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (!syscall_ret) { 18062306a36Sopenharmony_ci print_cachestat(&cs); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if (cs.nr_dirty) { 18362306a36Sopenharmony_ci ret = KSFT_FAIL; 18462306a36Sopenharmony_ci ksft_print_msg( 18562306a36Sopenharmony_ci "Number of dirty should be zero after fsync.\n"); 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci } else { 18862306a36Sopenharmony_ci ksft_print_msg("Cachestat (after fsync) returned non-zero.\n"); 18962306a36Sopenharmony_ci ret = KSFT_FAIL; 19062306a36Sopenharmony_ci goto out1; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ciout1: 19662306a36Sopenharmony_ci close(fd); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (create) 19962306a36Sopenharmony_ci remove(filename); 20062306a36Sopenharmony_ciout: 20162306a36Sopenharmony_ci return ret; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cibool test_cachestat_shmem(void) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci size_t PS = sysconf(_SC_PAGESIZE); 20762306a36Sopenharmony_ci size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */ 20862306a36Sopenharmony_ci int syscall_ret; 20962306a36Sopenharmony_ci size_t compute_len = PS * 512; 21062306a36Sopenharmony_ci struct cachestat_range cs_range = { PS, compute_len }; 21162306a36Sopenharmony_ci char *filename = "tmpshmcstat"; 21262306a36Sopenharmony_ci struct cachestat cs; 21362306a36Sopenharmony_ci bool ret = true; 21462306a36Sopenharmony_ci unsigned long num_pages = compute_len / PS; 21562306a36Sopenharmony_ci int fd = shm_open(filename, O_CREAT | O_RDWR, 0600); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci if (fd < 0) { 21862306a36Sopenharmony_ci ksft_print_msg("Unable to create shmem file.\n"); 21962306a36Sopenharmony_ci ret = false; 22062306a36Sopenharmony_ci goto out; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci if (ftruncate(fd, filesize)) { 22462306a36Sopenharmony_ci ksft_print_msg("Unable to truncate shmem file.\n"); 22562306a36Sopenharmony_ci ret = false; 22662306a36Sopenharmony_ci goto close_fd; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (!write_exactly(fd, filesize)) { 23062306a36Sopenharmony_ci ksft_print_msg("Unable to write to shmem file.\n"); 23162306a36Sopenharmony_ci ret = false; 23262306a36Sopenharmony_ci goto close_fd; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (syscall_ret) { 23862306a36Sopenharmony_ci ksft_print_msg("Cachestat returned non-zero.\n"); 23962306a36Sopenharmony_ci ret = false; 24062306a36Sopenharmony_ci goto close_fd; 24162306a36Sopenharmony_ci } else { 24262306a36Sopenharmony_ci print_cachestat(&cs); 24362306a36Sopenharmony_ci if (cs.nr_cache + cs.nr_evicted != num_pages) { 24462306a36Sopenharmony_ci ksft_print_msg( 24562306a36Sopenharmony_ci "Total number of cached and evicted pages is off.\n"); 24662306a36Sopenharmony_ci ret = false; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ciclose_fd: 25162306a36Sopenharmony_ci shm_unlink(filename); 25262306a36Sopenharmony_ciout: 25362306a36Sopenharmony_ci return ret; 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ciint main(void) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci int ret; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci ksft_print_header(); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ret = syscall(__NR_cachestat, -1, NULL, NULL, 0); 26362306a36Sopenharmony_ci if (ret == -1 && errno == ENOSYS) 26462306a36Sopenharmony_ci ksft_exit_skip("cachestat syscall not available\n"); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci ksft_set_plan(NR_TESTS); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (ret == -1 && errno == EBADF) { 26962306a36Sopenharmony_ci ksft_test_result_pass("bad file descriptor recognized\n"); 27062306a36Sopenharmony_ci ret = 0; 27162306a36Sopenharmony_ci } else { 27262306a36Sopenharmony_ci ksft_test_result_fail("bad file descriptor ignored\n"); 27362306a36Sopenharmony_ci ret = 1; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci for (int i = 0; i < 5; i++) { 27762306a36Sopenharmony_ci const char *dev_filename = dev_files[i]; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (test_cachestat(dev_filename, false, false, false, 28062306a36Sopenharmony_ci 4, O_RDONLY, 0400) == KSFT_PASS) 28162306a36Sopenharmony_ci ksft_test_result_pass("cachestat works with %s\n", dev_filename); 28262306a36Sopenharmony_ci else { 28362306a36Sopenharmony_ci ksft_test_result_fail("cachestat fails with %s\n", dev_filename); 28462306a36Sopenharmony_ci ret = 1; 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (test_cachestat("tmpfilecachestat", true, true, 28962306a36Sopenharmony_ci false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS) 29062306a36Sopenharmony_ci ksft_test_result_pass("cachestat works with a normal file\n"); 29162306a36Sopenharmony_ci else { 29262306a36Sopenharmony_ci ksft_test_result_fail("cachestat fails with normal file\n"); 29362306a36Sopenharmony_ci ret = 1; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci switch (test_cachestat("tmpfilecachestat", true, true, 29762306a36Sopenharmony_ci true, 4, O_CREAT | O_RDWR, 0600)) { 29862306a36Sopenharmony_ci case KSFT_FAIL: 29962306a36Sopenharmony_ci ksft_test_result_fail("cachestat fsync fails with normal file\n"); 30062306a36Sopenharmony_ci ret = KSFT_FAIL; 30162306a36Sopenharmony_ci break; 30262306a36Sopenharmony_ci case KSFT_PASS: 30362306a36Sopenharmony_ci ksft_test_result_pass("cachestat fsync works with a normal file\n"); 30462306a36Sopenharmony_ci break; 30562306a36Sopenharmony_ci case KSFT_SKIP: 30662306a36Sopenharmony_ci ksft_test_result_skip("tmpfilecachestat is on tmpfs\n"); 30762306a36Sopenharmony_ci break; 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (test_cachestat_shmem()) 31162306a36Sopenharmony_ci ksft_test_result_pass("cachestat works with a shmem file\n"); 31262306a36Sopenharmony_ci else { 31362306a36Sopenharmony_ci ksft_test_result_fail("cachestat fails with a shmem file\n"); 31462306a36Sopenharmony_ci ret = 1; 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci return ret; 31862306a36Sopenharmony_ci} 319