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