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 = getpid(); 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 = 1234; 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 in.body.getlk.owner == (uint32_t)pid && 260 in.body.getlk.lk.start == 10 && 261 in.body.getlk.lk.end == 1009 && 262 in.body.getlk.lk.type == F_RDLCK && 263 in.body.getlk.lk.pid == (uint64_t)pid); 264 }, Eq(true)), 265 _) 266 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 267 SET_OUT_HEADER_LEN(out, getlk); 268 out.body.getlk.lk = in.body.getlk.lk; 269 out.body.getlk.lk.type = F_UNLCK; 270 }))); 271 272 fd = open(FULLPATH, O_RDWR); 273 ASSERT_LE(0, fd) << strerror(errno); 274 fl.l_start = 10; 275 fl.l_len = 1000; 276 fl.l_pid = pid; 277 fl.l_type = F_RDLCK; 278 fl.l_whence = SEEK_SET; 279 fl.l_sysid = 0; 280 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); 281 ASSERT_EQ(F_UNLCK, fl.l_type); 282 leak(fd); 283 } 284 285 /* A different pid does have a lock */ 286 TEST_F(Getlk, lock_exists) 287 { 288 const char FULLPATH[] = "mountpoint/some_file.txt"; 289 const char RELPATH[] = "some_file.txt"; 290 uint64_t ino = 42; 291 struct flock fl; 292 int fd; 293 pid_t pid = 1234; 294 pid_t pid2 = 1235; 295 296 expect_lookup(RELPATH, ino); 297 expect_open(ino, 0, 1); 298 EXPECT_CALL(*m_mock, process( 299 ResultOf([=](auto in) { 300 return (in.header.opcode == FUSE_GETLK && 301 in.header.nodeid == ino && 302 in.body.getlk.fh == FH && 303 in.body.getlk.owner == (uint32_t)pid && 304 in.body.getlk.lk.start == 10 && 305 in.body.getlk.lk.end == 1009 && 306 in.body.getlk.lk.type == F_RDLCK && 307 in.body.getlk.lk.pid == (uint64_t)pid); 308 }, Eq(true)), 309 _) 310 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 311 SET_OUT_HEADER_LEN(out, getlk); 312 out.body.getlk.lk.start = 100; 313 out.body.getlk.lk.end = 199; 314 out.body.getlk.lk.type = F_WRLCK; 315 out.body.getlk.lk.pid = (uint32_t)pid2;; 316 }))); 317 318 fd = open(FULLPATH, O_RDWR); 319 ASSERT_LE(0, fd) << strerror(errno); 320 fl.l_start = 10; 321 fl.l_len = 1000; 322 fl.l_pid = pid; 323 fl.l_type = F_RDLCK; 324 fl.l_whence = SEEK_SET; 325 fl.l_sysid = 0; 326 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno); 327 EXPECT_EQ(100, fl.l_start); 328 EXPECT_EQ(100, fl.l_len); 329 EXPECT_EQ(pid2, fl.l_pid); 330 EXPECT_EQ(F_WRLCK, fl.l_type); 331 EXPECT_EQ(SEEK_SET, fl.l_whence); 332 EXPECT_EQ(0, fl.l_sysid); 333 leak(fd); 334 } 335 336 /* 337 * If the fuse filesystem does not support posix file locks, then the kernel 338 * should fall back to local locks. 339 */ 340 TEST_F(SetlkFallback, local) 341 { 342 const char FULLPATH[] = "mountpoint/some_file.txt"; 343 const char RELPATH[] = "some_file.txt"; 344 uint64_t ino = 42; 345 struct flock fl; 346 int fd; 347 348 expect_lookup(RELPATH, ino); 349 expect_open(ino, 0, 1); 350 351 fd = open(FULLPATH, O_RDWR); 352 ASSERT_LE(0, fd) << strerror(errno); 353 fl.l_start = 10; 354 fl.l_len = 1000; 355 fl.l_pid = getpid(); 356 fl.l_type = F_RDLCK; 357 fl.l_whence = SEEK_SET; 358 fl.l_sysid = 0; 359 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 360 leak(fd); 361 } 362 363 /* Clear a lock with FUSE_SETLK */ 364 TEST_F(Setlk, clear) 365 { 366 const char FULLPATH[] = "mountpoint/some_file.txt"; 367 const char RELPATH[] = "some_file.txt"; 368 uint64_t ino = 42; 369 struct flock fl; 370 int fd; 371 pid_t pid = 1234; 372 373 expect_lookup(RELPATH, ino); 374 expect_open(ino, 0, 1); 375 expect_setlk(ino, pid, 10, 1009, F_UNLCK, 0); 376 377 fd = open(FULLPATH, O_RDWR); 378 ASSERT_LE(0, fd) << strerror(errno); 379 fl.l_start = 10; 380 fl.l_len = 1000; 381 fl.l_pid = pid; 382 fl.l_type = F_UNLCK; 383 fl.l_whence = SEEK_SET; 384 fl.l_sysid = 0; 385 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 386 leak(fd); 387 } 388 389 /* Set a new lock with FUSE_SETLK */ 390 TEST_F(Setlk, set) 391 { 392 const char FULLPATH[] = "mountpoint/some_file.txt"; 393 const char RELPATH[] = "some_file.txt"; 394 uint64_t ino = 42; 395 struct flock fl; 396 int fd; 397 pid_t pid = 1234; 398 399 expect_lookup(RELPATH, ino); 400 expect_open(ino, 0, 1); 401 expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0); 402 403 fd = open(FULLPATH, O_RDWR); 404 ASSERT_LE(0, fd) << strerror(errno); 405 fl.l_start = 10; 406 fl.l_len = 1000; 407 fl.l_pid = pid; 408 fl.l_type = F_RDLCK; 409 fl.l_whence = SEEK_SET; 410 fl.l_sysid = 0; 411 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 412 leak(fd); 413 } 414 415 /* l_len = 0 is a flag value that means to lock until EOF */ 416 TEST_F(Setlk, set_eof) 417 { 418 const char FULLPATH[] = "mountpoint/some_file.txt"; 419 const char RELPATH[] = "some_file.txt"; 420 uint64_t ino = 42; 421 struct flock fl; 422 int fd; 423 pid_t pid = 1234; 424 425 expect_lookup(RELPATH, ino); 426 expect_open(ino, 0, 1); 427 expect_setlk(ino, pid, 10, OFFSET_MAX, F_RDLCK, 0); 428 429 fd = open(FULLPATH, O_RDWR); 430 ASSERT_LE(0, fd) << strerror(errno); 431 fl.l_start = 10; 432 fl.l_len = 0; 433 fl.l_pid = pid; 434 fl.l_type = F_RDLCK; 435 fl.l_whence = SEEK_SET; 436 fl.l_sysid = 0; 437 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 438 leak(fd); 439 } 440 441 /* Fail to set a new lock with FUSE_SETLK due to a conflict */ 442 TEST_F(Setlk, eagain) 443 { 444 const char FULLPATH[] = "mountpoint/some_file.txt"; 445 const char RELPATH[] = "some_file.txt"; 446 uint64_t ino = 42; 447 struct flock fl; 448 int fd; 449 pid_t pid = 1234; 450 451 expect_lookup(RELPATH, ino); 452 expect_open(ino, 0, 1); 453 expect_setlk(ino, pid, 10, 1009, F_RDLCK, EAGAIN); 454 455 fd = open(FULLPATH, O_RDWR); 456 ASSERT_LE(0, fd) << strerror(errno); 457 fl.l_start = 10; 458 fl.l_len = 1000; 459 fl.l_pid = pid; 460 fl.l_type = F_RDLCK; 461 fl.l_whence = SEEK_SET; 462 fl.l_sysid = 0; 463 ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl)); 464 ASSERT_EQ(EAGAIN, errno); 465 leak(fd); 466 } 467 468 /* 469 * If the fuse filesystem does not support posix file locks, then the kernel 470 * should fall back to local locks. 471 */ 472 TEST_F(SetlkwFallback, local) 473 { 474 const char FULLPATH[] = "mountpoint/some_file.txt"; 475 const char RELPATH[] = "some_file.txt"; 476 uint64_t ino = 42; 477 struct flock fl; 478 int fd; 479 480 expect_lookup(RELPATH, ino); 481 expect_open(ino, 0, 1); 482 483 fd = open(FULLPATH, O_RDWR); 484 ASSERT_LE(0, fd) << strerror(errno); 485 fl.l_start = 10; 486 fl.l_len = 1000; 487 fl.l_pid = getpid(); 488 fl.l_type = F_RDLCK; 489 fl.l_whence = SEEK_SET; 490 fl.l_sysid = 0; 491 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); 492 leak(fd); 493 } 494 495 /* 496 * Set a new lock with FUSE_SETLK. If the lock is not available, then the 497 * command should block. But to the kernel, that's the same as just being 498 * slow, so we don't need a separate test method 499 */ 500 TEST_F(Setlkw, set) 501 { 502 const char FULLPATH[] = "mountpoint/some_file.txt"; 503 const char RELPATH[] = "some_file.txt"; 504 uint64_t ino = 42; 505 struct flock fl; 506 int fd; 507 pid_t pid = 1234; 508 509 expect_lookup(RELPATH, ino); 510 expect_open(ino, 0, 1); 511 expect_setlkw(ino, pid, 10, 1009, F_RDLCK, 0); 512 513 fd = open(FULLPATH, O_RDWR); 514 ASSERT_LE(0, fd) << strerror(errno); 515 fl.l_start = 10; 516 fl.l_len = 1000; 517 fl.l_pid = pid; 518 fl.l_type = F_RDLCK; 519 fl.l_whence = SEEK_SET; 520 fl.l_sysid = 0; 521 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno); 522 leak(fd); 523 } 524