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