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 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 }))); 163 164 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 165 EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize); 166 } 167 168 TEST_F(Getattr, enoent) 169 { 170 const char FULLPATH[] = "mountpoint/some_file.txt"; 171 const char RELPATH[] = "some_file.txt"; 172 struct stat sb; 173 const uint64_t ino = 42; 174 175 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0); 176 EXPECT_CALL(*m_mock, process( 177 ResultOf([](auto in) { 178 return (in.header.opcode == FUSE_GETATTR && 179 in.header.nodeid == ino); 180 }, Eq(true)), 181 _) 182 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 183 EXPECT_NE(0, stat(FULLPATH, &sb)); 184 EXPECT_EQ(ENOENT, errno); 185 } 186 187 TEST_F(Getattr, ok) 188 { 189 const char FULLPATH[] = "mountpoint/some_file.txt"; 190 const char RELPATH[] = "some_file.txt"; 191 const uint64_t ino = 42; 192 struct stat sb; 193 194 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0); 195 EXPECT_CALL(*m_mock, process( 196 ResultOf([](auto in) { 197 return (in.header.opcode == FUSE_GETATTR && 198 in.body.getattr.getattr_flags == 0 && 199 in.header.nodeid == ino); 200 }, Eq(true)), 201 _) 202 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 203 SET_OUT_HEADER_LEN(out, attr); 204 out.body.attr.attr.ino = ino; // Must match nodeid 205 out.body.attr.attr.mode = S_IFREG | 0644; 206 out.body.attr.attr.size = 1; 207 out.body.attr.attr.blocks = 2; 208 out.body.attr.attr.atime = 3; 209 out.body.attr.attr.mtime = 4; 210 out.body.attr.attr.ctime = 5; 211 out.body.attr.attr.atimensec = 6; 212 out.body.attr.attr.mtimensec = 7; 213 out.body.attr.attr.ctimensec = 8; 214 out.body.attr.attr.nlink = 9; 215 out.body.attr.attr.uid = 10; 216 out.body.attr.attr.gid = 11; 217 out.body.attr.attr.rdev = 12; 218 out.body.attr.attr.blksize = 12345; 219 }))); 220 221 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 222 EXPECT_EQ(1, sb.st_size); 223 EXPECT_EQ(2, sb.st_blocks); 224 EXPECT_EQ(3, sb.st_atim.tv_sec); 225 EXPECT_EQ(6, sb.st_atim.tv_nsec); 226 EXPECT_EQ(4, sb.st_mtim.tv_sec); 227 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 228 EXPECT_EQ(5, sb.st_ctim.tv_sec); 229 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 230 EXPECT_EQ(9ull, sb.st_nlink); 231 EXPECT_EQ(10ul, sb.st_uid); 232 EXPECT_EQ(11ul, sb.st_gid); 233 EXPECT_EQ(12ul, sb.st_rdev); 234 EXPECT_EQ((blksize_t)12345, sb.st_blksize); 235 EXPECT_EQ(ino, sb.st_ino); 236 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 237 238 //st_birthtim and st_flags are not supported by protocol 7.8. They're 239 //only supported as OS-specific extensions to OSX. 240 //EXPECT_EQ(, sb.st_birthtim); 241 //EXPECT_EQ(, sb.st_flags); 242 243 //FUSE can't set st_blksize until protocol 7.9 244 } 245 246 TEST_F(Getattr_7_8, ok) 247 { 248 const char FULLPATH[] = "mountpoint/some_file.txt"; 249 const char RELPATH[] = "some_file.txt"; 250 const uint64_t ino = 42; 251 struct stat sb; 252 253 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 254 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 255 SET_OUT_HEADER_LEN(out, entry_7_8); 256 out.body.entry.attr.mode = S_IFREG | 0644; 257 out.body.entry.nodeid = ino; 258 out.body.entry.attr.nlink = 1; 259 out.body.entry.attr.size = 1; 260 }))); 261 EXPECT_CALL(*m_mock, process( 262 ResultOf([](auto in) { 263 return (in.header.opcode == FUSE_GETATTR && 264 in.header.nodeid == ino); 265 }, Eq(true)), 266 _) 267 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 268 SET_OUT_HEADER_LEN(out, attr_7_8); 269 out.body.attr.attr.ino = ino; // Must match nodeid 270 out.body.attr.attr.mode = S_IFREG | 0644; 271 out.body.attr.attr.size = 1; 272 out.body.attr.attr.blocks = 2; 273 out.body.attr.attr.atime = 3; 274 out.body.attr.attr.mtime = 4; 275 out.body.attr.attr.ctime = 5; 276 out.body.attr.attr.atimensec = 6; 277 out.body.attr.attr.mtimensec = 7; 278 out.body.attr.attr.ctimensec = 8; 279 out.body.attr.attr.nlink = 9; 280 out.body.attr.attr.uid = 10; 281 out.body.attr.attr.gid = 11; 282 out.body.attr.attr.rdev = 12; 283 }))); 284 285 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 286 EXPECT_EQ(1, sb.st_size); 287 EXPECT_EQ(2, sb.st_blocks); 288 EXPECT_EQ(3, sb.st_atim.tv_sec); 289 EXPECT_EQ(6, sb.st_atim.tv_nsec); 290 EXPECT_EQ(4, sb.st_mtim.tv_sec); 291 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 292 EXPECT_EQ(5, sb.st_ctim.tv_sec); 293 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 294 EXPECT_EQ(9ull, sb.st_nlink); 295 EXPECT_EQ(10ul, sb.st_uid); 296 EXPECT_EQ(11ul, sb.st_gid); 297 EXPECT_EQ(12ul, sb.st_rdev); 298 EXPECT_EQ(ino, sb.st_ino); 299 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 300 301 //st_birthtim and st_flags are not supported by protocol 7.8. They're 302 //only supported as OS-specific extensions to OSX. 303 } 304