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 * A regression test for a bug that affected old FUSE implementations: 375 * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 376 * contradiction between O_WRONLY and 0444 377 * 378 * For example: 379 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 380 */ 381 TEST_F(Create, wronly_0444) 382 { 383 const char FULLPATH[] = "mountpoint/some_file.txt"; 384 const char RELPATH[] = "some_file.txt"; 385 mode_t mode = S_IFREG | 0444; 386 uint64_t ino = 42; 387 int fd; 388 389 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 390 .WillOnce(Invoke(ReturnErrno(ENOENT))); 391 expect_create(RELPATH, mode, 392 ReturnImmediate([=](auto in __unused, auto& out) { 393 SET_OUT_HEADER_LEN(out, create); 394 out.body.create.entry.attr.mode = mode; 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 400 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 401 ASSERT_LE(0, fd) << strerror(errno); 402 leak(fd); 403 } 404 405 TEST_F(Create_7_8, ok) 406 { 407 const char FULLPATH[] = "mountpoint/some_file.txt"; 408 const char RELPATH[] = "some_file.txt"; 409 mode_t mode = S_IFREG | 0755; 410 uint64_t ino = 42; 411 int fd; 412 413 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 414 .WillOnce(Invoke(ReturnErrno(ENOENT))); 415 expect_create(RELPATH, mode, 416 ReturnImmediate([=](auto in __unused, auto& out) { 417 SET_OUT_HEADER_LEN(out, create_7_8); 418 out.body.create.entry.attr.mode = mode; 419 out.body.create.entry.nodeid = ino; 420 out.body.create.entry.entry_valid = UINT64_MAX; 421 out.body.create.entry.attr_valid = UINT64_MAX; 422 })); 423 424 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 425 ASSERT_LE(0, fd) << strerror(errno); 426 leak(fd); 427 } 428 429 TEST_F(Create_7_11, ok) 430 { 431 const char FULLPATH[] = "mountpoint/some_file.txt"; 432 const char RELPATH[] = "some_file.txt"; 433 mode_t mode = S_IFREG | 0755; 434 uint64_t ino = 42; 435 int fd; 436 437 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 438 .WillOnce(Invoke(ReturnErrno(ENOENT))); 439 expect_create(RELPATH, mode, 440 ReturnImmediate([=](auto in __unused, auto& out) { 441 SET_OUT_HEADER_LEN(out, create); 442 out.body.create.entry.attr.mode = mode; 443 out.body.create.entry.nodeid = ino; 444 out.body.create.entry.entry_valid = UINT64_MAX; 445 out.body.create.entry.attr_valid = UINT64_MAX; 446 })); 447 448 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 449 ASSERT_LE(0, fd) << strerror(errno); 450 leak(fd); 451 } 452