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