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 34 #include <semaphore.h> 35 } 36 37 #include "mockfs.hh" 38 #include "utils.hh" 39 40 using namespace testing; 41 42 class Getattr : public FuseTest { 43 public: 44 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 45 uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec) 46 { 47 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 48 .Times(times) 49 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 50 SET_OUT_HEADER_LEN(out, entry); 51 out.body.entry.attr.mode = mode; 52 out.body.entry.nodeid = ino; 53 out.body.entry.attr.nlink = 1; 54 out.body.entry.attr_valid = attr_valid; 55 out.body.entry.attr_valid_nsec = attr_valid_nsec; 56 out.body.entry.attr.size = size; 57 out.body.entry.entry_valid = UINT64_MAX; 58 }))); 59 } 60 }; 61 62 class Getattr_7_8: public FuseTest { 63 public: 64 virtual void SetUp() { 65 m_kernel_minor_version = 8; 66 FuseTest::SetUp(); 67 } 68 }; 69 70 /* 71 * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs 72 * should use the cached attributes, rather than query the daemon 73 */ 74 TEST_F(Getattr, attr_cache) 75 { 76 const char FULLPATH[] = "mountpoint/some_file.txt"; 77 const char RELPATH[] = "some_file.txt"; 78 const uint64_t ino = 42; 79 struct stat sb; 80 81 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 82 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 83 SET_OUT_HEADER_LEN(out, entry); 84 out.body.entry.attr.mode = S_IFREG | 0644; 85 out.body.entry.nodeid = ino; 86 out.body.entry.entry_valid = UINT64_MAX; 87 }))); 88 EXPECT_CALL(*m_mock, process( 89 ResultOf([](auto in) { 90 return (in.header.opcode == FUSE_GETATTR && 91 in.header.nodeid == ino); 92 }, Eq(true)), 93 _) 94 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 95 SET_OUT_HEADER_LEN(out, attr); 96 out.body.attr.attr_valid = UINT64_MAX; 97 out.body.attr.attr.ino = ino; // Must match nodeid 98 out.body.attr.attr.mode = S_IFREG | 0644; 99 }))); 100 EXPECT_EQ(0, stat(FULLPATH, &sb)); 101 /* The second stat(2) should use cached attributes */ 102 EXPECT_EQ(0, stat(FULLPATH, &sb)); 103 } 104 105 /* 106 * If getattr returns a finite but non-zero cache timeout, then we should 107 * discard the cached attributes and requery the daemon after the timeout 108 * period passes. 109 */ 110 TEST_F(Getattr, attr_cache_timeout) 111 { 112 const char FULLPATH[] = "mountpoint/some_file.txt"; 113 const char RELPATH[] = "some_file.txt"; 114 const uint64_t ino = 42; 115 struct stat sb; 116 117 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0); 118 EXPECT_CALL(*m_mock, process( 119 ResultOf([](auto in) { 120 return (in.header.opcode == FUSE_GETATTR && 121 in.header.nodeid == ino); 122 }, Eq(true)), 123 _) 124 ).Times(2) 125 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 126 SET_OUT_HEADER_LEN(out, attr); 127 out.body.attr.attr_valid_nsec = NAP_NS / 2; 128 out.body.attr.attr_valid = 0; 129 out.body.attr.attr.ino = ino; // Must match nodeid 130 out.body.attr.attr.mode = S_IFREG | 0644; 131 }))); 132 133 EXPECT_EQ(0, stat(FULLPATH, &sb)); 134 nap(); 135 /* Timeout has expired. stat(2) should requery the daemon */ 136 EXPECT_EQ(0, stat(FULLPATH, &sb)); 137 } 138 139 /* 140 * If attr.blksize is zero, then the kernel should use a default value for 141 * st_blksize 142 */ 143 TEST_F(Getattr, blksize_zero) 144 { 145 const char FULLPATH[] = "mountpoint/some_file.txt"; 146 const char RELPATH[] = "some_file.txt"; 147 const uint64_t ino = 42; 148 struct stat sb; 149 150 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0); 151 EXPECT_CALL(*m_mock, process( 152 ResultOf([](auto in) { 153 return (in.header.opcode == FUSE_GETATTR && 154 in.header.nodeid == ino); 155 }, Eq(true)), 156 _) 157 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 158 SET_OUT_HEADER_LEN(out, attr); 159 out.body.attr.attr.mode = S_IFREG | 0644; 160 out.body.attr.attr.ino = ino; // Must match nodeid 161 out.body.attr.attr.blksize = 0; 162 out.body.attr.attr.size = 1; 163 }))); 164 165 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 166 EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize); 167 } 168 169 TEST_F(Getattr, enoent) 170 { 171 const char FULLPATH[] = "mountpoint/some_file.txt"; 172 const char RELPATH[] = "some_file.txt"; 173 struct stat sb; 174 const uint64_t ino = 42; 175 sem_t sem; 176 177 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 178 179 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0); 180 EXPECT_CALL(*m_mock, process( 181 ResultOf([](auto in) { 182 return (in.header.opcode == FUSE_GETATTR && 183 in.header.nodeid == ino); 184 }, Eq(true)), 185 _) 186 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 187 // Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode 188 // and send a FUSE_FORGET 189 expect_forget(ino, 1, &sem); 190 191 EXPECT_NE(0, stat(FULLPATH, &sb)); 192 EXPECT_EQ(ENOENT, errno); 193 194 sem_wait(&sem); 195 sem_destroy(&sem); 196 } 197 198 TEST_F(Getattr, ok) 199 { 200 const char FULLPATH[] = "mountpoint/some_file.txt"; 201 const char RELPATH[] = "some_file.txt"; 202 const uint64_t ino = 42; 203 struct stat sb; 204 205 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0); 206 EXPECT_CALL(*m_mock, process( 207 ResultOf([](auto in) { 208 return (in.header.opcode == FUSE_GETATTR && 209 in.body.getattr.getattr_flags == 0 && 210 in.header.nodeid == ino); 211 }, Eq(true)), 212 _) 213 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 214 SET_OUT_HEADER_LEN(out, attr); 215 out.body.attr.attr.ino = ino; // Must match nodeid 216 out.body.attr.attr.mode = S_IFREG | 0644; 217 out.body.attr.attr.size = 1; 218 out.body.attr.attr.blocks = 2; 219 out.body.attr.attr.atime = 3; 220 out.body.attr.attr.mtime = 4; 221 out.body.attr.attr.ctime = 5; 222 out.body.attr.attr.atimensec = 6; 223 out.body.attr.attr.mtimensec = 7; 224 out.body.attr.attr.ctimensec = 8; 225 out.body.attr.attr.nlink = 9; 226 out.body.attr.attr.uid = 10; 227 out.body.attr.attr.gid = 11; 228 out.body.attr.attr.rdev = 12; 229 out.body.attr.attr.blksize = 12345; 230 }))); 231 232 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 233 EXPECT_EQ(1, sb.st_size); 234 EXPECT_EQ(2, sb.st_blocks); 235 EXPECT_EQ(3, sb.st_atim.tv_sec); 236 EXPECT_EQ(6, sb.st_atim.tv_nsec); 237 EXPECT_EQ(4, sb.st_mtim.tv_sec); 238 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 239 EXPECT_EQ(5, sb.st_ctim.tv_sec); 240 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 241 EXPECT_EQ(9ull, sb.st_nlink); 242 EXPECT_EQ(10ul, sb.st_uid); 243 EXPECT_EQ(11ul, sb.st_gid); 244 EXPECT_EQ(12ul, sb.st_rdev); 245 EXPECT_EQ((blksize_t)12345, sb.st_blksize); 246 EXPECT_EQ(ino, sb.st_ino); 247 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 248 249 /* 250 * st_birthtim and st_flags are not supported by the fuse protocol. 251 * They're only supported as OS-specific extensions to OSX. For 252 * birthtime, the convention for "not supported" is "negative one 253 * second". 254 */ 255 EXPECT_EQ(-1, sb.st_birthtim.tv_sec); 256 EXPECT_EQ(0, sb.st_birthtim.tv_nsec); 257 EXPECT_EQ(0u, sb.st_flags); 258 } 259 260 /* 261 * FUSE_GETATTR returns a different file type, even though the entry cache 262 * hasn't expired. This is a server bug! It probably means that the server 263 * removed the file and recreated it with the same inode but a different vtyp. 264 * The best thing fusefs can do is return ENOENT to the caller. After all, the 265 * entry must not have existed recently. 266 */ 267 TEST_F(Getattr, vtyp_conflict) 268 { 269 const char FULLPATH[] = "mountpoint/some_file.txt"; 270 const char RELPATH[] = "some_file.txt"; 271 const uint64_t ino = 42; 272 struct stat sb; 273 sem_t sem; 274 275 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 276 277 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 278 .WillOnce(Invoke( 279 ReturnImmediate([=](auto in __unused, auto& out) { 280 SET_OUT_HEADER_LEN(out, entry); 281 out.body.entry.attr.mode = S_IFREG | 0644; 282 out.body.entry.nodeid = ino; 283 out.body.entry.attr.nlink = 1; 284 out.body.entry.attr_valid = 0; 285 out.body.entry.entry_valid = UINT64_MAX; 286 }))); 287 EXPECT_CALL(*m_mock, process( 288 ResultOf([](auto in) { 289 return (in.header.opcode == FUSE_GETATTR && 290 in.body.getattr.getattr_flags == 0 && 291 in.header.nodeid == ino); 292 }, Eq(true)), 293 _) 294 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 295 SET_OUT_HEADER_LEN(out, attr); 296 out.body.attr.attr.ino = ino; // Must match nodeid 297 out.body.attr.attr.mode = S_IFDIR | 0755; // Changed! 298 out.body.attr.attr.nlink = 2; 299 }))); 300 // We should reclaim stale vnodes 301 expect_forget(ino, 1, &sem); 302 303 ASSERT_NE(0, stat(FULLPATH, &sb)); 304 EXPECT_EQ(errno, ENOENT); 305 306 sem_wait(&sem); 307 sem_destroy(&sem); 308 } 309 310 TEST_F(Getattr_7_8, ok) 311 { 312 const char FULLPATH[] = "mountpoint/some_file.txt"; 313 const char RELPATH[] = "some_file.txt"; 314 const uint64_t ino = 42; 315 struct stat sb; 316 317 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 318 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 319 SET_OUT_HEADER_LEN(out, entry_7_8); 320 out.body.entry.attr.mode = S_IFREG | 0644; 321 out.body.entry.nodeid = ino; 322 out.body.entry.attr.nlink = 1; 323 out.body.entry.attr.size = 1; 324 }))); 325 EXPECT_CALL(*m_mock, process( 326 ResultOf([](auto in) { 327 return (in.header.opcode == FUSE_GETATTR && 328 in.header.nodeid == ino); 329 }, Eq(true)), 330 _) 331 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 332 SET_OUT_HEADER_LEN(out, attr_7_8); 333 out.body.attr.attr.ino = ino; // Must match nodeid 334 out.body.attr.attr.mode = S_IFREG | 0644; 335 out.body.attr.attr.size = 1; 336 out.body.attr.attr.blocks = 2; 337 out.body.attr.attr.atime = 3; 338 out.body.attr.attr.mtime = 4; 339 out.body.attr.attr.ctime = 5; 340 out.body.attr.attr.atimensec = 6; 341 out.body.attr.attr.mtimensec = 7; 342 out.body.attr.attr.ctimensec = 8; 343 out.body.attr.attr.nlink = 9; 344 out.body.attr.attr.uid = 10; 345 out.body.attr.attr.gid = 11; 346 out.body.attr.attr.rdev = 12; 347 }))); 348 349 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 350 EXPECT_EQ(1, sb.st_size); 351 EXPECT_EQ(2, sb.st_blocks); 352 EXPECT_EQ(3, sb.st_atim.tv_sec); 353 EXPECT_EQ(6, sb.st_atim.tv_nsec); 354 EXPECT_EQ(4, sb.st_mtim.tv_sec); 355 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 356 EXPECT_EQ(5, sb.st_ctim.tv_sec); 357 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 358 EXPECT_EQ(9ull, sb.st_nlink); 359 EXPECT_EQ(10ul, sb.st_uid); 360 EXPECT_EQ(11ul, sb.st_gid); 361 EXPECT_EQ(12ul, sb.st_rdev); 362 EXPECT_EQ(ino, sb.st_ino); 363 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 364 365 //st_birthtim and st_flags are not supported by protocol 7.8. They're 366 //only supported as OS-specific extensions to OSX. 367 } 368