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 * $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 TEST_F(Getattr_7_8, ok) 260 { 261 const char FULLPATH[] = "mountpoint/some_file.txt"; 262 const char RELPATH[] = "some_file.txt"; 263 const uint64_t ino = 42; 264 struct stat sb; 265 266 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 267 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 268 SET_OUT_HEADER_LEN(out, entry_7_8); 269 out.body.entry.attr.mode = S_IFREG | 0644; 270 out.body.entry.nodeid = ino; 271 out.body.entry.attr.nlink = 1; 272 out.body.entry.attr.size = 1; 273 }))); 274 EXPECT_CALL(*m_mock, process( 275 ResultOf([](auto in) { 276 return (in.header.opcode == FUSE_GETATTR && 277 in.header.nodeid == ino); 278 }, Eq(true)), 279 _) 280 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 281 SET_OUT_HEADER_LEN(out, attr_7_8); 282 out.body.attr.attr.ino = ino; // Must match nodeid 283 out.body.attr.attr.mode = S_IFREG | 0644; 284 out.body.attr.attr.size = 1; 285 out.body.attr.attr.blocks = 2; 286 out.body.attr.attr.atime = 3; 287 out.body.attr.attr.mtime = 4; 288 out.body.attr.attr.ctime = 5; 289 out.body.attr.attr.atimensec = 6; 290 out.body.attr.attr.mtimensec = 7; 291 out.body.attr.attr.ctimensec = 8; 292 out.body.attr.attr.nlink = 9; 293 out.body.attr.attr.uid = 10; 294 out.body.attr.attr.gid = 11; 295 out.body.attr.attr.rdev = 12; 296 }))); 297 298 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 299 EXPECT_EQ(1, sb.st_size); 300 EXPECT_EQ(2, sb.st_blocks); 301 EXPECT_EQ(3, sb.st_atim.tv_sec); 302 EXPECT_EQ(6, sb.st_atim.tv_nsec); 303 EXPECT_EQ(4, sb.st_mtim.tv_sec); 304 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 305 EXPECT_EQ(5, sb.st_ctim.tv_sec); 306 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 307 EXPECT_EQ(9ull, sb.st_nlink); 308 EXPECT_EQ(10ul, sb.st_uid); 309 EXPECT_EQ(11ul, sb.st_gid); 310 EXPECT_EQ(12ul, sb.st_rdev); 311 EXPECT_EQ(ino, sb.st_ino); 312 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 313 314 //st_birthtim and st_flags are not supported by protocol 7.8. They're 315 //only supported as OS-specific extensions to OSX. 316 } 317