Path: blob/master/tools/testing/selftests/cachestat/test_cachestat.c
26285 views
// SPDX-License-Identifier: GPL-2.01#define _GNU_SOURCE2#define __SANE_USERSPACE_TYPES__ // Use ll6434#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>1718#include "../kselftest.h"1920#define NR_TESTS 92122static const char * const dev_files[] = {23"/dev/zero", "/dev/null", "/dev/urandom",24"/proc/version", "/proc"25};2627void print_cachestat(struct cachestat *cs)28{29ksft_print_msg(30"Using cachestat: Cached: %llu, Dirty: %llu, Writeback: %llu, Evicted: %llu, Recently Evicted: %llu\n",31cs->nr_cache, cs->nr_dirty, cs->nr_writeback,32cs->nr_evicted, cs->nr_recently_evicted);33}3435enum file_type {36FILE_MMAP,37FILE_SHMEM38};3940bool write_exactly(int fd, size_t filesize)41{42int random_fd = open("/dev/urandom", O_RDONLY);43char *cursor, *data;44int remained;45bool ret;4647if (random_fd < 0) {48ksft_print_msg("Unable to access urandom.\n");49ret = false;50goto out;51}5253data = malloc(filesize);54if (!data) {55ksft_print_msg("Unable to allocate data.\n");56ret = false;57goto close_random_fd;58}5960remained = filesize;61cursor = data;6263while (remained) {64ssize_t read_len = read(random_fd, cursor, remained);6566if (read_len <= 0) {67ksft_print_msg("Unable to read from urandom.\n");68ret = false;69goto out_free_data;70}7172remained -= read_len;73cursor += read_len;74}7576/* write random data to fd */77remained = filesize;78cursor = data;79while (remained) {80ssize_t write_len = write(fd, cursor, remained);8182if (write_len <= 0) {83ksft_print_msg("Unable write random data to file.\n");84ret = false;85goto out_free_data;86}8788remained -= write_len;89cursor += write_len;90}9192ret = true;93out_free_data:94free(data);95close_random_fd:96close(random_fd);97out:98return ret;99}100101/*102* fsync() is implemented via noop_fsync() on tmpfs. This makes the fsync()103* test fail below, so we need to check for test file living on a tmpfs.104*/105static bool is_on_tmpfs(int fd)106{107struct statfs statfs_buf;108109if (fstatfs(fd, &statfs_buf))110return false;111112return statfs_buf.f_type == TMPFS_MAGIC;113}114115/*116* Open/create the file at filename, (optionally) write random data to it117* (exactly num_pages), then test the cachestat syscall on this file.118*119* If test_fsync == true, fsync the file, then check the number of dirty120* pages.121*/122static int test_cachestat(const char *filename, bool write_random, bool create,123bool test_fsync, unsigned long num_pages,124int open_flags, mode_t open_mode)125{126size_t PS = sysconf(_SC_PAGESIZE);127int filesize = num_pages * PS;128int ret = KSFT_PASS;129long syscall_ret;130struct cachestat cs;131struct cachestat_range cs_range = { 0, filesize };132133int fd = open(filename, open_flags, open_mode);134135if (fd == -1) {136ksft_print_msg("Unable to create/open file.\n");137ret = KSFT_FAIL;138goto out;139} else {140ksft_print_msg("Create/open %s\n", filename);141}142143if (write_random) {144if (!write_exactly(fd, filesize)) {145ksft_print_msg("Unable to access urandom.\n");146ret = KSFT_FAIL;147goto out1;148}149}150151syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);152153ksft_print_msg("Cachestat call returned %ld\n", syscall_ret);154155if (syscall_ret) {156ksft_print_msg("Cachestat returned non-zero.\n");157ret = KSFT_FAIL;158goto out1;159160} else {161print_cachestat(&cs);162163if (write_random) {164if (cs.nr_cache + cs.nr_evicted != num_pages) {165ksft_print_msg(166"Total number of cached and evicted pages is off.\n");167ret = KSFT_FAIL;168}169}170}171172if (test_fsync) {173if (is_on_tmpfs(fd)) {174ret = KSFT_SKIP;175} else if (fsync(fd)) {176ksft_print_msg("fsync fails.\n");177ret = KSFT_FAIL;178} else {179syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);180181ksft_print_msg("Cachestat call (after fsync) returned %ld\n",182syscall_ret);183184if (!syscall_ret) {185print_cachestat(&cs);186187if (cs.nr_dirty) {188ret = KSFT_FAIL;189ksft_print_msg(190"Number of dirty should be zero after fsync.\n");191}192} else {193ksft_print_msg("Cachestat (after fsync) returned non-zero.\n");194ret = KSFT_FAIL;195goto out1;196}197}198}199200out1:201close(fd);202203if (create)204remove(filename);205out:206return ret;207}208const char *file_type_str(enum file_type type)209{210switch (type) {211case FILE_SHMEM:212return "shmem";213case FILE_MMAP:214return "mmap";215default:216return "unknown";217}218}219220221bool run_cachestat_test(enum file_type type)222{223size_t PS = sysconf(_SC_PAGESIZE);224size_t filesize = PS * 512 * 2; /* 2 2MB huge pages */225int syscall_ret;226size_t compute_len = PS * 512;227struct cachestat_range cs_range = { PS, compute_len };228char *filename = "tmpshmcstat";229struct cachestat cs;230bool ret = true;231int fd;232unsigned long num_pages = compute_len / PS;233if (type == FILE_SHMEM)234fd = shm_open(filename, O_CREAT | O_RDWR, 0600);235else236fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);237238if (fd < 0) {239ksft_print_msg("Unable to create %s file.\n",240file_type_str(type));241ret = false;242goto out;243}244245if (ftruncate(fd, filesize)) {246ksft_print_msg("Unable to truncate %s file.\n",file_type_str(type));247ret = false;248goto close_fd;249}250switch (type) {251case FILE_SHMEM:252if (!write_exactly(fd, filesize)) {253ksft_print_msg("Unable to write to file.\n");254ret = false;255goto close_fd;256}257break;258case FILE_MMAP:259char *map = mmap(NULL, filesize, PROT_READ | PROT_WRITE,260MAP_SHARED, fd, 0);261262if (map == MAP_FAILED) {263ksft_print_msg("mmap failed.\n");264ret = false;265goto close_fd;266}267for (int i = 0; i < filesize; i++)268map[i] = 'A';269break;270default:271ksft_print_msg("Unsupported file type.\n");272ret = false;273goto close_fd;274}275syscall_ret = syscall(__NR_cachestat, fd, &cs_range, &cs, 0);276277if (syscall_ret) {278ksft_print_msg("Cachestat returned non-zero.\n");279ret = false;280goto close_fd;281} else {282print_cachestat(&cs);283if (cs.nr_cache + cs.nr_evicted != num_pages) {284ksft_print_msg(285"Total number of cached and evicted pages is off.\n");286ret = false;287}288}289290close_fd:291shm_unlink(filename);292out:293return ret;294}295296int main(void)297{298int ret;299300ksft_print_header();301302ret = syscall(__NR_cachestat, -1, NULL, NULL, 0);303if (ret == -1 && errno == ENOSYS)304ksft_exit_skip("cachestat syscall not available\n");305306ksft_set_plan(NR_TESTS);307308if (ret == -1 && errno == EBADF) {309ksft_test_result_pass("bad file descriptor recognized\n");310ret = 0;311} else {312ksft_test_result_fail("bad file descriptor ignored\n");313ret = 1;314}315316for (int i = 0; i < 5; i++) {317const char *dev_filename = dev_files[i];318319if (test_cachestat(dev_filename, false, false, false,3204, O_RDONLY, 0400) == KSFT_PASS)321ksft_test_result_pass("cachestat works with %s\n", dev_filename);322else {323ksft_test_result_fail("cachestat fails with %s\n", dev_filename);324ret = 1;325}326}327328if (test_cachestat("tmpfilecachestat", true, true,329false, 4, O_CREAT | O_RDWR, 0600) == KSFT_PASS)330ksft_test_result_pass("cachestat works with a normal file\n");331else {332ksft_test_result_fail("cachestat fails with normal file\n");333ret = 1;334}335336switch (test_cachestat("tmpfilecachestat", true, true,337true, 4, O_CREAT | O_RDWR, 0600)) {338case KSFT_FAIL:339ksft_test_result_fail("cachestat fsync fails with normal file\n");340ret = KSFT_FAIL;341break;342case KSFT_PASS:343ksft_test_result_pass("cachestat fsync works with a normal file\n");344break;345case KSFT_SKIP:346ksft_test_result_skip("tmpfilecachestat is on tmpfs\n");347break;348}349350if (run_cachestat_test(FILE_SHMEM))351ksft_test_result_pass("cachestat works with a shmem file\n");352else {353ksft_test_result_fail("cachestat fails with a shmem file\n");354ret = 1;355}356357if (run_cachestat_test(FILE_MMAP))358ksft_test_result_pass("cachestat works with a mmap file\n");359else {360ksft_test_result_fail("cachestat fails with a mmap file\n");361ret = 1;362}363return ret;364}365366367