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