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