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 the 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 /* 97 * The fuse daemon fails the request with EEXIST. This usually indicates a 98 * race condition: some other FUSE client created the file in between when the 99 * kernel checked for it with lookup and tried to create it with create 100 */ 101 TEST_F(Create, eexist) 102 { 103 const char FULLPATH[] = "mountpoint/some_file.txt"; 104 const char RELPATH[] = "some_file.txt"; 105 mode_t mode = S_IFREG | 0755; 106 107 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 108 expect_create(RELPATH, mode, ReturnErrno(EEXIST)); 109 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 110 EXPECT_EQ(EEXIST, errno); 111 } 112 113 /* 114 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 115 * to FUSE_MKNOD/FUSE_OPEN 116 */ 117 TEST_F(Create, Enosys) 118 { 119 const char FULLPATH[] = "mountpoint/some_file.txt"; 120 const char RELPATH[] = "some_file.txt"; 121 mode_t mode = S_IFREG | 0755; 122 uint64_t ino = 42; 123 int fd; 124 125 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 126 expect_create(RELPATH, mode, ReturnErrno(ENOSYS)); 127 128 EXPECT_CALL(*m_mock, process( 129 ResultOf([=](auto in) { 130 const char *name = (const char*)in->body.bytes + 131 sizeof(fuse_mknod_in); 132 return (in->header.opcode == FUSE_MKNOD && 133 in->body.mknod.mode == (S_IFREG | mode) && 134 in->body.mknod.rdev == 0 && 135 (0 == strcmp(RELPATH, name))); 136 }, Eq(true)), 137 _) 138 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 139 SET_OUT_HEADER_LEN(out, entry); 140 out->body.entry.attr.mode = mode; 141 out->body.entry.nodeid = ino; 142 out->body.entry.entry_valid = UINT64_MAX; 143 out->body.entry.attr_valid = UINT64_MAX; 144 }))); 145 146 EXPECT_CALL(*m_mock, process( 147 ResultOf([=](auto in) { 148 return (in->header.opcode == FUSE_OPEN && 149 in->header.nodeid == ino); 150 }, Eq(true)), 151 _) 152 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 153 out->header.len = sizeof(out->header); 154 SET_OUT_HEADER_LEN(out, open); 155 }))); 156 157 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 158 EXPECT_LE(0, fd) << strerror(errno); 159 /* Deliberately leak fd. close(2) will be tested in release.cc */ 160 } 161 162 /* 163 * Creating a new file after FUSE_LOOKUP returned a negative cache entry 164 */ 165 TEST_F(Create, entry_cache_negative) 166 { 167 const char FULLPATH[] = "mountpoint/some_file.txt"; 168 const char RELPATH[] = "some_file.txt"; 169 mode_t mode = S_IFREG | 0755; 170 uint64_t ino = 42; 171 int fd; 172 /* 173 * Set entry_valid = 0 because this test isn't concerned with whether 174 * or not we actually cache negative entries, only with whether we 175 * interpret negative cache responses correctly. 176 */ 177 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 178 179 /* create will first do a LOOKUP, adding a negative cache entry */ 180 EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 181 expect_create(RELPATH, mode, 182 ReturnImmediate([=](auto in __unused, auto out) { 183 SET_OUT_HEADER_LEN(out, create); 184 out->body.create.entry.attr.mode = mode; 185 out->body.create.entry.nodeid = ino; 186 out->body.create.entry.entry_valid = UINT64_MAX; 187 out->body.create.entry.attr_valid = UINT64_MAX; 188 })); 189 190 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 191 ASSERT_LE(0, fd) << strerror(errno); 192 /* Deliberately leak fd. close(2) will be tested in release.cc */ 193 } 194 195 /* 196 * Creating a new file should purge any negative namecache entries 197 */ 198 TEST_F(Create, entry_cache_negative_purge) 199 { 200 const char FULLPATH[] = "mountpoint/some_file.txt"; 201 const char RELPATH[] = "some_file.txt"; 202 mode_t mode = S_IFREG | 0755; 203 uint64_t ino = 42; 204 int fd; 205 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 206 207 /* create will first do a LOOKUP, adding a negative cache entry */ 208 EXPECT_LOOKUP(1, RELPATH).Times(1) 209 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 210 .RetiresOnSaturation(); 211 212 /* Then the CREATE should purge the negative cache entry */ 213 expect_create(RELPATH, mode, 214 ReturnImmediate([=](auto in __unused, auto out) { 215 SET_OUT_HEADER_LEN(out, create); 216 out->body.create.entry.attr.mode = mode; 217 out->body.create.entry.nodeid = ino; 218 out->body.create.entry.attr_valid = UINT64_MAX; 219 })); 220 221 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 222 ASSERT_LE(0, fd) << strerror(errno); 223 224 /* Finally, a subsequent lookup should query the daemon */ 225 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 226 227 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 228 /* Deliberately leak fd. close(2) will be tested in release.cc */ 229 } 230 231 /* 232 * The daemon is responsible for checking file permissions (unless the 233 * default_permissions mount option was used) 234 */ 235 TEST_F(Create, eperm) 236 { 237 const char FULLPATH[] = "mountpoint/some_file.txt"; 238 const char RELPATH[] = "some_file.txt"; 239 mode_t mode = S_IFREG | 0755; 240 241 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 242 expect_create(RELPATH, mode, ReturnErrno(EPERM)); 243 244 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 245 EXPECT_EQ(EPERM, errno); 246 } 247 248 TEST_F(Create, ok) 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 256 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 257 expect_create(RELPATH, mode, 258 ReturnImmediate([=](auto in __unused, auto out) { 259 SET_OUT_HEADER_LEN(out, create); 260 out->body.create.entry.attr.mode = mode; 261 out->body.create.entry.nodeid = ino; 262 out->body.create.entry.entry_valid = UINT64_MAX; 263 out->body.create.entry.attr_valid = UINT64_MAX; 264 })); 265 266 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 267 EXPECT_LE(0, fd) << strerror(errno); 268 /* Deliberately leak fd. close(2) will be tested in release.cc */ 269 } 270 271 /* Create a unix-domain socket */ 272 TEST_F(Create, socket) 273 { 274 const char FULLPATH[] = "mountpoint/some_sock"; 275 const char RELPATH[] = "some_sock"; 276 mode_t mode = S_IFSOCK | 0755; 277 struct sockaddr_un sa; 278 uint64_t ino = 42; 279 int fd; 280 281 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 282 expect_create(RELPATH, mode, 283 ReturnImmediate([=](auto in __unused, auto out) { 284 SET_OUT_HEADER_LEN(out, create); 285 out->body.create.entry.attr.mode = mode; 286 out->body.create.entry.nodeid = ino; 287 out->body.create.entry.entry_valid = UINT64_MAX; 288 out->body.create.entry.attr_valid = UINT64_MAX; 289 })); 290 291 fd = socket(AF_UNIX, SOCK_STREAM, 0); 292 ASSERT_LE(0, fd) << strerror(errno); 293 sa.sun_family = AF_UNIX; 294 strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path)); 295 ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa))) 296 << strerror(errno); 297 } 298 299 /* 300 * A regression test for a bug that affected old FUSE implementations: 301 * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 302 * contradiction between O_WRONLY and 0444 303 * 304 * For example: 305 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 306 */ 307 TEST_F(Create, wronly_0444) 308 { 309 const char FULLPATH[] = "mountpoint/some_file.txt"; 310 const char RELPATH[] = "some_file.txt"; 311 mode_t mode = S_IFREG | 0444; 312 uint64_t ino = 42; 313 int fd; 314 315 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 316 expect_create(RELPATH, mode, 317 ReturnImmediate([=](auto in __unused, auto out) { 318 SET_OUT_HEADER_LEN(out, create); 319 out->body.create.entry.attr.mode = mode; 320 out->body.create.entry.nodeid = ino; 321 out->body.create.entry.entry_valid = UINT64_MAX; 322 out->body.create.entry.attr_valid = UINT64_MAX; 323 })); 324 325 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 326 EXPECT_LE(0, fd) << strerror(errno); 327 /* Deliberately leak fd. close(2) will be tested in release.cc */ 328 } 329