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 //st_birthtim and st_flags are not supported by protocol 7.8. They're 250 //only supported as OS-specific extensions to OSX. 251 //EXPECT_EQ(, sb.st_birthtim); 252 //EXPECT_EQ(, sb.st_flags); 253 254 //FUSE can't set st_blksize until protocol 7.9 255 } 256 257 /* 258 * FUSE_GETATTR returns a different file type, even though the entry cache 259 * hasn't expired. This is a server bug! It probably means that the server 260 * removed the file and recreated it with the same inode but a different vtyp. 261 * The best thing fusefs can do is return ENOENT to the caller. After all, the 262 * entry must not have existed recently. 263 */ 264 TEST_F(Getattr, vtyp_conflict) 265 { 266 const char FULLPATH[] = "mountpoint/some_file.txt"; 267 const char RELPATH[] = "some_file.txt"; 268 const uint64_t ino = 42; 269 struct stat sb; 270 sem_t sem; 271 272 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 273 274 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 275 .WillOnce(Invoke( 276 ReturnImmediate([=](auto in __unused, auto& out) { 277 SET_OUT_HEADER_LEN(out, entry); 278 out.body.entry.attr.mode = S_IFREG | 0644; 279 out.body.entry.nodeid = ino; 280 out.body.entry.attr.nlink = 1; 281 out.body.entry.attr_valid = 0; 282 out.body.entry.entry_valid = UINT64_MAX; 283 }))); 284 EXPECT_CALL(*m_mock, process( 285 ResultOf([](auto in) { 286 return (in.header.opcode == FUSE_GETATTR && 287 in.body.getattr.getattr_flags == 0 && 288 in.header.nodeid == ino); 289 }, Eq(true)), 290 _) 291 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 292 SET_OUT_HEADER_LEN(out, attr); 293 out.body.attr.attr.ino = ino; // Must match nodeid 294 out.body.attr.attr.mode = S_IFDIR | 0755; // Changed! 295 out.body.attr.attr.nlink = 2; 296 }))); 297 // We should reclaim stale vnodes 298 expect_forget(ino, 1, &sem); 299 300 ASSERT_NE(0, stat(FULLPATH, &sb)); 301 EXPECT_EQ(errno, ENOENT); 302 303 sem_wait(&sem); 304 sem_destroy(&sem); 305 } 306 307 TEST_F(Getattr_7_8, ok) 308 { 309 const char FULLPATH[] = "mountpoint/some_file.txt"; 310 const char RELPATH[] = "some_file.txt"; 311 const uint64_t ino = 42; 312 struct stat sb; 313 314 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 315 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 316 SET_OUT_HEADER_LEN(out, entry_7_8); 317 out.body.entry.attr.mode = S_IFREG | 0644; 318 out.body.entry.nodeid = ino; 319 out.body.entry.attr.nlink = 1; 320 out.body.entry.attr.size = 1; 321 }))); 322 EXPECT_CALL(*m_mock, process( 323 ResultOf([](auto in) { 324 return (in.header.opcode == FUSE_GETATTR && 325 in.header.nodeid == ino); 326 }, Eq(true)), 327 _) 328 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 329 SET_OUT_HEADER_LEN(out, attr_7_8); 330 out.body.attr.attr.ino = ino; // Must match nodeid 331 out.body.attr.attr.mode = S_IFREG | 0644; 332 out.body.attr.attr.size = 1; 333 out.body.attr.attr.blocks = 2; 334 out.body.attr.attr.atime = 3; 335 out.body.attr.attr.mtime = 4; 336 out.body.attr.attr.ctime = 5; 337 out.body.attr.attr.atimensec = 6; 338 out.body.attr.attr.mtimensec = 7; 339 out.body.attr.attr.ctimensec = 8; 340 out.body.attr.attr.nlink = 9; 341 out.body.attr.attr.uid = 10; 342 out.body.attr.attr.gid = 11; 343 out.body.attr.attr.rdev = 12; 344 }))); 345 346 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 347 EXPECT_EQ(1, sb.st_size); 348 EXPECT_EQ(2, sb.st_blocks); 349 EXPECT_EQ(3, sb.st_atim.tv_sec); 350 EXPECT_EQ(6, sb.st_atim.tv_nsec); 351 EXPECT_EQ(4, sb.st_mtim.tv_sec); 352 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 353 EXPECT_EQ(5, sb.st_ctim.tv_sec); 354 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 355 EXPECT_EQ(9ull, sb.st_nlink); 356 EXPECT_EQ(10ul, sb.st_uid); 357 EXPECT_EQ(11ul, sb.st_gid); 358 EXPECT_EQ(12ul, sb.st_rdev); 359 EXPECT_EQ(ino, sb.st_ino); 360 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 361 362 //st_birthtim and st_flags are not supported by protocol 7.8. They're 363 //only supported as OS-specific extensions to OSX. 364 } 365