xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/cmd/file/file_uncached.c (revision 2f10ffc003be396f3fc23cd2888023896560252b)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright (c) 2026, TrueNAS.
15  */
16 
17 /*
18  * Read or write a file with a hint that the data will not be reused, writing
19  * a per-block pattern that the read side verifies.  The hint is either
20  * RWF_DONTCACHE on each I/O (-d, Linux only) or POSIX_FADV_NOREUSE on the
21  * descriptor (-a).  With neither, the same I/O is done unhinted, so a test
22  * can check that all paths see the same data.  Exits 2 if RWF_DONTCACHE is
23  * not supported, which the caller reports as an unsupported test.
24  */
25 
26 #include "file_common.h"
27 #include <sys/uio.h>
28 
29 /*
30  * The RWF_DONTCACHE flag reaches the kernel through preadv2()/pwritev2().
31  * Every kernel version ZFS supports has those syscalls, but some libcs do not
32  * wrap them, so the flag can only be offered where the wrappers exist.
33  */
34 #if defined(HAVE_PREADV2) && defined(HAVE_PWRITEV2)
35 #define	HAVE_DONTCACHE
36 #ifndef RWF_DONTCACHE
37 #define	RWF_DONTCACHE	0x00000080
38 #endif
39 #endif
40 
41 #define	EXIT_UNSUPPORTED	2
42 
43 static const char *execname = "file_uncached";
44 
45 static void
usage(void)46 usage(void)
47 {
48 	(void) fprintf(stderr,
49 	    "usage: %s [-a] [-d] [-r] -f filename -b blocksize -c count\n",
50 	    execname);
51 }
52 
53 static ssize_t
do_io(int fd,int doread,int dontcache,void * buf,size_t len,off_t off)54 do_io(int fd, int doread, int dontcache, void *buf, size_t len, off_t off)
55 {
56 #ifdef HAVE_DONTCACHE
57 	if (dontcache) {
58 		struct iovec iov = { .iov_base = buf, .iov_len = len };
59 
60 		return (doread ?
61 		    preadv2(fd, &iov, 1, off, RWF_DONTCACHE) :
62 		    pwritev2(fd, &iov, 1, off, RWF_DONTCACHE));
63 	}
64 #else
65 	(void) dontcache;
66 #endif
67 	return (doread ? pread(fd, buf, len, off) :
68 	    pwrite(fd, buf, len, off));
69 }
70 
71 int
main(int argc,char * argv[])72 main(int argc, char *argv[])
73 {
74 	const char *filename = NULL;
75 	size_t blocksize = BLOCKSZ;
76 	long count = 0, i;
77 	int doread = 0, dontcache = 0, noreuse = 0, ch, fd;
78 	char *buf;
79 
80 	while ((ch = getopt(argc, argv, "ab:c:df:r")) != EOF) {
81 		switch (ch) {
82 		case 'a':
83 			noreuse = 1;
84 			break;
85 		case 'b':
86 			blocksize = strtoul(optarg, NULL, 0);
87 			break;
88 		case 'c':
89 			count = strtol(optarg, NULL, 0);
90 			break;
91 		case 'd':
92 			dontcache = 1;
93 			break;
94 		case 'f':
95 			filename = optarg;
96 			break;
97 		case 'r':
98 			doread = 1;
99 			break;
100 		default:
101 			usage();
102 			return (1);
103 		}
104 	}
105 
106 	if (filename == NULL || blocksize == 0 || count <= 0) {
107 		usage();
108 		return (1);
109 	}
110 
111 #ifndef HAVE_DONTCACHE
112 	if (dontcache) {
113 		(void) fprintf(stderr, "RWF_DONTCACHE is not available\n");
114 		return (EXIT_UNSUPPORTED);
115 	}
116 #endif
117 
118 	if ((buf = malloc(blocksize)) == NULL) {
119 		perror("malloc");
120 		return (1);
121 	}
122 
123 	fd = open(filename, doread ? O_RDONLY : O_WRONLY | O_CREAT, 0666);
124 	if (fd < 0) {
125 		perror("open");
126 		return (1);
127 	}
128 
129 	if (noreuse && posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE) != 0) {
130 		perror("posix_fadvise");
131 		return (1);
132 	}
133 
134 	for (i = 0; i < count; i++) {
135 		off_t off = (off_t)i * blocksize;
136 		unsigned char pattern = i & 0xff;
137 		ssize_t n;
138 		size_t j;
139 
140 		if (!doread)
141 			(void) memset(buf, pattern, blocksize);
142 
143 		n = do_io(fd, doread, dontcache, buf, blocksize, off);
144 		if (n < 0) {
145 			/*
146 			 * Both a kernel without RWF_DONTCACHE and a filesystem
147 			 * that has not set FOP_DONTCACHE report EOPNOTSUPP.
148 			 */
149 			if (dontcache &&
150 			    (errno == EOPNOTSUPP || errno == ENOSYS)) {
151 				(void) fprintf(stderr,
152 				    "RWF_DONTCACHE is not supported\n");
153 				return (EXIT_UNSUPPORTED);
154 			}
155 			perror(doread ? "pread" : "pwrite");
156 			return (1);
157 		}
158 		if ((size_t)n != blocksize) {
159 			(void) fprintf(stderr, "short %s of %zd of %zu bytes "
160 			    "at block %ld\n", doread ? "read" : "write", n,
161 			    blocksize, i);
162 			return (1);
163 		}
164 		if (!doread)
165 			continue;
166 
167 		for (j = 0; j < blocksize; j++) {
168 			if ((unsigned char)buf[j] != pattern) {
169 				(void) fprintf(stderr, "block %ld offset %zu: "
170 				    "expected 0x%02x, got 0x%02x\n", i, j,
171 				    pattern, (unsigned char)buf[j]);
172 				return (1);
173 			}
174 		}
175 	}
176 
177 	if (close(fd) != 0) {
178 		perror("close");
179 		return (1);
180 	}
181 	free(buf);
182 
183 	return (0);
184 }
185