2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
39 /* This flag value should probably be defined in fuse_kernel.h */
40 #define OFFSET_MAX 0x7fffffffffffffffLL
42 using namespace testing;
44 /* For testing filesystems without posix locking support */
45 class Fallback: public FuseTest {
48 void expect_lookup(const char *relpath, uint64_t ino)
50 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
55 /* For testing filesystems with posix locking support */
56 class Locks: public Fallback {
57 virtual void SetUp() {
58 m_init_flags = FUSE_POSIX_LOCKS;
63 class Fcntl: public Locks {
65 void expect_setlk(uint64_t ino, pid_t pid, uint64_t start, uint64_t end,
66 uint32_t type, int err)
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);
80 ).WillOnce(Invoke(ReturnErrno(err)));
84 class Flock: public Locks {
86 void expect_setlk(uint64_t ino, uint32_t type, int err)
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 &&
94 * The owner should be set to the address of
95 * the vnode. That's hard to verify.
97 /* in.body.setlk.owner == ??? && */
98 in.body.setlk.lk.type == type);
101 ).WillOnce(Invoke(ReturnErrno(err)));
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 {};
114 * If the fuse filesystem does not support flock locks, then the kernel should
115 * fall back to local locks.
117 TEST_F(FlockFallback, local)
119 const char FULLPATH[] = "mountpoint/some_file.txt";
120 const char RELPATH[] = "some_file.txt";
124 expect_lookup(RELPATH, ino);
125 expect_open(ino, 0, 1);
127 fd = open(FULLPATH, O_RDWR);
128 ASSERT_LE(0, fd) << strerror(errno);
129 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
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.
140 const char FULLPATH[] = "mountpoint/some_file.txt";
141 const char RELPATH[] = "some_file.txt";
145 expect_lookup(RELPATH, ino);
146 expect_open(ino, 0, 1);
148 fd = open(FULLPATH, O_RDWR);
149 ASSERT_LE(0, fd) << strerror(errno);
150 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
154 /* Set a new flock lock with FUSE_SETLK */
155 /* TODO: enable after upgrading to protocol 7.17 */
156 TEST_F(Flock, DISABLED_set)
158 const char FULLPATH[] = "mountpoint/some_file.txt";
159 const char RELPATH[] = "some_file.txt";
163 expect_lookup(RELPATH, ino);
164 expect_open(ino, 0, 1);
165 expect_setlk(ino, F_WRLCK, 0);
167 fd = open(FULLPATH, O_RDWR);
168 ASSERT_LE(0, fd) << strerror(errno);
169 ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
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)
177 const char FULLPATH[] = "mountpoint/some_file.txt";
178 const char RELPATH[] = "some_file.txt";
182 expect_lookup(RELPATH, ino);
183 expect_open(ino, 0, 1);
184 expect_setlk(ino, F_WRLCK, EAGAIN);
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);
194 * If the fuse filesystem does not support posix file locks, then the kernel
195 * should fall back to local locks.
197 TEST_F(GetlkFallback, local)
199 const char FULLPATH[] = "mountpoint/some_file.txt";
200 const char RELPATH[] = "some_file.txt";
205 expect_lookup(RELPATH, ino);
206 expect_open(ino, 0, 1);
208 fd = open(FULLPATH, O_RDWR);
209 ASSERT_LE(0, fd) << strerror(errno);
214 fl.l_whence = SEEK_SET;
216 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
221 * If the filesystem has no locks that fit the description, the filesystem
222 * should return F_UNLCK
224 TEST_F(Getlk, no_locks)
226 const char FULLPATH[] = "mountpoint/some_file.txt";
227 const char RELPATH[] = "some_file.txt";
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);
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;
253 fd = open(FULLPATH, O_RDWR);
254 ASSERT_LE(0, fd) << strerror(errno);
259 fl.l_whence = SEEK_SET;
261 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
262 ASSERT_EQ(F_UNLCK, fl.l_type);
266 /* A different pid does have a lock */
267 TEST_F(Getlk, lock_exists)
269 const char FULLPATH[] = "mountpoint/some_file.txt";
270 const char RELPATH[] = "some_file.txt";
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);
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;;
299 fd = open(FULLPATH, O_RDWR);
300 ASSERT_LE(0, fd) << strerror(errno);
305 fl.l_whence = SEEK_SET;
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);
318 * If the fuse filesystem does not support posix file locks, then the kernel
319 * should fall back to local locks.
321 TEST_F(SetlkFallback, local)
323 const char FULLPATH[] = "mountpoint/some_file.txt";
324 const char RELPATH[] = "some_file.txt";
329 expect_lookup(RELPATH, ino);
330 expect_open(ino, 0, 1);
332 fd = open(FULLPATH, O_RDWR);
333 ASSERT_LE(0, fd) << strerror(errno);
338 fl.l_whence = SEEK_SET;
340 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
344 /* Set a new lock with FUSE_SETLK */
347 const char FULLPATH[] = "mountpoint/some_file.txt";
348 const char RELPATH[] = "some_file.txt";
354 expect_lookup(RELPATH, ino);
355 expect_open(ino, 0, 1);
356 expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
358 fd = open(FULLPATH, O_RDWR);
359 ASSERT_LE(0, fd) << strerror(errno);
364 fl.l_whence = SEEK_SET;
366 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
370 /* l_len = 0 is a flag value that means to lock until EOF */
371 TEST_F(Setlk, set_eof)
373 const char FULLPATH[] = "mountpoint/some_file.txt";
374 const char RELPATH[] = "some_file.txt";
380 expect_lookup(RELPATH, ino);
381 expect_open(ino, 0, 1);
382 expect_setlk(ino, pid, 10, OFFSET_MAX, F_RDLCK, 0);
384 fd = open(FULLPATH, O_RDWR);
385 ASSERT_LE(0, fd) << strerror(errno);
390 fl.l_whence = SEEK_SET;
392 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
396 /* Fail to set a new lock with FUSE_SETLK due to a conflict */
397 TEST_F(Setlk, eagain)
399 const char FULLPATH[] = "mountpoint/some_file.txt";
400 const char RELPATH[] = "some_file.txt";
406 expect_lookup(RELPATH, ino);
407 expect_open(ino, 0, 1);
408 expect_setlk(ino, pid, 10, 1009, F_RDLCK, EAGAIN);
410 fd = open(FULLPATH, O_RDWR);
411 ASSERT_LE(0, fd) << strerror(errno);
416 fl.l_whence = SEEK_SET;
418 ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl));
419 ASSERT_EQ(EAGAIN, errno);
424 * If the fuse filesystem does not support posix file locks, then the kernel
425 * should fall back to local locks.
427 TEST_F(SetlkwFallback, local)
429 const char FULLPATH[] = "mountpoint/some_file.txt";
430 const char RELPATH[] = "some_file.txt";
435 expect_lookup(RELPATH, ino);
436 expect_open(ino, 0, 1);
438 fd = open(FULLPATH, O_RDWR);
439 ASSERT_LE(0, fd) << strerror(errno);
444 fl.l_whence = SEEK_SET;
446 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
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
457 const char FULLPATH[] = "mountpoint/some_file.txt";
458 const char RELPATH[] = "some_file.txt";
464 expect_lookup(RELPATH, ino);
465 expect_open(ino, 0, 1);
466 expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
468 fd = open(FULLPATH, O_RDWR);
469 ASSERT_LE(0, fd) << strerror(errno);
474 fl.l_whence = SEEK_SET;
476 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);