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 <fcntl.h> 35 } 36 37 #include "mockfs.hh" 38 #include "utils.hh" 39 40 using namespace testing; 41 42 class Create: public FuseTest { 43 public: 44 45 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 46 { 47 mode_t mask = umask(0); 48 (void)umask(mask); 49 50 EXPECT_CALL(*m_mock, process( 51 ResultOf([=](auto in) { 52 const char *name = (const char*)in.body.bytes + 53 sizeof(fuse_create_in); 54 return (in.header.opcode == FUSE_CREATE && 55 in.body.create.mode == mode && 56 in.body.create.umask == mask && 57 (0 == strcmp(relpath, name))); 58 }, Eq(true)), 59 _) 60 ).WillOnce(Invoke(r)); 61 } 62 63 }; 64 65 /* FUSE_CREATE operations for a protocol 7.8 server */ 66 class Create_7_8: public Create { 67 public: 68 virtual void SetUp() { 69 m_kernel_minor_version = 8; 70 Create::SetUp(); 71 } 72 73 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 74 { 75 EXPECT_CALL(*m_mock, process( 76 ResultOf([=](auto in) { 77 const char *name = (const char*)in.body.bytes + 78 sizeof(fuse_open_in); 79 return (in.header.opcode == FUSE_CREATE && 80 in.body.create.mode == mode && 81 (0 == strcmp(relpath, name))); 82 }, Eq(true)), 83 _) 84 ).WillOnce(Invoke(r)); 85 } 86 87 }; 88 89 /* FUSE_CREATE operations for a server built at protocol <= 7.11 */ 90 class Create_7_11: public FuseTest { 91 public: 92 virtual void SetUp() { 93 m_kernel_minor_version = 11; 94 FuseTest::SetUp(); 95 } 96 97 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 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 in.body.create.mode == mode && 105 (0 == strcmp(relpath, name))); 106 }, Eq(true)), 107 _) 108 ).WillOnce(Invoke(r)); 109 } 110 111 }; 112 113 114 /* 115 * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the 116 * attribute cache 117 */ 118 TEST_F(Create, attr_cache) 119 { 120 const char FULLPATH[] = "mountpoint/some_file.txt"; 121 const char RELPATH[] = "some_file.txt"; 122 mode_t mode = S_IFREG | 0755; 123 uint64_t ino = 42; 124 int fd; 125 126 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 127 .WillOnce(Invoke(ReturnErrno(ENOENT))); 128 expect_create(RELPATH, mode, 129 ReturnImmediate([=](auto in __unused, auto& out) { 130 SET_OUT_HEADER_LEN(out, create); 131 out.body.create.entry.attr.mode = mode; 132 out.body.create.entry.nodeid = ino; 133 out.body.create.entry.entry_valid = UINT64_MAX; 134 out.body.create.entry.attr_valid = UINT64_MAX; 135 })); 136 137 EXPECT_CALL(*m_mock, process( 138 ResultOf([=](auto in) { 139 return (in.header.opcode == FUSE_GETATTR && 140 in.header.nodeid == ino); 141 }, Eq(true)), 142 _) 143 ).Times(0); 144 145 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 146 ASSERT_LE(0, fd) << strerror(errno); 147 leak(fd); 148 } 149 150 /* A successful CREATE operation should purge the parent dir's attr cache */ 151 TEST_F(Create, clear_attr_cache) 152 { 153 const char FULLPATH[] = "mountpoint/src"; 154 const char RELPATH[] = "src"; 155 mode_t mode = S_IFREG | 0755; 156 uint64_t ino = 42; 157 int fd; 158 struct stat sb; 159 160 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 161 .WillOnce(Invoke(ReturnErrno(ENOENT))); 162 EXPECT_CALL(*m_mock, process( 163 ResultOf([=](auto in) { 164 return (in.header.opcode == FUSE_GETATTR && 165 in.header.nodeid == FUSE_ROOT_ID); 166 }, Eq(true)), 167 _) 168 ).Times(2) 169 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 170 SET_OUT_HEADER_LEN(out, attr); 171 out.body.attr.attr.ino = FUSE_ROOT_ID; 172 out.body.attr.attr.mode = S_IFDIR | 0755; 173 out.body.attr.attr_valid = UINT64_MAX; 174 }))); 175 176 expect_create(RELPATH, mode, 177 ReturnImmediate([=](auto in __unused, auto& out) { 178 SET_OUT_HEADER_LEN(out, create); 179 out.body.create.entry.attr.mode = mode; 180 out.body.create.entry.nodeid = ino; 181 out.body.create.entry.entry_valid = UINT64_MAX; 182 out.body.create.entry.attr_valid = UINT64_MAX; 183 })); 184 185 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 186 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 187 ASSERT_LE(0, fd) << strerror(errno); 188 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 189 190 leak(fd); 191 } 192 193 /* 194 * The fuse daemon fails the request with EEXIST. This usually indicates a 195 * race condition: some other FUSE client created the file in between when the 196 * kernel checked for it with lookup and tried to create it with create 197 */ 198 TEST_F(Create, eexist) 199 { 200 const char FULLPATH[] = "mountpoint/some_file.txt"; 201 const char RELPATH[] = "some_file.txt"; 202 mode_t mode = S_IFREG | 0755; 203 204 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 205 .WillOnce(Invoke(ReturnErrno(ENOENT))); 206 expect_create(RELPATH, mode, ReturnErrno(EEXIST)); 207 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 208 EXPECT_EQ(EEXIST, errno); 209 } 210 211 /* 212 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 213 * to FUSE_MKNOD/FUSE_OPEN 214 */ 215 TEST_F(Create, Enosys) 216 { 217 const char FULLPATH[] = "mountpoint/some_file.txt"; 218 const char RELPATH[] = "some_file.txt"; 219 mode_t mode = S_IFREG | 0755; 220 uint64_t ino = 42; 221 int fd; 222 223 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 224 .WillOnce(Invoke(ReturnErrno(ENOENT))); 225 expect_create(RELPATH, mode, ReturnErrno(ENOSYS)); 226 227 EXPECT_CALL(*m_mock, process( 228 ResultOf([=](auto in) { 229 const char *name = (const char*)in.body.bytes + 230 sizeof(fuse_mknod_in); 231 return (in.header.opcode == FUSE_MKNOD && 232 in.body.mknod.mode == (S_IFREG | mode) && 233 in.body.mknod.rdev == 0 && 234 (0 == strcmp(RELPATH, name))); 235 }, Eq(true)), 236 _) 237 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 238 SET_OUT_HEADER_LEN(out, entry); 239 out.body.entry.attr.mode = mode; 240 out.body.entry.nodeid = ino; 241 out.body.entry.entry_valid = UINT64_MAX; 242 out.body.entry.attr_valid = UINT64_MAX; 243 }))); 244 245 EXPECT_CALL(*m_mock, process( 246 ResultOf([=](auto in) { 247 return (in.header.opcode == FUSE_OPEN && 248 in.header.nodeid == ino); 249 }, Eq(true)), 250 _) 251 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 252 out.header.len = sizeof(out.header); 253 SET_OUT_HEADER_LEN(out, open); 254 }))); 255 256 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 257 ASSERT_LE(0, fd) << strerror(errno); 258 leak(fd); 259 } 260 261 /* 262 * Creating a new file after FUSE_LOOKUP returned a negative cache entry 263 */ 264 TEST_F(Create, entry_cache_negative) 265 { 266 const char FULLPATH[] = "mountpoint/some_file.txt"; 267 const char RELPATH[] = "some_file.txt"; 268 mode_t mode = S_IFREG | 0755; 269 uint64_t ino = 42; 270 int fd; 271 /* 272 * Set entry_valid = 0 because this test isn't concerned with whether 273 * or not we actually cache negative entries, only with whether we 274 * interpret negative cache responses correctly. 275 */ 276 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 277 278 /* create will first do a LOOKUP, adding a negative cache entry */ 279 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 280 .WillOnce(ReturnNegativeCache(&entry_valid)); 281 expect_create(RELPATH, mode, 282 ReturnImmediate([=](auto in __unused, auto& out) { 283 SET_OUT_HEADER_LEN(out, create); 284 out.body.create.entry.attr.mode = mode; 285 out.body.create.entry.nodeid = ino; 286 out.body.create.entry.entry_valid = UINT64_MAX; 287 out.body.create.entry.attr_valid = UINT64_MAX; 288 })); 289 290 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 291 ASSERT_LE(0, fd) << strerror(errno); 292 leak(fd); 293 } 294 295 /* 296 * Creating a new file should purge any negative namecache entries 297 */ 298 TEST_F(Create, entry_cache_negative_purge) 299 { 300 const char FULLPATH[] = "mountpoint/some_file.txt"; 301 const char RELPATH[] = "some_file.txt"; 302 mode_t mode = S_IFREG | 0755; 303 uint64_t ino = 42; 304 int fd; 305 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 306 307 /* create will first do a LOOKUP, adding a negative cache entry */ 308 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH).Times(1) 309 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 310 .RetiresOnSaturation(); 311 312 /* Then the CREATE should purge the negative cache entry */ 313 expect_create(RELPATH, mode, 314 ReturnImmediate([=](auto in __unused, auto& out) { 315 SET_OUT_HEADER_LEN(out, create); 316 out.body.create.entry.attr.mode = mode; 317 out.body.create.entry.nodeid = ino; 318 out.body.create.entry.attr_valid = UINT64_MAX; 319 })); 320 321 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 322 ASSERT_LE(0, fd) << strerror(errno); 323 324 /* Finally, a subsequent lookup should query the daemon */ 325 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 326 327 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 328 leak(fd); 329 } 330 331 /* 332 * The daemon is responsible for checking file permissions (unless the 333 * default_permissions mount option was used) 334 */ 335 TEST_F(Create, eperm) 336 { 337 const char FULLPATH[] = "mountpoint/some_file.txt"; 338 const char RELPATH[] = "some_file.txt"; 339 mode_t mode = S_IFREG | 0755; 340 341 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 342 .WillOnce(Invoke(ReturnErrno(ENOENT))); 343 expect_create(RELPATH, mode, ReturnErrno(EPERM)); 344 345 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 346 EXPECT_EQ(EPERM, errno); 347 } 348 349 TEST_F(Create, ok) 350 { 351 const char FULLPATH[] = "mountpoint/some_file.txt"; 352 const char RELPATH[] = "some_file.txt"; 353 mode_t mode = S_IFREG | 0755; 354 uint64_t ino = 42; 355 int fd; 356 357 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 358 .WillOnce(Invoke(ReturnErrno(ENOENT))); 359 expect_create(RELPATH, mode, 360 ReturnImmediate([=](auto in __unused, auto& out) { 361 SET_OUT_HEADER_LEN(out, create); 362 out.body.create.entry.attr.mode = mode; 363 out.body.create.entry.nodeid = ino; 364 out.body.create.entry.entry_valid = UINT64_MAX; 365 out.body.create.entry.attr_valid = UINT64_MAX; 366 })); 367 368 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 369 ASSERT_LE(0, fd) << strerror(errno); 370 leak(fd); 371 } 372 373 /* 374 * Nothing bad should happen if the server returns the parent's inode number 375 * for the newly created file. Regression test for bug 263662 376 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 377 */ 378 TEST_F(Create, parent_inode) 379 { 380 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 381 const char RELDIRPATH[] = "some_dir"; 382 const char RELPATH[] = "some_file.txt"; 383 mode_t mode = 0755; 384 uint64_t ino = 42; 385 int fd; 386 387 expect_lookup(RELDIRPATH, ino, S_IFDIR | mode, 0, 1); 388 EXPECT_LOOKUP(ino, RELPATH) 389 .WillOnce(Invoke(ReturnErrno(ENOENT))); 390 expect_create(RELPATH, S_IFREG | mode, 391 ReturnImmediate([=](auto in __unused, auto& out) { 392 SET_OUT_HEADER_LEN(out, create); 393 out.body.create.entry.attr.mode = S_IFREG | mode; 394 /* Return the same inode as the parent dir */ 395 out.body.create.entry.nodeid = ino; 396 out.body.create.entry.entry_valid = UINT64_MAX; 397 out.body.create.entry.attr_valid = UINT64_MAX; 398 })); 399 // FUSE_RELEASE happens asynchronously, so it may or may not arrive 400 // before the test completes. 401 EXPECT_CALL(*m_mock, process( 402 ResultOf([=](auto in) { 403 return (in.header.opcode == FUSE_RELEASE); 404 }, Eq(true)), 405 _) 406 ).Times(AtMost(1)) 407 .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { })); 408 409 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 410 ASSERT_EQ(-1, fd); 411 EXPECT_EQ(EIO, errno); 412 } 413 414 /* 415 * A regression test for a bug that affected old FUSE implementations: 416 * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 417 * contradiction between O_WRONLY and 0444 418 * 419 * For example: 420 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 421 */ 422 TEST_F(Create, wronly_0444) 423 { 424 const char FULLPATH[] = "mountpoint/some_file.txt"; 425 const char RELPATH[] = "some_file.txt"; 426 mode_t mode = S_IFREG | 0444; 427 uint64_t ino = 42; 428 int fd; 429 430 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 431 .WillOnce(Invoke(ReturnErrno(ENOENT))); 432 expect_create(RELPATH, mode, 433 ReturnImmediate([=](auto in __unused, auto& out) { 434 SET_OUT_HEADER_LEN(out, create); 435 out.body.create.entry.attr.mode = mode; 436 out.body.create.entry.nodeid = ino; 437 out.body.create.entry.entry_valid = UINT64_MAX; 438 out.body.create.entry.attr_valid = UINT64_MAX; 439 })); 440 441 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 442 ASSERT_LE(0, fd) << strerror(errno); 443 leak(fd); 444 } 445 446 TEST_F(Create_7_8, ok) 447 { 448 const char FULLPATH[] = "mountpoint/some_file.txt"; 449 const char RELPATH[] = "some_file.txt"; 450 mode_t mode = S_IFREG | 0755; 451 uint64_t ino = 42; 452 int fd; 453 454 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 455 .WillOnce(Invoke(ReturnErrno(ENOENT))); 456 expect_create(RELPATH, mode, 457 ReturnImmediate([=](auto in __unused, auto& out) { 458 SET_OUT_HEADER_LEN(out, create_7_8); 459 out.body.create_7_8.entry.attr.mode = mode; 460 out.body.create_7_8.entry.nodeid = ino; 461 out.body.create_7_8.entry.entry_valid = UINT64_MAX; 462 out.body.create_7_8.entry.attr_valid = UINT64_MAX; 463 out.body.create_7_8.open.fh = FH; 464 })); 465 expect_flush(ino, 1, ReturnErrno(0)); 466 expect_release(ino, FH); 467 468 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 469 ASSERT_LE(0, fd) << strerror(errno); 470 close(fd); 471 } 472 473 TEST_F(Create_7_11, ok) 474 { 475 const char FULLPATH[] = "mountpoint/some_file.txt"; 476 const char RELPATH[] = "some_file.txt"; 477 mode_t mode = S_IFREG | 0755; 478 uint64_t ino = 42; 479 int fd; 480 481 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 482 .WillOnce(Invoke(ReturnErrno(ENOENT))); 483 expect_create(RELPATH, mode, 484 ReturnImmediate([=](auto in __unused, auto& out) { 485 SET_OUT_HEADER_LEN(out, create); 486 out.body.create.entry.attr.mode = mode; 487 out.body.create.entry.nodeid = ino; 488 out.body.create.entry.entry_valid = UINT64_MAX; 489 out.body.create.entry.attr_valid = UINT64_MAX; 490 })); 491 492 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 493 ASSERT_LE(0, fd) << strerror(errno); 494 leak(fd); 495 } 496