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 31 extern "C" { 32 #include <sys/param.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Getattr : public FuseTest { 41 public: 42 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 43 uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec) 44 { 45 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 46 .Times(times) 47 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 48 SET_OUT_HEADER_LEN(out, entry); 49 out.body.entry.attr.mode = mode; 50 out.body.entry.nodeid = ino; 51 out.body.entry.attr.nlink = 1; 52 out.body.entry.attr_valid = attr_valid; 53 out.body.entry.attr_valid_nsec = attr_valid_nsec; 54 out.body.entry.attr.size = size; 55 out.body.entry.entry_valid = UINT64_MAX; 56 }))); 57 } 58 }; 59 60 class Getattr_7_8: public FuseTest { 61 public: 62 virtual void SetUp() { 63 m_kernel_minor_version = 8; 64 FuseTest::SetUp(); 65 } 66 }; 67 68 /* 69 * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs 70 * should use the cached attributes, rather than query the daemon 71 */ 72 TEST_F(Getattr, attr_cache) 73 { 74 const char FULLPATH[] = "mountpoint/some_file.txt"; 75 const char RELPATH[] = "some_file.txt"; 76 const uint64_t ino = 42; 77 struct stat sb; 78 79 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 80 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 81 SET_OUT_HEADER_LEN(out, entry); 82 out.body.entry.attr.mode = S_IFREG | 0644; 83 out.body.entry.nodeid = ino; 84 out.body.entry.entry_valid = UINT64_MAX; 85 }))); 86 EXPECT_CALL(*m_mock, process( 87 ResultOf([](auto in) { 88 return (in.header.opcode == FUSE_GETATTR && 89 in.header.nodeid == ino); 90 }, Eq(true)), 91 _) 92 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 93 SET_OUT_HEADER_LEN(out, attr); 94 out.body.attr.attr_valid = UINT64_MAX; 95 out.body.attr.attr.ino = ino; // Must match nodeid 96 out.body.attr.attr.mode = S_IFREG | 0644; 97 }))); 98 EXPECT_EQ(0, stat(FULLPATH, &sb)); 99 /* The second stat(2) should use cached attributes */ 100 EXPECT_EQ(0, stat(FULLPATH, &sb)); 101 } 102 103 /* 104 * If getattr returns a finite but non-zero cache timeout, then we should 105 * discard the cached attributes and requery the daemon after the timeout 106 * period passes. 107 */ 108 TEST_F(Getattr, attr_cache_timeout) 109 { 110 const char FULLPATH[] = "mountpoint/some_file.txt"; 111 const char RELPATH[] = "some_file.txt"; 112 const uint64_t ino = 42; 113 struct stat sb; 114 115 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0); 116 EXPECT_CALL(*m_mock, process( 117 ResultOf([](auto in) { 118 return (in.header.opcode == FUSE_GETATTR && 119 in.header.nodeid == ino); 120 }, Eq(true)), 121 _) 122 ).Times(2) 123 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 124 SET_OUT_HEADER_LEN(out, attr); 125 out.body.attr.attr_valid_nsec = NAP_NS / 2; 126 out.body.attr.attr_valid = 0; 127 out.body.attr.attr.ino = ino; // Must match nodeid 128 out.body.attr.attr.mode = S_IFREG | 0644; 129 }))); 130 131 EXPECT_EQ(0, stat(FULLPATH, &sb)); 132 nap(); 133 /* Timeout has expired. stat(2) should requery the daemon */ 134 EXPECT_EQ(0, stat(FULLPATH, &sb)); 135 } 136 137 /* 138 * If attr.blksize is zero, then the kernel should use a default value for 139 * st_blksize 140 */ 141 TEST_F(Getattr, blksize_zero) 142 { 143 const char FULLPATH[] = "mountpoint/some_file.txt"; 144 const char RELPATH[] = "some_file.txt"; 145 const uint64_t ino = 42; 146 struct stat sb; 147 148 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0); 149 EXPECT_CALL(*m_mock, process( 150 ResultOf([](auto in) { 151 return (in.header.opcode == FUSE_GETATTR && 152 in.header.nodeid == ino); 153 }, Eq(true)), 154 _) 155 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 156 SET_OUT_HEADER_LEN(out, attr); 157 out.body.attr.attr.mode = S_IFREG | 0644; 158 out.body.attr.attr.ino = ino; // Must match nodeid 159 out.body.attr.attr.blksize = 0; 160 }))); 161 162 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 163 EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize); 164 } 165 166 TEST_F(Getattr, enoent) 167 { 168 const char FULLPATH[] = "mountpoint/some_file.txt"; 169 const char RELPATH[] = "some_file.txt"; 170 struct stat sb; 171 const uint64_t ino = 42; 172 173 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0); 174 EXPECT_CALL(*m_mock, process( 175 ResultOf([](auto in) { 176 return (in.header.opcode == FUSE_GETATTR && 177 in.header.nodeid == ino); 178 }, Eq(true)), 179 _) 180 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 181 EXPECT_NE(0, stat(FULLPATH, &sb)); 182 EXPECT_EQ(ENOENT, errno); 183 } 184 185 TEST_F(Getattr, ok) 186 { 187 const char FULLPATH[] = "mountpoint/some_file.txt"; 188 const char RELPATH[] = "some_file.txt"; 189 const uint64_t ino = 42; 190 struct stat sb; 191 192 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0); 193 EXPECT_CALL(*m_mock, process( 194 ResultOf([](auto in) { 195 return (in.header.opcode == FUSE_GETATTR && 196 in.header.nodeid == ino); 197 }, Eq(true)), 198 _) 199 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 200 SET_OUT_HEADER_LEN(out, attr); 201 out.body.attr.attr.ino = ino; // Must match nodeid 202 out.body.attr.attr.mode = S_IFREG | 0644; 203 out.body.attr.attr.size = 1; 204 out.body.attr.attr.blocks = 2; 205 out.body.attr.attr.atime = 3; 206 out.body.attr.attr.mtime = 4; 207 out.body.attr.attr.ctime = 5; 208 out.body.attr.attr.atimensec = 6; 209 out.body.attr.attr.mtimensec = 7; 210 out.body.attr.attr.ctimensec = 8; 211 out.body.attr.attr.nlink = 9; 212 out.body.attr.attr.uid = 10; 213 out.body.attr.attr.gid = 11; 214 out.body.attr.attr.rdev = 12; 215 out.body.attr.attr.blksize = 12345; 216 }))); 217 218 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 219 EXPECT_EQ(1, sb.st_size); 220 EXPECT_EQ(2, sb.st_blocks); 221 EXPECT_EQ(3, sb.st_atim.tv_sec); 222 EXPECT_EQ(6, sb.st_atim.tv_nsec); 223 EXPECT_EQ(4, sb.st_mtim.tv_sec); 224 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 225 EXPECT_EQ(5, sb.st_ctim.tv_sec); 226 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 227 EXPECT_EQ(9ull, sb.st_nlink); 228 EXPECT_EQ(10ul, sb.st_uid); 229 EXPECT_EQ(11ul, sb.st_gid); 230 EXPECT_EQ(12ul, sb.st_rdev); 231 EXPECT_EQ((blksize_t)12345, sb.st_blksize); 232 EXPECT_EQ(ino, sb.st_ino); 233 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 234 235 //st_birthtim and st_flags are not supported by protocol 7.8. They're 236 //only supported as OS-specific extensions to OSX. 237 //EXPECT_EQ(, sb.st_birthtim); 238 //EXPECT_EQ(, sb.st_flags); 239 240 //FUSE can't set st_blksize until protocol 7.9 241 } 242 243 TEST_F(Getattr_7_8, ok) 244 { 245 const char FULLPATH[] = "mountpoint/some_file.txt"; 246 const char RELPATH[] = "some_file.txt"; 247 const uint64_t ino = 42; 248 struct stat sb; 249 250 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 251 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 252 SET_OUT_HEADER_LEN(out, entry_7_8); 253 out.body.entry.attr.mode = S_IFREG | 0644; 254 out.body.entry.nodeid = ino; 255 out.body.entry.attr.nlink = 1; 256 out.body.entry.attr.size = 1; 257 }))); 258 EXPECT_CALL(*m_mock, process( 259 ResultOf([](auto in) { 260 return (in.header.opcode == FUSE_GETATTR && 261 in.header.nodeid == ino); 262 }, Eq(true)), 263 _) 264 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 265 SET_OUT_HEADER_LEN(out, attr_7_8); 266 out.body.attr.attr.ino = ino; // Must match nodeid 267 out.body.attr.attr.mode = S_IFREG | 0644; 268 out.body.attr.attr.size = 1; 269 out.body.attr.attr.blocks = 2; 270 out.body.attr.attr.atime = 3; 271 out.body.attr.attr.mtime = 4; 272 out.body.attr.attr.ctime = 5; 273 out.body.attr.attr.atimensec = 6; 274 out.body.attr.attr.mtimensec = 7; 275 out.body.attr.attr.ctimensec = 8; 276 out.body.attr.attr.nlink = 9; 277 out.body.attr.attr.uid = 10; 278 out.body.attr.attr.gid = 11; 279 out.body.attr.attr.rdev = 12; 280 }))); 281 282 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 283 EXPECT_EQ(1, sb.st_size); 284 EXPECT_EQ(2, sb.st_blocks); 285 EXPECT_EQ(3, sb.st_atim.tv_sec); 286 EXPECT_EQ(6, sb.st_atim.tv_nsec); 287 EXPECT_EQ(4, sb.st_mtim.tv_sec); 288 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 289 EXPECT_EQ(5, sb.st_ctim.tv_sec); 290 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 291 EXPECT_EQ(9ull, sb.st_nlink); 292 EXPECT_EQ(10ul, sb.st_uid); 293 EXPECT_EQ(11ul, sb.st_gid); 294 EXPECT_EQ(12ul, sb.st_rdev); 295 EXPECT_EQ(ino, sb.st_ino); 296 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 297 298 //st_birthtim and st_flags are not supported by protocol 7.8. They're 299 //only supported as OS-specific extensions to OSX. 300 } 301