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