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
38 /* This flag value should probably be defined in fuse_kernel.h */
39 #define OFFSET_MAX 0x7fffffffffffffffLL
41 using namespace testing;
43 /* For testing filesystems without posix locking support */
44 class Fallback: public FuseTest {
47 void expect_lookup(const char *relpath, uint64_t ino)
49 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
54 /* For testing filesystems with posix locking support */
55 class Locks: public Fallback {
56 virtual void SetUp() {
57 m_init_flags = FUSE_POSIX_LOCKS;
62 class GetlkFallback: public Fallback {};
63 class Getlk: public Locks {};
64 class SetlkFallback: public Fallback {};
65 class Setlk: public Locks {};
66 class SetlkwFallback: public Fallback {};
67 class Setlkw: public Locks {};
70 * If the fuse filesystem does not support posix file locks, then the kernel
71 * should fall back to local locks.
73 TEST_F(GetlkFallback, local)
75 const char FULLPATH[] = "mountpoint/some_file.txt";
76 const char RELPATH[] = "some_file.txt";
81 expect_lookup(RELPATH, ino);
82 expect_open(ino, 0, 1);
84 fd = open(FULLPATH, O_RDWR);
85 ASSERT_LE(0, fd) << strerror(errno);
90 fl.l_whence = SEEK_SET;
92 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
93 /* Deliberately leak fd. close(2) will be tested in release.cc */
97 * If the filesystem has no locks that fit the description, the filesystem
98 * should return F_UNLCK
100 TEST_F(Getlk, no_locks)
102 const char FULLPATH[] = "mountpoint/some_file.txt";
103 const char RELPATH[] = "some_file.txt";
109 expect_lookup(RELPATH, ino);
110 expect_open(ino, 0, 1);
111 EXPECT_CALL(*m_mock, process(
112 ResultOf([=](auto in) {
113 return (in.header.opcode == FUSE_GETLK &&
114 in.header.nodeid == ino &&
115 in.body.getlk.fh == FH &&
116 in.body.getlk.owner == (uint32_t)pid &&
117 in.body.getlk.lk.start == 10 &&
118 in.body.getlk.lk.end == 1009 &&
119 in.body.getlk.lk.type == F_RDLCK &&
120 in.body.getlk.lk.pid == (uint64_t)pid);
123 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
124 SET_OUT_HEADER_LEN(out, getlk);
125 out.body.getlk.lk = in.body.getlk.lk;
126 out.body.getlk.lk.type = F_UNLCK;
129 fd = open(FULLPATH, O_RDWR);
130 ASSERT_LE(0, fd) << strerror(errno);
135 fl.l_whence = SEEK_SET;
137 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
138 ASSERT_EQ(F_UNLCK, fl.l_type);
139 /* Deliberately leak fd. close(2) will be tested in release.cc */
142 /* A different pid does have a lock */
143 TEST_F(Getlk, lock_exists)
145 const char FULLPATH[] = "mountpoint/some_file.txt";
146 const char RELPATH[] = "some_file.txt";
153 expect_lookup(RELPATH, ino);
154 expect_open(ino, 0, 1);
155 EXPECT_CALL(*m_mock, process(
156 ResultOf([=](auto in) {
157 return (in.header.opcode == FUSE_GETLK &&
158 in.header.nodeid == ino &&
159 in.body.getlk.fh == FH &&
160 in.body.getlk.owner == (uint32_t)pid &&
161 in.body.getlk.lk.start == 10 &&
162 in.body.getlk.lk.end == 1009 &&
163 in.body.getlk.lk.type == F_RDLCK &&
164 in.body.getlk.lk.pid == (uint64_t)pid);
167 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
168 SET_OUT_HEADER_LEN(out, getlk);
169 out.body.getlk.lk.start = 100;
170 out.body.getlk.lk.end = 199;
171 out.body.getlk.lk.type = F_WRLCK;
172 out.body.getlk.lk.pid = (uint32_t)pid2;;
175 fd = open(FULLPATH, O_RDWR);
176 ASSERT_LE(0, fd) << strerror(errno);
181 fl.l_whence = SEEK_SET;
183 ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
184 EXPECT_EQ(100, fl.l_start);
185 EXPECT_EQ(100, fl.l_len);
186 EXPECT_EQ(pid2, fl.l_pid);
187 EXPECT_EQ(F_WRLCK, fl.l_type);
188 EXPECT_EQ(SEEK_SET, fl.l_whence);
189 EXPECT_EQ(0, fl.l_sysid);
190 /* Deliberately leak fd. close(2) will be tested in release.cc */
194 * If the fuse filesystem does not support posix file locks, then the kernel
195 * should fall back to local locks.
197 TEST_F(SetlkFallback, 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_SETLK, &fl)) << strerror(errno);
217 /* Deliberately leak fd. close(2) will be tested in release.cc */
220 /* Set a new lock with FUSE_SETLK */
223 const char FULLPATH[] = "mountpoint/some_file.txt";
224 const char RELPATH[] = "some_file.txt";
230 expect_lookup(RELPATH, ino);
231 expect_open(ino, 0, 1);
232 EXPECT_CALL(*m_mock, process(
233 ResultOf([=](auto in) {
234 return (in.header.opcode == FUSE_SETLK &&
235 in.header.nodeid == ino &&
236 in.body.setlk.fh == FH &&
237 in.body.setlk.owner == (uint32_t)pid &&
238 in.body.setlk.lk.start == 10 &&
239 in.body.setlk.lk.end == 1009 &&
240 in.body.setlk.lk.type == F_RDLCK &&
241 in.body.setlk.lk.pid == (uint64_t)pid);
244 ).WillOnce(Invoke(ReturnErrno(0)));
246 fd = open(FULLPATH, O_RDWR);
247 ASSERT_LE(0, fd) << strerror(errno);
252 fl.l_whence = SEEK_SET;
254 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
255 /* Deliberately leak fd. close(2) will be tested in release.cc */
258 /* l_len = 0 is a flag value that means to lock until EOF */
259 TEST_F(Setlk, set_eof)
261 const char FULLPATH[] = "mountpoint/some_file.txt";
262 const char RELPATH[] = "some_file.txt";
268 expect_lookup(RELPATH, ino);
269 expect_open(ino, 0, 1);
270 EXPECT_CALL(*m_mock, process(
271 ResultOf([=](auto in) {
272 return (in.header.opcode == FUSE_SETLK &&
273 in.header.nodeid == ino &&
274 in.body.setlk.fh == FH &&
275 in.body.setlk.owner == (uint32_t)pid &&
276 in.body.setlk.lk.start == 10 &&
277 in.body.setlk.lk.end == OFFSET_MAX &&
278 in.body.setlk.lk.type == F_RDLCK &&
279 in.body.setlk.lk.pid == (uint64_t)pid);
282 ).WillOnce(Invoke(ReturnErrno(0)));
284 fd = open(FULLPATH, O_RDWR);
285 ASSERT_LE(0, fd) << strerror(errno);
290 fl.l_whence = SEEK_SET;
292 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
293 /* Deliberately leak fd. close(2) will be tested in release.cc */
296 /* Fail to set a new lock with FUSE_SETLK due to a conflict */
297 TEST_F(Setlk, eagain)
299 const char FULLPATH[] = "mountpoint/some_file.txt";
300 const char RELPATH[] = "some_file.txt";
306 expect_lookup(RELPATH, ino);
307 expect_open(ino, 0, 1);
308 EXPECT_CALL(*m_mock, process(
309 ResultOf([=](auto in) {
310 return (in.header.opcode == FUSE_SETLK &&
311 in.header.nodeid == ino &&
312 in.body.setlk.fh == FH &&
313 in.body.setlk.owner == (uint32_t)pid &&
314 in.body.setlk.lk.start == 10 &&
315 in.body.setlk.lk.end == 1009 &&
316 in.body.setlk.lk.type == F_RDLCK &&
317 in.body.setlk.lk.pid == (uint64_t)pid);
320 ).WillOnce(Invoke(ReturnErrno(EAGAIN)));
322 fd = open(FULLPATH, O_RDWR);
323 ASSERT_LE(0, fd) << strerror(errno);
328 fl.l_whence = SEEK_SET;
330 ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl));
331 ASSERT_EQ(EAGAIN, errno);
332 /* Deliberately leak fd. close(2) will be tested in release.cc */
336 * If the fuse filesystem does not support posix file locks, then the kernel
337 * should fall back to local locks.
339 TEST_F(SetlkwFallback, local)
341 const char FULLPATH[] = "mountpoint/some_file.txt";
342 const char RELPATH[] = "some_file.txt";
347 expect_lookup(RELPATH, ino);
348 expect_open(ino, 0, 1);
350 fd = open(FULLPATH, O_RDWR);
351 ASSERT_LE(0, fd) << strerror(errno);
356 fl.l_whence = SEEK_SET;
358 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
359 /* Deliberately leak fd. close(2) will be tested in release.cc */
363 * Set a new lock with FUSE_SETLK. If the lock is not available, then the
364 * command should block. But to the kernel, that's the same as just being
365 * slow, so we don't need a separate test method
369 const char FULLPATH[] = "mountpoint/some_file.txt";
370 const char RELPATH[] = "some_file.txt";
376 expect_lookup(RELPATH, ino);
377 expect_open(ino, 0, 1);
378 EXPECT_CALL(*m_mock, process(
379 ResultOf([=](auto in) {
380 return (in.header.opcode == FUSE_SETLK &&
381 in.header.nodeid == ino &&
382 in.body.setlkw.fh == FH &&
383 in.body.setlkw.owner == (uint32_t)pid &&
384 in.body.setlkw.lk.start == 10 &&
385 in.body.setlkw.lk.end == 1009 &&
386 in.body.setlkw.lk.type == F_RDLCK &&
387 in.body.setlkw.lk.pid == (uint64_t)pid);
390 ).WillOnce(Invoke(ReturnErrno(0)));
392 fd = open(FULLPATH, O_RDWR);
393 ASSERT_LE(0, fd) << strerror(errno);
398 fl.l_whence = SEEK_SET;
400 ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
401 /* Deliberately leak fd. close(2) will be tested in release.cc */