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