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