1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 The FreeBSD Foundation
5 *
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 extern "C" {
32 #include <sys/param.h>
33 #include <sys/ioctl.h>
34 #include <sys/filio.h>
35
36 #include <fcntl.h>
37 }
38
39 #include "mockfs.hh"
40 #include "utils.hh"
41
42 using namespace testing;
43
44 const static char FULLPATH[] = "mountpoint/foo";
45 const static char RELPATH[] = "foo";
46
47 class Bmap: public FuseTest,
48 public WithParamInterface<tuple<int, int>>
49 {
50 public:
SetUp()51 virtual void SetUp() {
52 m_maxreadahead = UINT32_MAX;
53 m_init_flags |= get<0>(GetParam());
54 FuseTest::SetUp();
55 }
expect_bmap(uint64_t ino,uint64_t lbn,uint32_t blocksize,uint64_t pbn)56 void expect_bmap(uint64_t ino, uint64_t lbn, uint32_t blocksize, uint64_t pbn)
57 {
58 EXPECT_CALL(*m_mock, process(
59 ResultOf([=](auto in) {
60 return (in.header.opcode == FUSE_BMAP &&
61 in.header.nodeid == ino &&
62 in.body.bmap.block == lbn &&
63 in.body.bmap.blocksize == blocksize);
64 }, Eq(true)),
65 _)
66 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
67 SET_OUT_HEADER_LEN(out, bmap);
68 out.body.bmap.block = pbn;
69 })));
70 }
71
expect_lookup(const char * relpath,uint64_t ino,off_t size)72 void expect_lookup(const char *relpath, uint64_t ino, off_t size)
73 {
74 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1,
75 UINT64_MAX);
76 }
77 };
78
79 class BmapEof: public Bmap {};
80
81 /*
82 * Test FUSE_BMAP
83 */
TEST_P(Bmap,bmap)84 TEST_P(Bmap, bmap)
85 {
86 struct fiobmap2_arg arg;
87 /*
88 * Pick fsize and lbn large enough that max length runs won't reach
89 * either beginning or end of file
90 */
91 const off_t filesize = 1 << 30;
92 int64_t lbn = 100;
93 int64_t pbn = 12345;
94 const ino_t ino = 42;
95 int fd;
96
97 expect_lookup(RELPATH, 42, filesize);
98 expect_open(ino, 0, 1);
99 expect_bmap(ino, lbn, m_maxbcachebuf, pbn);
100
101 fd = open(FULLPATH, O_RDWR);
102 ASSERT_LE(0, fd) << strerror(errno);
103
104 arg.bn = lbn;
105 arg.runp = -1;
106 arg.runb = -1;
107 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
108 EXPECT_EQ(arg.bn, pbn);
109 /*
110 * XXX The FUSE protocol does not include the runp and runb variables,
111 * so those must be guessed in-kernel. There's no "right" answer, so
112 * just check that they're within reasonable limits.
113 */
114 EXPECT_LE(arg.runb, lbn);
115 EXPECT_LE((unsigned long)arg.runb, m_maxreadahead / m_maxbcachebuf);
116 EXPECT_LE((unsigned long)arg.runb, m_maxphys / m_maxbcachebuf);
117 EXPECT_GT(arg.runb, 0);
118 EXPECT_LE(arg.runp, filesize / m_maxbcachebuf - lbn);
119 EXPECT_LE((unsigned long)arg.runp, m_maxreadahead / m_maxbcachebuf);
120 EXPECT_LE((unsigned long)arg.runp, m_maxphys / m_maxbcachebuf);
121 EXPECT_GT(arg.runp, 0);
122
123 leak(fd);
124 }
125
126 /*
127 * If the daemon does not implement VOP_BMAP, fusefs should return sensible
128 * defaults.
129 */
TEST_P(Bmap,default_)130 TEST_P(Bmap, default_)
131 {
132 struct fiobmap2_arg arg;
133 const off_t filesize = 1 << 30;
134 const ino_t ino = 42;
135 int64_t lbn;
136 int fd;
137
138 expect_lookup(RELPATH, 42, filesize);
139 expect_open(ino, 0, 1);
140 EXPECT_CALL(*m_mock, process(
141 ResultOf([=](auto in) {
142 return (in.header.opcode == FUSE_BMAP);
143 }, Eq(true)),
144 _)
145 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
146
147 fd = open(FULLPATH, O_RDWR);
148 ASSERT_LE(0, fd) << strerror(errno);
149
150 /* First block */
151 lbn = 0;
152 arg.bn = lbn;
153 arg.runp = -1;
154 arg.runb = -1;
155 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
156 EXPECT_EQ(arg.bn, 0);
157 EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
158 EXPECT_EQ(arg.runb, 0);
159
160 /* In the middle */
161 lbn = filesize / m_maxbcachebuf / 2;
162 arg.bn = lbn;
163 arg.runp = -1;
164 arg.runb = -1;
165 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
166 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
167 EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
168 EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
169
170 /* Last block */
171 lbn = filesize / m_maxbcachebuf - 1;
172 arg.bn = lbn;
173 arg.runp = -1;
174 arg.runb = -1;
175 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
176 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
177 EXPECT_EQ(arg.runp, 0);
178 EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
179
180 leak(fd);
181 }
182
183 /*
184 * The server returns an error for some reason for FUSE_BMAP. fusefs should
185 * faithfully report that error up to the caller.
186 */
TEST_P(Bmap,einval)187 TEST_P(Bmap, einval)
188 {
189 struct fiobmap2_arg arg;
190 const off_t filesize = 1 << 30;
191 int64_t lbn = 100;
192 const ino_t ino = 42;
193 int fd;
194
195 expect_lookup(RELPATH, 42, filesize);
196 expect_open(ino, 0, 1);
197 EXPECT_CALL(*m_mock, process(
198 ResultOf([=](auto in) {
199 return (in.header.opcode == FUSE_BMAP &&
200 in.header.nodeid == ino);
201 }, Eq(true)),
202 _)
203 ).WillOnce(Invoke(ReturnErrno(EINVAL)));
204
205 fd = open(FULLPATH, O_RDWR);
206 ASSERT_LE(0, fd) << strerror(errno);
207
208 arg.bn = lbn;
209 arg.runp = -1;
210 arg.runb = -1;
211 ASSERT_EQ(-1, ioctl(fd, FIOBMAP2, &arg));
212 EXPECT_EQ(EINVAL, errno);
213
214 leak(fd);
215 }
216
217 /*
218 * Even if the server returns EINVAL during VOP_BMAP, we should still be able
219 * to successfully read a block. This is a regression test for
220 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264196 . The bug did not
221 * lie in fusefs, but this is a convenient place for a regression test.
222 */
TEST_P(Bmap,spurious_einval)223 TEST_P(Bmap, spurious_einval)
224 {
225 const off_t filesize = 4ull << 30;
226 const ino_t ino = 42;
227 int fd, r;
228 char buf[1];
229
230 expect_lookup(RELPATH, 42, filesize);
231 expect_open(ino, 0, 1);
232 EXPECT_CALL(*m_mock, process(
233 ResultOf([=](auto in) {
234 return (in.header.opcode == FUSE_BMAP &&
235 in.header.nodeid == ino);
236 }, Eq(true)),
237 _)
238 ).WillRepeatedly(Invoke(ReturnErrno(EINVAL)));
239 EXPECT_CALL(*m_mock, process(
240 ResultOf([=](auto in) {
241 return (in.header.opcode == FUSE_READ &&
242 in.header.nodeid == ino &&
243 in.body.read.offset == 0 &&
244 in.body.read.size == (uint64_t)m_maxbcachebuf);
245 }, Eq(true)),
246 _)
247 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
248 size_t osize = in.body.read.size;
249
250 assert(osize < sizeof(out.body.bytes));
251 out.header.len = sizeof(struct fuse_out_header) + osize;
252 bzero(out.body.bytes, osize);
253 })));
254
255 fd = open(FULLPATH, O_RDWR);
256 ASSERT_LE(0, fd) << strerror(errno);
257
258 /*
259 * Read the same block multiple times. On a system affected by PR
260 * 264196 , the second read will fail.
261 */
262 r = read(fd, buf, sizeof(buf));
263 EXPECT_EQ(r, 1) << strerror(errno);
264 r = read(fd, buf, sizeof(buf));
265 EXPECT_EQ(r, 1) << strerror(errno);
266 r = read(fd, buf, sizeof(buf));
267 EXPECT_EQ(r, 1) << strerror(errno);
268 }
269
270 /*
271 * VOP_BMAP should not query the server for the file's size, even if its cached
272 * attributes have expired.
273 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937
274 */
TEST_P(BmapEof,eof)275 TEST_P(BmapEof, eof)
276 {
277 /*
278 * Outline:
279 * 1) lookup the file, setting attr_valid=0
280 * 2) Read more than one block, causing the kernel to issue VOP_BMAP to
281 * plan readahead.
282 * 3) Nothing should panic
283 * 4) Repeat the tests, truncating the file after different numbers of
284 * GETATTR operations.
285 */
286 Sequence seq;
287 const off_t filesize = 2 * m_maxbcachebuf;
288 const ino_t ino = 42;
289 mode_t mode = S_IFREG | 0644;
290 char *buf;
291 int fd;
292 int ngetattrs;
293
294 ngetattrs = get<1>(GetParam());
295 FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0);
296 expect_open(ino, 0, 1);
297 // Depending on ngetattrs, FUSE_READ could be called with either
298 // filesize or filesize / 2 .
299 EXPECT_CALL(*m_mock, process(
300 ResultOf([=](auto in) {
301 return (in.header.opcode == FUSE_READ &&
302 in.header.nodeid == ino &&
303 in.body.read.offset == 0 &&
304 ( in.body.read.size == filesize ||
305 in.body.read.size == filesize / 2));
306 }, Eq(true)),
307 _)
308 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
309 size_t osize = in.body.read.size;
310
311 assert(osize < sizeof(out.body.bytes));
312 out.header.len = sizeof(struct fuse_out_header) + osize;
313 bzero(out.body.bytes, osize);
314 })));
315 EXPECT_CALL(*m_mock, process(
316 ResultOf([](auto in) {
317 return (in.header.opcode == FUSE_GETATTR &&
318 in.header.nodeid == ino);
319 }, Eq(true)),
320 _)
321 ).Times(Between(ngetattrs - 1, ngetattrs))
322 .InSequence(seq)
323 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
324 SET_OUT_HEADER_LEN(out, attr);
325 out.body.attr.attr_valid = 0;
326 out.body.attr.attr.ino = ino;
327 out.body.attr.attr.mode = S_IFREG | 0644;
328 out.body.attr.attr.size = filesize;
329 })));
330 EXPECT_CALL(*m_mock, process(
331 ResultOf([](auto in) {
332 return (in.header.opcode == FUSE_GETATTR &&
333 in.header.nodeid == ino);
334 }, Eq(true)),
335 _)
336 ).InSequence(seq)
337 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
338 SET_OUT_HEADER_LEN(out, attr);
339 out.body.attr.attr_valid = 0;
340 out.body.attr.attr.ino = ino;
341 out.body.attr.attr.mode = S_IFREG | 0644;
342 out.body.attr.attr.size = filesize / 2;
343 })));
344
345 buf = new char[filesize]();
346 fd = open(FULLPATH, O_RDWR);
347 ASSERT_LE(0, fd) << strerror(errno);
348 read(fd, buf, filesize);
349
350 delete[] buf;
351 leak(fd);
352 }
353
354 /*
355 * Try with and without async reads, because it affects the type of vnode lock
356 * on entry to fuse_vnop_bmap.
357 */
358 INSTANTIATE_TEST_SUITE_P(B, Bmap, Values(
359 tuple(0, 0),
360 tuple(FUSE_ASYNC_READ, 0)
361 ));
362
363 INSTANTIATE_TEST_SUITE_P(BE, BmapEof, Values(
364 tuple(0, 1),
365 tuple(0, 2),
366 tuple(0, 3)
367 ));
368