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