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 <sys/file.h> 33 #include <fcntl.h> 34 } 35 36 #include "mockfs.hh" 37 #include "utils.hh" 38 39 /* This flag value should probably be defined in fuse_kernel.h */ 40 #define OFFSET_MAX 0x7fffffffffffffffLL 41 42 using namespace testing; 43 44 /* For testing filesystems without posix locking support */ 45 class Fallback: public FuseTest { 46 public: 47 48 void expect_lookup(const char *relpath, uint64_t ino) 49 { 50 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); 51 } 52 53 }; 54 55 /* For testing filesystems with posix locking support */ 56 class Locks: public Fallback { 57 virtual void SetUp() { 58 m_init_flags = FUSE_POSIX_LOCKS; 59 Fallback::SetUp(); 60 } 61 }; 62 63 class Fcntl: public Locks { 64 public: 65 void expect_setlk(uint64_t ino, pid_t pid, uint64_t start, uint64_t end, 66 uint32_t type, int err) 67 { 68 EXPECT_CALL(*m_mock, process( 69 ResultOf([=](auto in) { 70 return (in.header.opcode == FUSE_SETLK && 71 in.header.nodeid == ino && 72 in.body.setlk.fh == FH && 73 in.body.setlkw.owner == (uint32_t)pid && 74 in.body.setlkw.lk.start == start && 75 in.body.setlkw.lk.end == end && 76 in.body.setlkw.lk.type == type && 77 in.body.setlkw.lk.pid == (uint64_t)pid); 78 }, Eq(true)), 79 _) 80 ).WillOnce(Invoke(ReturnErrno(err))); 81 } 82 }; 83 84 class Flock: public Locks { 85 public: 86 void expect_setlk(uint64_t ino, uint32_t type, int err) 87 { 88 EXPECT_CALL(*m_mock, process( 89 ResultOf([=](auto in) { 90 return (in.header.opcode == FUSE_SETLK && 91 in.header.nodeid == ino && 92 in.body.setlk.fh == FH && 93 /* 94 * The owner should be set to the address of 95 * the vnode. That's hard to verify. 96 */ 97 /* in.body.setlk.owner == ??? && */ 98 in.body.setlk.lk.type == type); 99 }, Eq(true)), 100 _) 101 ).WillOnce(Invoke(ReturnErrno(err))); 102 } 103 }; 104 105 class FlockFallback: public Fallback {}; 106 class GetlkFallback: public Fallback {}; 107 class Getlk: public Fcntl {}; 108 class SetlkFallback: public Fallback {}; 109 class Setlk: public Fcntl {}; 110 class SetlkwFallback: public Fallback {}; 111 class Setlkw: public Fcntl {}; 112 113 /* 114 * If the fuse filesystem does not support flock locks, then the kernel should 115 * fall back to local locks. 116 */ 117 TEST_F(FlockFallback, local) 118 { 119 const char FULLPATH[] = "mountpoint/some_file.txt"; 120 const char RELPATH[] = "some_file.txt"; 121 uint64_t ino = 42; 122 int fd; 123 124 expect_lookup(RELPATH, ino); 125 expect_open(ino, 0, 1); 126 127 fd = open(FULLPATH, O_RDWR); 128 ASSERT_LE(0, fd) << strerror(errno); 129 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno); 130 leak(fd); 131 } 132 133 /* 134 * Even if the fuse file system supports POSIX locks, we must implement flock 135 * locks locally until protocol 7.17. Protocol 7.9 added partial buggy support 136 * but we won't implement that. 137 */ 138 TEST_F(Flock, local) 139 { 140 const char FULLPATH[] = "mountpoint/some_file.txt"; 141 const char RELPATH[] = "some_file.txt"; 142 uint64_t ino = 42; 143 int fd; 144 145 expect_lookup(RELPATH, ino); 146 expect_open(ino, 0, 1); 147 148 fd = open(FULLPATH, O_RDWR); 149 ASSERT_LE(0, fd) << strerror(errno); 150 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno); 151 leak(fd); 152 } 153 154 /* Set a new flock lock with FUSE_SETLK */ 155 /* TODO: enable after upgrading to protocol 7.17 */ 156 TEST_F(Flock, DISABLED_set) 157 { 158 const char FULLPATH[] = "mountpoint/some_file.txt"; 159 const char RELPATH[] = "some_file.txt"; 160 uint64_t ino = 42; 161 int fd; 162 163 expect_lookup(RELPATH, ino); 164 expect_open(ino, 0, 1); 165 expect_setlk(ino, F_WRLCK, 0); 166 167 fd = open(FULLPATH, O_RDWR); 168 ASSERT_LE(0, fd) << strerror(errno); 169 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno); 170 leak(fd); 171 } 172 173 /* Fail to set a flock lock in non-blocking mode */ 174 /* TODO: enable after upgrading to protocol 7.17 */ 175 TEST_F(Flock, DISABLED_eagain) 176 { 177 const char FULLPATH[] = "mountpoint/some_file.txt"; 178 const char RELPATH[] = "some_file.txt"; 179 uint64_t ino = 42; 180 int fd; 181 182 expect_lookup(RELPATH, ino); 183 expect_open(ino, 0, 1); 184 expect_setlk(ino, F_WRLCK, EAGAIN); 185 186 fd = open(FULLPATH, O_RDWR); 187 ASSERT_LE(0, fd) << strerror(errno); 188 ASSERT_NE(0, flock(fd, LOCK_EX | LOCK_NB)); 189 ASSERT_EQ(EAGAIN, errno); 190 leak(fd); 191 } 192 193 /* 194 * If the fuse filesystem does not support posix file locks, then the kernel 195 * should fall back to local locks. 196 */ 197 TEST_F(GetlkFallback, local) 198 { 199 const char FULLPATH[] = "mountpoint/some_file.txt"; 200 const char RELPATH[] = "some_file.txt"; 201 uint64_t ino = 42; 202 struct flock fl; 203 int fd; 204 205 expect_lookup(RELPATH, ino); 206 expect_open(ino, 0, 1); 207 208 fd = open(FULLPATH, O_RDWR); 209 ASSERT_LE(0, fd) << strerror(errno); 210 fl.l_start = 10; 211 fl.l_len = 1000; 212 fl.l_pid = getpid(); 213 fl.l_type = F_RDLCK; 214 fl.l_whence = SEEK_SET; 215 fl.l_sysid = 0; 216 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); 217 leak(fd); 218 } 219 220 /* 221 * If the filesystem has no locks that fit the description, the filesystem 222 * should return F_UNLCK 223 */ 224 TEST_F(Getlk, no_locks) 225 { 226 const char FULLPATH[] = "mountpoint/some_file.txt"; 227 const char RELPATH[] = "some_file.txt"; 228 uint64_t ino = 42; 229 struct flock fl; 230 int fd; 231 pid_t pid = 1234; 232 233 expect_lookup(RELPATH, ino); 234 expect_open(ino, 0, 1); 235 EXPECT_CALL(*m_mock, process( 236 ResultOf([=](auto in) { 237 return (in.header.opcode == FUSE_GETLK && 238 in.header.nodeid == ino && 239 in.body.getlk.fh == FH && 240 in.body.getlk.owner == (uint32_t)pid && 241 in.body.getlk.lk.start == 10 && 242 in.body.getlk.lk.end == 1009 && 243 in.body.getlk.lk.type == F_RDLCK && 244 in.body.getlk.lk.pid == (uint64_t)pid); 245 }, Eq(true)), 246 _) 247 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 248 SET_OUT_HEADER_LEN(out, getlk); 249 out.body.getlk.lk = in.body.getlk.lk; 250 out.body.getlk.lk.type = F_UNLCK; 251 }))); 252 253 fd = open(FULLPATH, O_RDWR); 254 ASSERT_LE(0, fd) << strerror(errno); 255 fl.l_start = 10; 256 fl.l_len = 1000; 257 fl.l_pid = pid; 258 fl.l_type = F_RDLCK; 259 fl.l_whence = SEEK_SET; 260 fl.l_sysid = 0; 261 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); 262 ASSERT_EQ(F_UNLCK, fl.l_type); 263 leak(fd); 264 } 265 266 /* A different pid does have a lock */ 267 TEST_F(Getlk, lock_exists) 268 { 269 const char FULLPATH[] = "mountpoint/some_file.txt"; 270 const char RELPATH[] = "some_file.txt"; 271 uint64_t ino = 42; 272 struct flock fl; 273 int fd; 274 pid_t pid = 1234; 275 pid_t pid2 = 1235; 276 277 expect_lookup(RELPATH, ino); 278 expect_open(ino, 0, 1); 279 EXPECT_CALL(*m_mock, process( 280 ResultOf([=](auto in) { 281 return (in.header.opcode == FUSE_GETLK && 282 in.header.nodeid == ino && 283 in.body.getlk.fh == FH && 284 in.body.getlk.owner == (uint32_t)pid && 285 in.body.getlk.lk.start == 10 && 286 in.body.getlk.lk.end == 1009 && 287 in.body.getlk.lk.type == F_RDLCK && 288 in.body.getlk.lk.pid == (uint64_t)pid); 289 }, Eq(true)), 290 _) 291 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 292 SET_OUT_HEADER_LEN(out, getlk); 293 out.body.getlk.lk.start = 100; 294 out.body.getlk.lk.end = 199; 295 out.body.getlk.lk.type = F_WRLCK; 296 out.body.getlk.lk.pid = (uint32_t)pid2;; 297 }))); 298 299 fd = open(FULLPATH, O_RDWR); 300 ASSERT_LE(0, fd) << strerror(errno); 301 fl.l_start = 10; 302 fl.l_len = 1000; 303 fl.l_pid = pid; 304 fl.l_type = F_RDLCK; 305 fl.l_whence = SEEK_SET; 306 fl.l_sysid = 0; 307 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); 308 EXPECT_EQ(100, fl.l_start); 309 EXPECT_EQ(100, fl.l_len); 310 EXPECT_EQ(pid2, fl.l_pid); 311 EXPECT_EQ(F_WRLCK, fl.l_type); 312 EXPECT_EQ(SEEK_SET, fl.l_whence); 313 EXPECT_EQ(0, fl.l_sysid); 314 leak(fd); 315 } 316 317 /* 318 * If the fuse filesystem does not support posix file locks, then the kernel 319 * should fall back to local locks. 320 */ 321 TEST_F(SetlkFallback, local) 322 { 323 const char FULLPATH[] = "mountpoint/some_file.txt"; 324 const char RELPATH[] = "some_file.txt"; 325 uint64_t ino = 42; 326 struct flock fl; 327 int fd; 328 329 expect_lookup(RELPATH, ino); 330 expect_open(ino, 0, 1); 331 332 fd = open(FULLPATH, O_RDWR); 333 ASSERT_LE(0, fd) << strerror(errno); 334 fl.l_start = 10; 335 fl.l_len = 1000; 336 fl.l_pid = getpid(); 337 fl.l_type = F_RDLCK; 338 fl.l_whence = SEEK_SET; 339 fl.l_sysid = 0; 340 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 341 leak(fd); 342 } 343 344 /* Set a new lock with FUSE_SETLK */ 345 TEST_F(Setlk, set) 346 { 347 const char FULLPATH[] = "mountpoint/some_file.txt"; 348 const char RELPATH[] = "some_file.txt"; 349 uint64_t ino = 42; 350 struct flock fl; 351 int fd; 352 pid_t pid = 1234; 353 354 expect_lookup(RELPATH, ino); 355 expect_open(ino, 0, 1); 356 expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0); 357 358 fd = open(FULLPATH, O_RDWR); 359 ASSERT_LE(0, fd) << strerror(errno); 360 fl.l_start = 10; 361 fl.l_len = 1000; 362 fl.l_pid = pid; 363 fl.l_type = F_RDLCK; 364 fl.l_whence = SEEK_SET; 365 fl.l_sysid = 0; 366 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 367 leak(fd); 368 } 369 370 /* l_len = 0 is a flag value that means to lock until EOF */ 371 TEST_F(Setlk, set_eof) 372 { 373 const char FULLPATH[] = "mountpoint/some_file.txt"; 374 const char RELPATH[] = "some_file.txt"; 375 uint64_t ino = 42; 376 struct flock fl; 377 int fd; 378 pid_t pid = 1234; 379 380 expect_lookup(RELPATH, ino); 381 expect_open(ino, 0, 1); 382 expect_setlk(ino, pid, 10, OFFSET_MAX, F_RDLCK, 0); 383 384 fd = open(FULLPATH, O_RDWR); 385 ASSERT_LE(0, fd) << strerror(errno); 386 fl.l_start = 10; 387 fl.l_len = 0; 388 fl.l_pid = pid; 389 fl.l_type = F_RDLCK; 390 fl.l_whence = SEEK_SET; 391 fl.l_sysid = 0; 392 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 393 leak(fd); 394 } 395 396 /* Fail to set a new lock with FUSE_SETLK due to a conflict */ 397 TEST_F(Setlk, eagain) 398 { 399 const char FULLPATH[] = "mountpoint/some_file.txt"; 400 const char RELPATH[] = "some_file.txt"; 401 uint64_t ino = 42; 402 struct flock fl; 403 int fd; 404 pid_t pid = 1234; 405 406 expect_lookup(RELPATH, ino); 407 expect_open(ino, 0, 1); 408 expect_setlk(ino, pid, 10, 1009, F_RDLCK, EAGAIN); 409 410 fd = open(FULLPATH, O_RDWR); 411 ASSERT_LE(0, fd) << strerror(errno); 412 fl.l_start = 10; 413 fl.l_len = 1000; 414 fl.l_pid = pid; 415 fl.l_type = F_RDLCK; 416 fl.l_whence = SEEK_SET; 417 fl.l_sysid = 0; 418 ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl)); 419 ASSERT_EQ(EAGAIN, errno); 420 leak(fd); 421 } 422 423 /* 424 * If the fuse filesystem does not support posix file locks, then the kernel 425 * should fall back to local locks. 426 */ 427 TEST_F(SetlkwFallback, local) 428 { 429 const char FULLPATH[] = "mountpoint/some_file.txt"; 430 const char RELPATH[] = "some_file.txt"; 431 uint64_t ino = 42; 432 struct flock fl; 433 int fd; 434 435 expect_lookup(RELPATH, ino); 436 expect_open(ino, 0, 1); 437 438 fd = open(FULLPATH, O_RDWR); 439 ASSERT_LE(0, fd) << strerror(errno); 440 fl.l_start = 10; 441 fl.l_len = 1000; 442 fl.l_pid = getpid(); 443 fl.l_type = F_RDLCK; 444 fl.l_whence = SEEK_SET; 445 fl.l_sysid = 0; 446 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); 447 leak(fd); 448 } 449 450 /* 451 * Set a new lock with FUSE_SETLK. If the lock is not available, then the 452 * command should block. But to the kernel, that's the same as just being 453 * slow, so we don't need a separate test method 454 */ 455 TEST_F(Setlkw, set) 456 { 457 const char FULLPATH[] = "mountpoint/some_file.txt"; 458 const char RELPATH[] = "some_file.txt"; 459 uint64_t ino = 42; 460 struct flock fl; 461 int fd; 462 pid_t pid = 1234; 463 464 expect_lookup(RELPATH, ino); 465 expect_open(ino, 0, 1); 466 expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0); 467 468 fd = open(FULLPATH, O_RDWR); 469 ASSERT_LE(0, fd) << strerror(errno); 470 fl.l_start = 10; 471 fl.l_len = 1000; 472 fl.l_pid = pid; 473 fl.l_type = F_RDLCK; 474 fl.l_whence = SEEK_SET; 475 fl.l_sysid = 0; 476 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); 477 leak(fd); 478 } 479