xref: /freebsd-src/tests/sys/fs/fusefs/bmap.cc (revision b2792a300ddb8d8334b234fe7744f5141cc96103)
1a1c9f4adSAlan Somers /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3a1c9f4adSAlan Somers  *
4a1c9f4adSAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
5a1c9f4adSAlan Somers  *
6a1c9f4adSAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
7a1c9f4adSAlan Somers  * from the FreeBSD Foundation.
8a1c9f4adSAlan Somers  *
9a1c9f4adSAlan Somers  * Redistribution and use in source and binary forms, with or without
10a1c9f4adSAlan Somers  * modification, are permitted provided that the following conditions
11a1c9f4adSAlan Somers  * are met:
12a1c9f4adSAlan Somers  * 1. Redistributions of source code must retain the above copyright
13a1c9f4adSAlan Somers  *    notice, this list of conditions and the following disclaimer.
14a1c9f4adSAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
15a1c9f4adSAlan Somers  *    notice, this list of conditions and the following disclaimer in the
16a1c9f4adSAlan Somers  *    documentation and/or other materials provided with the distribution.
17a1c9f4adSAlan Somers  *
18a1c9f4adSAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19a1c9f4adSAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20a1c9f4adSAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21a1c9f4adSAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22a1c9f4adSAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23a1c9f4adSAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24a1c9f4adSAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25a1c9f4adSAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26a1c9f4adSAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27a1c9f4adSAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28a1c9f4adSAlan Somers  * SUCH DAMAGE.
29a1c9f4adSAlan Somers  */
30a1c9f4adSAlan Somers 
31a1c9f4adSAlan Somers extern "C" {
32a1c9f4adSAlan Somers #include <sys/param.h>
33a1c9f4adSAlan Somers #include <sys/ioctl.h>
34a1c9f4adSAlan Somers #include <sys/filio.h>
35a1c9f4adSAlan Somers 
36a1c9f4adSAlan Somers #include <fcntl.h>
37a1c9f4adSAlan Somers }
38a1c9f4adSAlan Somers 
39a1c9f4adSAlan Somers #include "mockfs.hh"
40a1c9f4adSAlan Somers #include "utils.hh"
41a1c9f4adSAlan Somers 
42a1c9f4adSAlan Somers using namespace testing;
43a1c9f4adSAlan Somers 
44a1c9f4adSAlan Somers const static char FULLPATH[] = "mountpoint/foo";
45a1c9f4adSAlan Somers const static char RELPATH[] = "foo";
46a1c9f4adSAlan Somers 
47a1c9f4adSAlan Somers class Bmap: public FuseTest {
48a1c9f4adSAlan Somers public:
SetUp()49a1c9f4adSAlan Somers virtual void SetUp() {
50a1c9f4adSAlan Somers 	m_maxreadahead = UINT32_MAX;
51a1c9f4adSAlan Somers 	FuseTest::SetUp();
52a1c9f4adSAlan Somers }
expect_bmap(uint64_t ino,uint64_t lbn,uint32_t blocksize,uint64_t pbn)53a1c9f4adSAlan Somers void expect_bmap(uint64_t ino, uint64_t lbn, uint32_t blocksize, uint64_t pbn)
54a1c9f4adSAlan Somers {
55a1c9f4adSAlan Somers 	EXPECT_CALL(*m_mock, process(
56a1c9f4adSAlan Somers 		ResultOf([=](auto in) {
57a1c9f4adSAlan Somers 			return (in.header.opcode == FUSE_BMAP &&
58a1c9f4adSAlan Somers 				in.header.nodeid == ino &&
59a1c9f4adSAlan Somers 				in.body.bmap.block == lbn &&
60a1c9f4adSAlan Somers 				in.body.bmap.blocksize == blocksize);
61a1c9f4adSAlan Somers 		}, Eq(true)),
62a1c9f4adSAlan Somers 		_)
63a1c9f4adSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
64a1c9f4adSAlan Somers 		SET_OUT_HEADER_LEN(out, bmap);
65a1c9f4adSAlan Somers 		out.body.bmap.block = pbn;
66a1c9f4adSAlan Somers 	})));
67a1c9f4adSAlan Somers }
68a1c9f4adSAlan Somers 
expect_lookup(const char * relpath,uint64_t ino,off_t size)69a1c9f4adSAlan Somers void expect_lookup(const char *relpath, uint64_t ino, off_t size)
70a1c9f4adSAlan Somers {
71a1c9f4adSAlan Somers 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1,
72a1c9f4adSAlan Somers 		UINT64_MAX);
73a1c9f4adSAlan Somers }
74a1c9f4adSAlan Somers };
75a1c9f4adSAlan Somers 
767430017bSAlan Somers class BmapEof: public Bmap, public WithParamInterface<int> {};
777430017bSAlan Somers 
78a1c9f4adSAlan Somers /*
79a1c9f4adSAlan Somers  * Test FUSE_BMAP
80a1c9f4adSAlan Somers  */
TEST_F(Bmap,bmap)81a1c9f4adSAlan Somers TEST_F(Bmap, bmap)
82a1c9f4adSAlan Somers {
83a1c9f4adSAlan Somers 	struct fiobmap2_arg arg;
84f928dbcbSAlan Somers 	/*
85f928dbcbSAlan Somers 	 * Pick fsize and lbn large enough that max length runs won't reach
86f928dbcbSAlan Somers 	 * either beginning or end of file
87f928dbcbSAlan Somers 	 */
88f928dbcbSAlan Somers 	const off_t filesize = 1 << 30;
89f928dbcbSAlan Somers 	int64_t lbn = 100;
90a1c9f4adSAlan Somers 	int64_t pbn = 12345;
91f928dbcbSAlan Somers 	const ino_t ino = 42;
92a1c9f4adSAlan Somers 	int fd;
93a1c9f4adSAlan Somers 
94a1c9f4adSAlan Somers 	expect_lookup(RELPATH, 42, filesize);
95a1c9f4adSAlan Somers 	expect_open(ino, 0, 1);
96a1c9f4adSAlan Somers 	expect_bmap(ino, lbn, m_maxbcachebuf, pbn);
97a1c9f4adSAlan Somers 
98a1c9f4adSAlan Somers 	fd = open(FULLPATH, O_RDWR);
99a1c9f4adSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
100a1c9f4adSAlan Somers 
101a1c9f4adSAlan Somers 	arg.bn = lbn;
102a1c9f4adSAlan Somers 	arg.runp = -1;
103a1c9f4adSAlan Somers 	arg.runb = -1;
104a1c9f4adSAlan Somers 	ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
105a1c9f4adSAlan Somers 	EXPECT_EQ(arg.bn, pbn);
106*b2792a30SAlan Somers        /*
107*b2792a30SAlan Somers 	* XXX The FUSE protocol does not include the runp and runb variables,
108*b2792a30SAlan Somers 	* so those must be guessed in-kernel.  There's no "right" answer, so
109*b2792a30SAlan Somers 	* just check that they're within reasonable limits.
110*b2792a30SAlan Somers 	*/
111*b2792a30SAlan Somers 	EXPECT_LE(arg.runb, lbn);
112*b2792a30SAlan Somers 	EXPECT_LE((unsigned long)arg.runb, m_maxreadahead / m_maxbcachebuf);
113*b2792a30SAlan Somers 	EXPECT_LE((unsigned long)arg.runb, m_maxphys / m_maxbcachebuf);
114*b2792a30SAlan Somers 	EXPECT_GT(arg.runb, 0);
115*b2792a30SAlan Somers 	EXPECT_LE(arg.runp, filesize / m_maxbcachebuf - lbn);
116*b2792a30SAlan Somers 	EXPECT_LE((unsigned long)arg.runp, m_maxreadahead / m_maxbcachebuf);
117*b2792a30SAlan Somers 	EXPECT_LE((unsigned long)arg.runp, m_maxphys / m_maxbcachebuf);
118*b2792a30SAlan Somers 	EXPECT_GT(arg.runp, 0);
1194ac4b126SAlan Somers 
1204ac4b126SAlan Somers 	leak(fd);
121a1c9f4adSAlan Somers }
122a1c9f4adSAlan Somers 
123a1c9f4adSAlan Somers /*
124a1c9f4adSAlan Somers  * If the daemon does not implement VOP_BMAP, fusefs should return sensible
125a1c9f4adSAlan Somers  * defaults.
126a1c9f4adSAlan Somers  */
TEST_F(Bmap,default_)127a1c9f4adSAlan Somers TEST_F(Bmap, default_)
128a1c9f4adSAlan Somers {
129a1c9f4adSAlan Somers 	struct fiobmap2_arg arg;
130f928dbcbSAlan Somers 	const off_t filesize = 1 << 30;
131a1c9f4adSAlan Somers 	const ino_t ino = 42;
132a1c9f4adSAlan Somers 	int64_t lbn;
133a1c9f4adSAlan Somers 	int fd;
134a1c9f4adSAlan Somers 
135a1c9f4adSAlan Somers 	expect_lookup(RELPATH, 42, filesize);
136a1c9f4adSAlan Somers 	expect_open(ino, 0, 1);
137a1c9f4adSAlan Somers 	EXPECT_CALL(*m_mock, process(
138a1c9f4adSAlan Somers 		ResultOf([=](auto in) {
139a1c9f4adSAlan Somers 			return (in.header.opcode == FUSE_BMAP);
140a1c9f4adSAlan Somers 		}, Eq(true)),
141a1c9f4adSAlan Somers 		_)
142a1c9f4adSAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
143a1c9f4adSAlan Somers 
144a1c9f4adSAlan Somers 	fd = open(FULLPATH, O_RDWR);
145a1c9f4adSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
146a1c9f4adSAlan Somers 
147a1c9f4adSAlan Somers 	/* First block */
148a1c9f4adSAlan Somers 	lbn = 0;
149a1c9f4adSAlan Somers 	arg.bn = lbn;
150a1c9f4adSAlan Somers 	arg.runp = -1;
151a1c9f4adSAlan Somers 	arg.runb = -1;
152a1c9f4adSAlan Somers 	ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
153a1c9f4adSAlan Somers 	EXPECT_EQ(arg.bn, 0);
154e9b411d2SGleb Smirnoff 	EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
155a1c9f4adSAlan Somers 	EXPECT_EQ(arg.runb, 0);
156a1c9f4adSAlan Somers 
157a1c9f4adSAlan Somers 	/* In the middle */
158a1c9f4adSAlan Somers 	lbn = filesize / m_maxbcachebuf / 2;
159a1c9f4adSAlan Somers 	arg.bn = lbn;
160a1c9f4adSAlan Somers 	arg.runp = -1;
161a1c9f4adSAlan Somers 	arg.runb = -1;
162a1c9f4adSAlan Somers 	ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
163a1c9f4adSAlan Somers 	EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
164e9b411d2SGleb Smirnoff 	EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
165e9b411d2SGleb Smirnoff 	EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
166a1c9f4adSAlan Somers 
167a1c9f4adSAlan Somers 	/* Last block */
168a1c9f4adSAlan Somers 	lbn = filesize / m_maxbcachebuf - 1;
169a1c9f4adSAlan Somers 	arg.bn = lbn;
170a1c9f4adSAlan Somers 	arg.runp = -1;
171a1c9f4adSAlan Somers 	arg.runb = -1;
172a1c9f4adSAlan Somers 	ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
173a1c9f4adSAlan Somers 	EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
174a1c9f4adSAlan Somers 	EXPECT_EQ(arg.runp, 0);
175e9b411d2SGleb Smirnoff 	EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
1768e765737SAlan Somers 
1778e765737SAlan Somers 	leak(fd);
178a1c9f4adSAlan Somers }
1797430017bSAlan Somers 
1807430017bSAlan Somers /*
1817430017bSAlan Somers  * VOP_BMAP should not query the server for the file's size, even if its cached
1827430017bSAlan Somers  * attributes have expired.
1837430017bSAlan Somers  * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937
1847430017bSAlan Somers  */
TEST_P(BmapEof,eof)1857430017bSAlan Somers TEST_P(BmapEof, eof)
1867430017bSAlan Somers {
1877430017bSAlan Somers 	/*
1887430017bSAlan Somers 	 * Outline:
1897430017bSAlan Somers 	 * 1) lookup the file, setting attr_valid=0
1907430017bSAlan Somers 	 * 2) Read more than one block, causing the kernel to issue VOP_BMAP to
1917430017bSAlan Somers 	 *    plan readahead.
1927430017bSAlan Somers 	 * 3) Nothing should panic
1937430017bSAlan Somers 	 * 4) Repeat the tests, truncating the file after different numbers of
1947430017bSAlan Somers 	 *    GETATTR operations.
1957430017bSAlan Somers 	 */
1967430017bSAlan Somers 	Sequence seq;
1977430017bSAlan Somers 	const off_t filesize = 2 * m_maxbcachebuf;
1987430017bSAlan Somers 	const ino_t ino = 42;
1997430017bSAlan Somers 	mode_t mode = S_IFREG | 0644;
2008bae22bbSAlan Somers 	char *buf;
2017430017bSAlan Somers 	int fd;
2027430017bSAlan Somers 	int ngetattrs;
2037430017bSAlan Somers 
2047430017bSAlan Somers 	ngetattrs = GetParam();
2057430017bSAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0);
2067430017bSAlan Somers 	expect_open(ino, 0, 1);
2077430017bSAlan Somers 	// Depending on ngetattrs, FUSE_READ could be called with either
2087430017bSAlan Somers 	// filesize or filesize / 2 .
2097430017bSAlan Somers 	EXPECT_CALL(*m_mock, process(
2107430017bSAlan Somers 	ResultOf([=](auto in) {
2117430017bSAlan Somers 		return (in.header.opcode == FUSE_READ &&
2127430017bSAlan Somers 			in.header.nodeid == ino &&
2137430017bSAlan Somers 			in.body.read.offset == 0 &&
2147430017bSAlan Somers 			( in.body.read.size == filesize ||
2157430017bSAlan Somers 			  in.body.read.size == filesize / 2));
2167430017bSAlan Somers 		}, Eq(true)),
2177430017bSAlan Somers 		_)
2187430017bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
2197430017bSAlan Somers 		size_t osize = in.body.read.size;
2200c9df4afSAlan Somers 
2210c9df4afSAlan Somers 		assert(osize < sizeof(out.body.bytes));
2227430017bSAlan Somers 		out.header.len = sizeof(struct fuse_out_header) + osize;
2237430017bSAlan Somers 		bzero(out.body.bytes, osize);
2247430017bSAlan Somers 	})));
2257430017bSAlan Somers 	EXPECT_CALL(*m_mock, process(
2267430017bSAlan Somers 		ResultOf([](auto in) {
2277430017bSAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
2287430017bSAlan Somers 				in.header.nodeid == ino);
2297430017bSAlan Somers 		}, Eq(true)),
2307430017bSAlan Somers 		_)
2317430017bSAlan Somers 	).Times(Between(ngetattrs - 1, ngetattrs))
2327430017bSAlan Somers 	.InSequence(seq)
2337430017bSAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
2347430017bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
2357430017bSAlan Somers 		out.body.attr.attr_valid = 0;
2367430017bSAlan Somers 		out.body.attr.attr.ino = ino;
2377430017bSAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
2387430017bSAlan Somers 		out.body.attr.attr.size = filesize;
2397430017bSAlan Somers 	})));
2407430017bSAlan Somers 	EXPECT_CALL(*m_mock, process(
2417430017bSAlan Somers 		ResultOf([](auto in) {
2427430017bSAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
2437430017bSAlan Somers 				in.header.nodeid == ino);
2447430017bSAlan Somers 		}, Eq(true)),
2457430017bSAlan Somers 		_)
2467430017bSAlan Somers 	).InSequence(seq)
2477430017bSAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
2487430017bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
2497430017bSAlan Somers 		out.body.attr.attr_valid = 0;
2507430017bSAlan Somers 		out.body.attr.attr.ino = ino;
2517430017bSAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
2527430017bSAlan Somers 		out.body.attr.attr.size = filesize / 2;
2537430017bSAlan Somers 	})));
2547430017bSAlan Somers 
2558bae22bbSAlan Somers 	buf = new char[filesize]();
2567430017bSAlan Somers 	fd = open(FULLPATH, O_RDWR);
2577430017bSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
2587430017bSAlan Somers 	read(fd, buf, filesize);
2594ac4b126SAlan Somers 
2608bae22bbSAlan Somers 	delete[] buf;
2614ac4b126SAlan Somers 	leak(fd);
2627430017bSAlan Somers }
2637430017bSAlan Somers 
264811e0a31SEnji Cooper INSTANTIATE_TEST_SUITE_P(BE, BmapEof,
2657430017bSAlan Somers 	Values(1, 2, 3)
2667430017bSAlan Somers );
267