1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 * $FreeBSD$ 31 */ 32 33 extern "C" { 34 #include <sys/param.h> 35 #include <sys/ioctl.h> 36 #include <sys/filio.h> 37 38 #include <fcntl.h> 39 } 40 41 #include "mockfs.hh" 42 #include "utils.hh" 43 44 using namespace testing; 45 46 const static char FULLPATH[] = "mountpoint/foo"; 47 const static char RELPATH[] = "foo"; 48 49 class Bmap: public FuseTest { 50 public: 51 virtual void SetUp() { 52 m_maxreadahead = UINT32_MAX; 53 FuseTest::SetUp(); 54 } 55 void expect_bmap(uint64_t ino, uint64_t lbn, uint32_t blocksize, uint64_t pbn) 56 { 57 EXPECT_CALL(*m_mock, process( 58 ResultOf([=](auto in) { 59 return (in.header.opcode == FUSE_BMAP && 60 in.header.nodeid == ino && 61 in.body.bmap.block == lbn && 62 in.body.bmap.blocksize == blocksize); 63 }, Eq(true)), 64 _) 65 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 66 SET_OUT_HEADER_LEN(out, bmap); 67 out.body.bmap.block = pbn; 68 }))); 69 } 70 71 void expect_lookup(const char *relpath, uint64_t ino, off_t size) 72 { 73 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1, 74 UINT64_MAX); 75 } 76 }; 77 78 class BmapEof: public Bmap, public WithParamInterface<int> {}; 79 80 /* 81 * Test FUSE_BMAP 82 * XXX The FUSE protocol does not include the runp and runb variables, so those 83 * must be guessed in-kernel. 84 */ 85 TEST_F(Bmap, bmap) 86 { 87 struct fiobmap2_arg arg; 88 /* 89 * Pick fsize and lbn large enough that max length runs won't reach 90 * either beginning or end of file 91 */ 92 const off_t filesize = 1 << 30; 93 int64_t lbn = 100; 94 int64_t pbn = 12345; 95 const ino_t ino = 42; 96 int fd; 97 98 expect_lookup(RELPATH, 42, filesize); 99 expect_open(ino, 0, 1); 100 expect_bmap(ino, lbn, m_maxbcachebuf, pbn); 101 102 fd = open(FULLPATH, O_RDWR); 103 ASSERT_LE(0, fd) << strerror(errno); 104 105 arg.bn = lbn; 106 arg.runp = -1; 107 arg.runb = -1; 108 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 109 EXPECT_EQ(arg.bn, pbn); 110 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 111 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 112 113 leak(fd); 114 } 115 116 /* 117 * If the daemon does not implement VOP_BMAP, fusefs should return sensible 118 * defaults. 119 */ 120 TEST_F(Bmap, default_) 121 { 122 struct fiobmap2_arg arg; 123 const off_t filesize = 1 << 30; 124 const ino_t ino = 42; 125 int64_t lbn; 126 int fd; 127 128 expect_lookup(RELPATH, 42, filesize); 129 expect_open(ino, 0, 1); 130 EXPECT_CALL(*m_mock, process( 131 ResultOf([=](auto in) { 132 return (in.header.opcode == FUSE_BMAP); 133 }, Eq(true)), 134 _) 135 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 136 137 fd = open(FULLPATH, O_RDWR); 138 ASSERT_LE(0, fd) << strerror(errno); 139 140 /* First block */ 141 lbn = 0; 142 arg.bn = lbn; 143 arg.runp = -1; 144 arg.runb = -1; 145 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 146 EXPECT_EQ(arg.bn, 0); 147 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 148 EXPECT_EQ(arg.runb, 0); 149 150 /* In the middle */ 151 lbn = filesize / m_maxbcachebuf / 2; 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, lbn * m_maxbcachebuf / DEV_BSIZE); 157 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 158 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 159 160 /* Last block */ 161 lbn = filesize / m_maxbcachebuf - 1; 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(arg.runp, 0); 168 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 169 170 leak(fd); 171 } 172 173 /* 174 * VOP_BMAP should not query the server for the file's size, even if its cached 175 * attributes have expired. 176 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937 177 */ 178 TEST_P(BmapEof, eof) 179 { 180 /* 181 * Outline: 182 * 1) lookup the file, setting attr_valid=0 183 * 2) Read more than one block, causing the kernel to issue VOP_BMAP to 184 * plan readahead. 185 * 3) Nothing should panic 186 * 4) Repeat the tests, truncating the file after different numbers of 187 * GETATTR operations. 188 */ 189 Sequence seq; 190 const off_t filesize = 2 * m_maxbcachebuf; 191 const ino_t ino = 42; 192 mode_t mode = S_IFREG | 0644; 193 void *buf; 194 int fd; 195 int ngetattrs; 196 197 ngetattrs = GetParam(); 198 FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0); 199 expect_open(ino, 0, 1); 200 // Depending on ngetattrs, FUSE_READ could be called with either 201 // filesize or filesize / 2 . 202 EXPECT_CALL(*m_mock, process( 203 ResultOf([=](auto in) { 204 return (in.header.opcode == FUSE_READ && 205 in.header.nodeid == ino && 206 in.body.read.offset == 0 && 207 ( in.body.read.size == filesize || 208 in.body.read.size == filesize / 2)); 209 }, Eq(true)), 210 _) 211 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 212 size_t osize = in.body.read.size; 213 214 assert(osize < sizeof(out.body.bytes)); 215 out.header.len = sizeof(struct fuse_out_header) + osize; 216 bzero(out.body.bytes, osize); 217 }))); 218 EXPECT_CALL(*m_mock, process( 219 ResultOf([](auto in) { 220 return (in.header.opcode == FUSE_GETATTR && 221 in.header.nodeid == ino); 222 }, Eq(true)), 223 _) 224 ).Times(Between(ngetattrs - 1, ngetattrs)) 225 .InSequence(seq) 226 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 227 SET_OUT_HEADER_LEN(out, attr); 228 out.body.attr.attr_valid = 0; 229 out.body.attr.attr.ino = ino; 230 out.body.attr.attr.mode = S_IFREG | 0644; 231 out.body.attr.attr.size = filesize; 232 }))); 233 EXPECT_CALL(*m_mock, process( 234 ResultOf([](auto in) { 235 return (in.header.opcode == FUSE_GETATTR && 236 in.header.nodeid == ino); 237 }, Eq(true)), 238 _) 239 ).InSequence(seq) 240 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 241 SET_OUT_HEADER_LEN(out, attr); 242 out.body.attr.attr_valid = 0; 243 out.body.attr.attr.ino = ino; 244 out.body.attr.attr.mode = S_IFREG | 0644; 245 out.body.attr.attr.size = filesize / 2; 246 }))); 247 248 buf = calloc(1, filesize); 249 fd = open(FULLPATH, O_RDWR); 250 ASSERT_LE(0, fd) << strerror(errno); 251 read(fd, buf, filesize); 252 253 leak(fd); 254 } 255 256 INSTANTIATE_TEST_CASE_P(BE, BmapEof, 257 Values(1, 2, 3) 258 ); 259