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