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: 49 virtual void SetUp() { 50 m_maxreadahead = UINT32_MAX; 51 FuseTest::SetUp(); 52 } 53 void expect_bmap(uint64_t ino, uint64_t lbn, uint32_t blocksize, uint64_t pbn) 54 { 55 EXPECT_CALL(*m_mock, process( 56 ResultOf([=](auto in) { 57 return (in.header.opcode == FUSE_BMAP && 58 in.header.nodeid == ino && 59 in.body.bmap.block == lbn && 60 in.body.bmap.blocksize == blocksize); 61 }, Eq(true)), 62 _) 63 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 64 SET_OUT_HEADER_LEN(out, bmap); 65 out.body.bmap.block = pbn; 66 }))); 67 } 68 69 void expect_lookup(const char *relpath, uint64_t ino, off_t size) 70 { 71 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1, 72 UINT64_MAX); 73 } 74 }; 75 76 class BmapEof: public Bmap, public WithParamInterface<int> {}; 77 78 /* 79 * Test FUSE_BMAP 80 */ 81 TEST_F(Bmap, bmap) 82 { 83 struct fiobmap2_arg arg; 84 /* 85 * Pick fsize and lbn large enough that max length runs won't reach 86 * either beginning or end of file 87 */ 88 const off_t filesize = 1 << 30; 89 int64_t lbn = 100; 90 int64_t pbn = 12345; 91 const ino_t ino = 42; 92 int fd; 93 94 expect_lookup(RELPATH, 42, filesize); 95 expect_open(ino, 0, 1); 96 expect_bmap(ino, lbn, m_maxbcachebuf, pbn); 97 98 fd = open(FULLPATH, O_RDWR); 99 ASSERT_LE(0, fd) << strerror(errno); 100 101 arg.bn = lbn; 102 arg.runp = -1; 103 arg.runb = -1; 104 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 105 EXPECT_EQ(arg.bn, pbn); 106 /* 107 * XXX The FUSE protocol does not include the runp and runb variables, 108 * so those must be guessed in-kernel. There's no "right" answer, so 109 * just check that they're within reasonable limits. 110 */ 111 EXPECT_LE(arg.runb, lbn); 112 EXPECT_LE((unsigned long)arg.runb, m_maxreadahead / m_maxbcachebuf); 113 EXPECT_LE((unsigned long)arg.runb, m_maxphys / m_maxbcachebuf); 114 EXPECT_GT(arg.runb, 0); 115 EXPECT_LE(arg.runp, filesize / m_maxbcachebuf - lbn); 116 EXPECT_LE((unsigned long)arg.runp, m_maxreadahead / m_maxbcachebuf); 117 EXPECT_LE((unsigned long)arg.runp, m_maxphys / m_maxbcachebuf); 118 EXPECT_GT(arg.runp, 0); 119 120 leak(fd); 121 } 122 123 /* 124 * If the daemon does not implement VOP_BMAP, fusefs should return sensible 125 * defaults. 126 */ 127 TEST_F(Bmap, default_) 128 { 129 struct fiobmap2_arg arg; 130 const off_t filesize = 1 << 30; 131 const ino_t ino = 42; 132 int64_t lbn; 133 int fd; 134 135 expect_lookup(RELPATH, 42, filesize); 136 expect_open(ino, 0, 1); 137 EXPECT_CALL(*m_mock, process( 138 ResultOf([=](auto in) { 139 return (in.header.opcode == FUSE_BMAP); 140 }, Eq(true)), 141 _) 142 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 143 144 fd = open(FULLPATH, O_RDWR); 145 ASSERT_LE(0, fd) << strerror(errno); 146 147 /* First block */ 148 lbn = 0; 149 arg.bn = lbn; 150 arg.runp = -1; 151 arg.runb = -1; 152 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 153 EXPECT_EQ(arg.bn, 0); 154 EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1); 155 EXPECT_EQ(arg.runb, 0); 156 157 /* In the middle */ 158 lbn = filesize / m_maxbcachebuf / 2; 159 arg.bn = lbn; 160 arg.runp = -1; 161 arg.runb = -1; 162 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 163 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE); 164 EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1); 165 EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1); 166 167 /* Last block */ 168 lbn = filesize / m_maxbcachebuf - 1; 169 arg.bn = lbn; 170 arg.runp = -1; 171 arg.runb = -1; 172 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 173 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE); 174 EXPECT_EQ(arg.runp, 0); 175 EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1); 176 177 leak(fd); 178 } 179 180 /* 181 * The server returns an error for some reason for FUSE_BMAP. fusefs should 182 * faithfully report that error up to the caller. 183 */ 184 TEST_F(Bmap, einval) 185 { 186 struct fiobmap2_arg arg; 187 const off_t filesize = 1 << 30; 188 int64_t lbn = 100; 189 const ino_t ino = 42; 190 int fd; 191 192 expect_lookup(RELPATH, 42, filesize); 193 expect_open(ino, 0, 1); 194 EXPECT_CALL(*m_mock, process( 195 ResultOf([=](auto in) { 196 return (in.header.opcode == FUSE_BMAP && 197 in.header.nodeid == ino); 198 }, Eq(true)), 199 _) 200 ).WillOnce(Invoke(ReturnErrno(EINVAL))); 201 202 fd = open(FULLPATH, O_RDWR); 203 ASSERT_LE(0, fd) << strerror(errno); 204 205 arg.bn = lbn; 206 arg.runp = -1; 207 arg.runb = -1; 208 ASSERT_EQ(-1, ioctl(fd, FIOBMAP2, &arg)); 209 EXPECT_EQ(EINVAL, errno); 210 211 leak(fd); 212 } 213 214 /* 215 * Even if the server returns EINVAL during VOP_BMAP, we should still be able 216 * to successfully read a block. This is a regression test for 217 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264196 . The bug did not 218 * lie in fusefs, but this is a convenient place for a regression test. 219 */ 220 TEST_F(Bmap, spurious_einval) 221 { 222 const off_t filesize = 4ull << 30; 223 const ino_t ino = 42; 224 int fd, r; 225 char buf[1]; 226 227 expect_lookup(RELPATH, 42, filesize); 228 expect_open(ino, 0, 1); 229 EXPECT_CALL(*m_mock, process( 230 ResultOf([=](auto in) { 231 return (in.header.opcode == FUSE_BMAP && 232 in.header.nodeid == ino); 233 }, Eq(true)), 234 _) 235 ).WillRepeatedly(Invoke(ReturnErrno(EINVAL))); 236 EXPECT_CALL(*m_mock, process( 237 ResultOf([=](auto in) { 238 return (in.header.opcode == FUSE_READ && 239 in.header.nodeid == ino && 240 in.body.read.offset == 0 && 241 in.body.read.size == (uint64_t)m_maxbcachebuf); 242 }, Eq(true)), 243 _) 244 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 245 size_t osize = in.body.read.size; 246 247 assert(osize < sizeof(out.body.bytes)); 248 out.header.len = sizeof(struct fuse_out_header) + osize; 249 bzero(out.body.bytes, osize); 250 }))); 251 252 fd = open(FULLPATH, O_RDWR); 253 ASSERT_LE(0, fd) << strerror(errno); 254 255 /* 256 * Read the same block multiple times. On a system affected by PR 257 * 264196 , the second read will fail. 258 */ 259 r = read(fd, buf, sizeof(buf)); 260 EXPECT_EQ(r, 1) << strerror(errno); 261 r = read(fd, buf, sizeof(buf)); 262 EXPECT_EQ(r, 1) << strerror(errno); 263 r = read(fd, buf, sizeof(buf)); 264 EXPECT_EQ(r, 1) << strerror(errno); 265 } 266 267 /* 268 * VOP_BMAP should not query the server for the file's size, even if its cached 269 * attributes have expired. 270 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937 271 */ 272 TEST_P(BmapEof, eof) 273 { 274 /* 275 * Outline: 276 * 1) lookup the file, setting attr_valid=0 277 * 2) Read more than one block, causing the kernel to issue VOP_BMAP to 278 * plan readahead. 279 * 3) Nothing should panic 280 * 4) Repeat the tests, truncating the file after different numbers of 281 * GETATTR operations. 282 */ 283 Sequence seq; 284 const off_t filesize = 2 * m_maxbcachebuf; 285 const ino_t ino = 42; 286 mode_t mode = S_IFREG | 0644; 287 char *buf; 288 int fd; 289 int ngetattrs; 290 291 ngetattrs = GetParam(); 292 FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0); 293 expect_open(ino, 0, 1); 294 // Depending on ngetattrs, FUSE_READ could be called with either 295 // filesize or filesize / 2 . 296 EXPECT_CALL(*m_mock, process( 297 ResultOf([=](auto in) { 298 return (in.header.opcode == FUSE_READ && 299 in.header.nodeid == ino && 300 in.body.read.offset == 0 && 301 ( in.body.read.size == filesize || 302 in.body.read.size == filesize / 2)); 303 }, Eq(true)), 304 _) 305 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 306 size_t osize = in.body.read.size; 307 308 assert(osize < sizeof(out.body.bytes)); 309 out.header.len = sizeof(struct fuse_out_header) + osize; 310 bzero(out.body.bytes, osize); 311 }))); 312 EXPECT_CALL(*m_mock, process( 313 ResultOf([](auto in) { 314 return (in.header.opcode == FUSE_GETATTR && 315 in.header.nodeid == ino); 316 }, Eq(true)), 317 _) 318 ).Times(Between(ngetattrs - 1, ngetattrs)) 319 .InSequence(seq) 320 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 321 SET_OUT_HEADER_LEN(out, attr); 322 out.body.attr.attr_valid = 0; 323 out.body.attr.attr.ino = ino; 324 out.body.attr.attr.mode = S_IFREG | 0644; 325 out.body.attr.attr.size = filesize; 326 }))); 327 EXPECT_CALL(*m_mock, process( 328 ResultOf([](auto in) { 329 return (in.header.opcode == FUSE_GETATTR && 330 in.header.nodeid == ino); 331 }, Eq(true)), 332 _) 333 ).InSequence(seq) 334 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 335 SET_OUT_HEADER_LEN(out, attr); 336 out.body.attr.attr_valid = 0; 337 out.body.attr.attr.ino = ino; 338 out.body.attr.attr.mode = S_IFREG | 0644; 339 out.body.attr.attr.size = filesize / 2; 340 }))); 341 342 buf = new char[filesize](); 343 fd = open(FULLPATH, O_RDWR); 344 ASSERT_LE(0, fd) << strerror(errno); 345 read(fd, buf, filesize); 346 347 delete[] buf; 348 leak(fd); 349 } 350 351 INSTANTIATE_TEST_SUITE_P(BE, BmapEof, 352 Values(1, 2, 3) 353 ); 354