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 * XXX The FUSE protocol does not include the runp and runb variables, so those 81 * must be guessed in-kernel. 82 */ 83 TEST_F(Bmap, bmap) 84 { 85 struct fiobmap2_arg arg; 86 /* 87 * Pick fsize and lbn large enough that max length runs won't reach 88 * either beginning or end of file 89 */ 90 const off_t filesize = 1 << 30; 91 int64_t lbn = 100; 92 int64_t pbn = 12345; 93 const ino_t ino = 42; 94 int fd; 95 96 expect_lookup(RELPATH, 42, filesize); 97 expect_open(ino, 0, 1); 98 expect_bmap(ino, lbn, m_maxbcachebuf, pbn); 99 100 fd = open(FULLPATH, O_RDWR); 101 ASSERT_LE(0, fd) << strerror(errno); 102 103 arg.bn = lbn; 104 arg.runp = -1; 105 arg.runb = -1; 106 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 107 EXPECT_EQ(arg.bn, pbn); 108 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 109 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 110 111 leak(fd); 112 } 113 114 /* 115 * If the daemon does not implement VOP_BMAP, fusefs should return sensible 116 * defaults. 117 */ 118 TEST_F(Bmap, default_) 119 { 120 struct fiobmap2_arg arg; 121 const off_t filesize = 1 << 30; 122 const ino_t ino = 42; 123 int64_t lbn; 124 int fd; 125 126 expect_lookup(RELPATH, 42, filesize); 127 expect_open(ino, 0, 1); 128 EXPECT_CALL(*m_mock, process( 129 ResultOf([=](auto in) { 130 return (in.header.opcode == FUSE_BMAP); 131 }, Eq(true)), 132 _) 133 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 134 135 fd = open(FULLPATH, O_RDWR); 136 ASSERT_LE(0, fd) << strerror(errno); 137 138 /* First block */ 139 lbn = 0; 140 arg.bn = lbn; 141 arg.runp = -1; 142 arg.runb = -1; 143 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 144 EXPECT_EQ(arg.bn, 0); 145 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 146 EXPECT_EQ(arg.runb, 0); 147 148 /* In the middle */ 149 lbn = filesize / m_maxbcachebuf / 2; 150 arg.bn = lbn; 151 arg.runp = -1; 152 arg.runb = -1; 153 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 154 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE); 155 EXPECT_EQ(arg.runp, m_maxphys / m_maxbcachebuf - 1); 156 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 157 158 /* Last block */ 159 lbn = filesize / m_maxbcachebuf - 1; 160 arg.bn = lbn; 161 arg.runp = -1; 162 arg.runb = -1; 163 ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno); 164 EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE); 165 EXPECT_EQ(arg.runp, 0); 166 EXPECT_EQ(arg.runb, m_maxphys / m_maxbcachebuf - 1); 167 168 leak(fd); 169 } 170 171 /* 172 * VOP_BMAP should not query the server for the file's size, even if its cached 173 * attributes have expired. 174 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937 175 */ 176 TEST_P(BmapEof, eof) 177 { 178 /* 179 * Outline: 180 * 1) lookup the file, setting attr_valid=0 181 * 2) Read more than one block, causing the kernel to issue VOP_BMAP to 182 * plan readahead. 183 * 3) Nothing should panic 184 * 4) Repeat the tests, truncating the file after different numbers of 185 * GETATTR operations. 186 */ 187 Sequence seq; 188 const off_t filesize = 2 * m_maxbcachebuf; 189 const ino_t ino = 42; 190 mode_t mode = S_IFREG | 0644; 191 char *buf; 192 int fd; 193 int ngetattrs; 194 195 ngetattrs = GetParam(); 196 FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0); 197 expect_open(ino, 0, 1); 198 // Depending on ngetattrs, FUSE_READ could be called with either 199 // filesize or filesize / 2 . 200 EXPECT_CALL(*m_mock, process( 201 ResultOf([=](auto in) { 202 return (in.header.opcode == FUSE_READ && 203 in.header.nodeid == ino && 204 in.body.read.offset == 0 && 205 ( in.body.read.size == filesize || 206 in.body.read.size == filesize / 2)); 207 }, Eq(true)), 208 _) 209 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 210 size_t osize = in.body.read.size; 211 212 assert(osize < sizeof(out.body.bytes)); 213 out.header.len = sizeof(struct fuse_out_header) + osize; 214 bzero(out.body.bytes, osize); 215 }))); 216 EXPECT_CALL(*m_mock, process( 217 ResultOf([](auto in) { 218 return (in.header.opcode == FUSE_GETATTR && 219 in.header.nodeid == ino); 220 }, Eq(true)), 221 _) 222 ).Times(Between(ngetattrs - 1, ngetattrs)) 223 .InSequence(seq) 224 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 225 SET_OUT_HEADER_LEN(out, attr); 226 out.body.attr.attr_valid = 0; 227 out.body.attr.attr.ino = ino; 228 out.body.attr.attr.mode = S_IFREG | 0644; 229 out.body.attr.attr.size = filesize; 230 }))); 231 EXPECT_CALL(*m_mock, process( 232 ResultOf([](auto in) { 233 return (in.header.opcode == FUSE_GETATTR && 234 in.header.nodeid == ino); 235 }, Eq(true)), 236 _) 237 ).InSequence(seq) 238 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 239 SET_OUT_HEADER_LEN(out, attr); 240 out.body.attr.attr_valid = 0; 241 out.body.attr.attr.ino = ino; 242 out.body.attr.attr.mode = S_IFREG | 0644; 243 out.body.attr.attr.size = filesize / 2; 244 }))); 245 246 buf = new char[filesize](); 247 fd = open(FULLPATH, O_RDWR); 248 ASSERT_LE(0, fd) << strerror(errno); 249 read(fd, buf, filesize); 250 251 delete[] buf; 252 leak(fd); 253 } 254 255 INSTANTIATE_TEST_SUITE_P(BE, BmapEof, 256 Values(1, 2, 3) 257 ); 258