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