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