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 31 extern "C" { 32 #include <fcntl.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Create: public FuseTest {}; 41 42 /* 43 * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the 44 * attribute cache 45 */ 46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ 47 TEST_F(Create, DISABLED_attr_cache) 48 { 49 const char FULLPATH[] = "mountpoint/some_file.txt"; 50 const char RELPATH[] = "some_file.txt"; 51 mode_t mode = 0755; 52 uint64_t ino = 42; 53 int fd; 54 55 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 56 57 EXPECT_CALL(*m_mock, process( 58 ResultOf([=](auto in) { 59 const char *name = (const char*)in->body.bytes + 60 sizeof(fuse_open_in); 61 return (in->header.opcode == FUSE_CREATE && 62 (0 == strcmp(RELPATH, name))); 63 }, Eq(true)), 64 _) 65 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 66 SET_OUT_HEADER_LEN(out, create); 67 out->body.create.entry.attr.mode = S_IFREG | mode; 68 out->body.create.entry.nodeid = ino; 69 out->body.create.entry.entry_valid = UINT64_MAX; 70 out->body.create.entry.attr_valid = UINT64_MAX; 71 }))); 72 73 EXPECT_CALL(*m_mock, process( 74 ResultOf([=](auto in) { 75 return (in->header.opcode == FUSE_GETATTR && 76 in->header.nodeid == ino); 77 }, Eq(true)), 78 _) 79 ).Times(0); 80 81 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 82 EXPECT_LE(0, fd) << strerror(errno); 83 /* Deliberately leak fd. close(2) will be tested in release.cc */ 84 } 85 86 /* 87 * The fuse daemon fails the request with EEXIST. This usually indicates a 88 * race condition: some other FUSE client created the file in between when the 89 * kernel checked for it with lookup and tried to create it with create 90 */ 91 TEST_F(Create, eexist) 92 { 93 const char FULLPATH[] = "mountpoint/some_file.txt"; 94 const char RELPATH[] = "some_file.txt"; 95 mode_t mode = 0755; 96 97 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 98 99 EXPECT_CALL(*m_mock, process( 100 ResultOf([=](auto in) { 101 const char *name = (const char*)in->body.bytes + 102 sizeof(fuse_open_in); 103 return (in->header.opcode == FUSE_CREATE && 104 (0 == strcmp(RELPATH, name))); 105 }, Eq(true)), 106 _) 107 ).WillOnce(Invoke(ReturnErrno(EEXIST))); 108 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 109 EXPECT_EQ(EEXIST, errno); 110 } 111 112 /* 113 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 114 * to FUSE_MKNOD/FUSE_OPEN 115 */ 116 TEST_F(Create, Enosys) 117 { 118 const char FULLPATH[] = "mountpoint/some_file.txt"; 119 const char RELPATH[] = "some_file.txt"; 120 mode_t mode = 0755; 121 uint64_t ino = 42; 122 int fd; 123 124 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 125 126 EXPECT_CALL(*m_mock, process( 127 ResultOf([=](auto in) { 128 const char *name = (const char*)in->body.bytes + 129 sizeof(fuse_open_in); 130 return (in->header.opcode == FUSE_CREATE && 131 (0 == strcmp(RELPATH, name))); 132 }, Eq(true)), 133 _) 134 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 135 136 EXPECT_CALL(*m_mock, process( 137 ResultOf([=](auto in) { 138 const char *name = (const char*)in->body.bytes + 139 sizeof(fuse_mknod_in); 140 return (in->header.opcode == FUSE_MKNOD && 141 in->body.mknod.mode == (S_IFREG | mode) && 142 in->body.mknod.rdev == 0 && 143 (0 == strcmp(RELPATH, name))); 144 }, Eq(true)), 145 _) 146 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 147 SET_OUT_HEADER_LEN(out, entry); 148 out->body.entry.attr.mode = S_IFREG | mode; 149 out->body.entry.nodeid = ino; 150 out->body.entry.entry_valid = UINT64_MAX; 151 out->body.entry.attr_valid = UINT64_MAX; 152 }))); 153 154 EXPECT_CALL(*m_mock, process( 155 ResultOf([=](auto in) { 156 return (in->header.opcode == FUSE_OPEN && 157 in->header.nodeid == ino); 158 }, Eq(true)), 159 _) 160 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 161 out->header.len = sizeof(out->header); 162 SET_OUT_HEADER_LEN(out, open); 163 }))); 164 165 /* Until the attr cache is working, we may send an additional GETATTR */ 166 EXPECT_CALL(*m_mock, process( 167 ResultOf([=](auto in) { 168 return (in->header.opcode == FUSE_GETATTR && 169 in->header.nodeid == ino); 170 }, Eq(true)), 171 _) 172 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 173 SET_OUT_HEADER_LEN(out, attr); 174 out->body.attr.attr.ino = ino; // Must match nodeid 175 out->body.attr.attr.mode = S_IFREG | 0644; 176 }))); 177 178 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 179 EXPECT_LE(0, fd) << strerror(errno); 180 /* Deliberately leak fd. close(2) will be tested in release.cc */ 181 } 182 183 /* 184 * Creating a new file after FUSE_LOOKUP returned a negative cache entry 185 */ 186 TEST_F(Create, entry_cache_negative) 187 { 188 const char FULLPATH[] = "mountpoint/some_file.txt"; 189 const char RELPATH[] = "some_file.txt"; 190 mode_t mode = 0755; 191 uint64_t ino = 42; 192 int fd; 193 /* 194 * Set entry_valid = 0 because this test isn't concerned with whether 195 * or not we actually cache negative entries, only with whether we 196 * interpret negative cache responses correctly. 197 */ 198 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 199 200 /* create will first do a LOOKUP, adding a negative cache entry */ 201 EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 202 203 EXPECT_CALL(*m_mock, process( 204 ResultOf([=](auto in) { 205 const char *name = (const char*)in->body.bytes + 206 sizeof(fuse_open_in); 207 return (in->header.opcode == FUSE_CREATE && 208 (0 == strcmp(RELPATH, name))); 209 }, Eq(true)), 210 _) 211 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 212 SET_OUT_HEADER_LEN(out, create); 213 out->body.create.entry.attr.mode = S_IFREG | mode; 214 out->body.create.entry.nodeid = ino; 215 out->body.create.entry.entry_valid = UINT64_MAX; 216 out->body.create.entry.attr_valid = UINT64_MAX; 217 }))); 218 219 /* Until the attr cache is working, we may send an additional GETATTR */ 220 EXPECT_CALL(*m_mock, process( 221 ResultOf([=](auto in) { 222 return (in->header.opcode == FUSE_GETATTR && 223 in->header.nodeid == ino); 224 }, Eq(true)), 225 _) 226 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 227 SET_OUT_HEADER_LEN(out, attr); 228 out->body.attr.attr.ino = ino; // Must match nodeid 229 out->body.attr.attr.mode = S_IFREG | 0644; 230 }))); 231 232 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 233 ASSERT_LE(0, fd) << strerror(errno); 234 /* Deliberately leak fd. close(2) will be tested in release.cc */ 235 } 236 237 /* 238 * Creating a new file should purge any negative namecache entries 239 */ 240 TEST_F(Create, entry_cache_negative_purge) 241 { 242 const char FULLPATH[] = "mountpoint/some_file.txt"; 243 const char RELPATH[] = "some_file.txt"; 244 mode_t mode = 0755; 245 uint64_t ino = 42; 246 int fd; 247 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 248 249 /* create will first do a LOOKUP, adding a negative cache entry */ 250 EXPECT_LOOKUP(1, RELPATH).Times(1) 251 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 252 .RetiresOnSaturation(); 253 254 /* Then the CREATE should purge the negative cache entry */ 255 EXPECT_CALL(*m_mock, process( 256 ResultOf([=](auto in) { 257 const char *name = (const char*)in->body.bytes + 258 sizeof(fuse_open_in); 259 return (in->header.opcode == FUSE_CREATE && 260 (0 == strcmp(RELPATH, name))); 261 }, Eq(true)), 262 _) 263 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 264 SET_OUT_HEADER_LEN(out, create); 265 out->body.create.entry.attr.mode = S_IFREG | mode; 266 out->body.create.entry.nodeid = ino; 267 out->body.create.entry.attr_valid = UINT64_MAX; 268 }))); 269 270 /* Until the attr cache is working, we may send an additional GETATTR */ 271 EXPECT_CALL(*m_mock, process( 272 ResultOf([=](auto in) { 273 return (in->header.opcode == FUSE_GETATTR && 274 in->header.nodeid == ino); 275 }, Eq(true)), 276 _) 277 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 278 SET_OUT_HEADER_LEN(out, attr); 279 out->body.attr.attr.ino = ino; // Must match nodeid 280 out->body.attr.attr.mode = S_IFREG | 0644; 281 }))); 282 283 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 284 ASSERT_LE(0, fd) << strerror(errno); 285 286 /* Finally, a subsequent lookup should query the daemon */ 287 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 288 289 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 290 /* Deliberately leak fd. close(2) will be tested in release.cc */ 291 } 292 293 /* 294 * The daemon is responsible for checking file permissions (unless the 295 * default_permissions mount option was used) 296 */ 297 TEST_F(Create, eperm) 298 { 299 const char FULLPATH[] = "mountpoint/some_file.txt"; 300 const char RELPATH[] = "some_file.txt"; 301 mode_t mode = 0755; 302 303 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 304 305 EXPECT_CALL(*m_mock, process( 306 ResultOf([=](auto in) { 307 const char *name = (const char*)in->body.bytes + 308 sizeof(fuse_open_in); 309 return (in->header.opcode == FUSE_CREATE && 310 (0 == strcmp(RELPATH, name))); 311 }, Eq(true)), 312 _) 313 ).WillOnce(Invoke(ReturnErrno(EPERM))); 314 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 315 EXPECT_EQ(EPERM, errno); 316 } 317 318 TEST_F(Create, ok) 319 { 320 const char FULLPATH[] = "mountpoint/some_file.txt"; 321 const char RELPATH[] = "some_file.txt"; 322 mode_t mode = 0755; 323 uint64_t ino = 42; 324 int fd; 325 326 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 327 328 EXPECT_CALL(*m_mock, process( 329 ResultOf([=](auto in) { 330 const char *name = (const char*)in->body.bytes + 331 sizeof(fuse_open_in); 332 return (in->header.opcode == FUSE_CREATE && 333 (0 == strcmp(RELPATH, name))); 334 }, Eq(true)), 335 _) 336 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 337 SET_OUT_HEADER_LEN(out, create); 338 out->body.create.entry.attr.mode = S_IFREG | mode; 339 out->body.create.entry.nodeid = ino; 340 out->body.create.entry.entry_valid = UINT64_MAX; 341 out->body.create.entry.attr_valid = UINT64_MAX; 342 }))); 343 344 /* Until the attr cache is working, we may send an additional GETATTR */ 345 EXPECT_CALL(*m_mock, process( 346 ResultOf([=](auto in) { 347 return (in->header.opcode == FUSE_GETATTR && 348 in->header.nodeid == ino); 349 }, Eq(true)), 350 _) 351 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 352 SET_OUT_HEADER_LEN(out, attr); 353 out->body.attr.attr.ino = ino; // Must match nodeid 354 out->body.attr.attr.mode = S_IFREG | 0644; 355 }))); 356 357 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 358 EXPECT_LE(0, fd) << strerror(errno); 359 /* Deliberately leak fd. close(2) will be tested in release.cc */ 360 } 361