xref: /freebsd/tests/sys/fs/fusefs/locks.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
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 
31 extern "C" {
32 #include <sys/file.h>
33 #include <fcntl.h>
34 }
35 
36 #include "mockfs.hh"
37 #include "utils.hh"
38 
39 /* This flag value should probably be defined in fuse_kernel.h */
40 #define OFFSET_MAX 0x7fffffffffffffffLL
41 
42 using namespace testing;
43 
44 /* For testing filesystems without posix locking support */
45 class Fallback: public FuseTest {
46 public:
47 
48 void expect_lookup(const char *relpath, uint64_t ino)
49 {
50 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1);
51 }
52 
53 };
54 
55 /* For testing filesystems with posix locking support */
56 class Locks: public Fallback {
57 	virtual void SetUp() {
58 		m_init_flags = FUSE_POSIX_LOCKS;
59 		Fallback::SetUp();
60 	}
61 };
62 
63 class Fcntl: public Locks {
64 public:
65 void expect_setlk(uint64_t ino, pid_t pid, uint64_t start, uint64_t end,
66 	uint32_t type, int err)
67 {
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);
78 		}, Eq(true)),
79 		_)
80 	).WillOnce(Invoke(ReturnErrno(err)));
81 }
82 };
83 
84 class Flock: public Locks {
85 public:
86 void expect_setlk(uint64_t ino, uint32_t type, int err)
87 {
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 &&
93 				/*
94 				 * The owner should be set to the address of
95 				 * the vnode.  That's hard to verify.
96 				 */
97 				/* in.body.setlk.owner == ??? && */
98 				in.body.setlk.lk.type == type);
99 		}, Eq(true)),
100 		_)
101 	).WillOnce(Invoke(ReturnErrno(err)));
102 }
103 };
104 
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 {};
112 
113 /*
114  * If the fuse filesystem does not support flock locks, then the kernel should
115  * fall back to local locks.
116  */
117 TEST_F(FlockFallback, local)
118 {
119 	const char FULLPATH[] = "mountpoint/some_file.txt";
120 	const char RELPATH[] = "some_file.txt";
121 	uint64_t ino = 42;
122 	int fd;
123 
124 	expect_lookup(RELPATH, ino);
125 	expect_open(ino, 0, 1);
126 
127 	fd = open(FULLPATH, O_RDWR);
128 	ASSERT_LE(0, fd) << strerror(errno);
129 	ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
130 	leak(fd);
131 }
132 
133 /*
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.
137  */
138 TEST_F(Flock, local)
139 {
140 	const char FULLPATH[] = "mountpoint/some_file.txt";
141 	const char RELPATH[] = "some_file.txt";
142 	uint64_t ino = 42;
143 	int fd;
144 
145 	expect_lookup(RELPATH, ino);
146 	expect_open(ino, 0, 1);
147 
148 	fd = open(FULLPATH, O_RDWR);
149 	ASSERT_LE(0, fd) << strerror(errno);
150 	ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
151 	leak(fd);
152 }
153 
154 /* Set a new flock lock with FUSE_SETLK */
155 /* TODO: enable after upgrading to protocol 7.17 */
156 TEST_F(Flock, DISABLED_set)
157 {
158 	const char FULLPATH[] = "mountpoint/some_file.txt";
159 	const char RELPATH[] = "some_file.txt";
160 	uint64_t ino = 42;
161 	int fd;
162 
163 	expect_lookup(RELPATH, ino);
164 	expect_open(ino, 0, 1);
165 	expect_setlk(ino, F_WRLCK, 0);
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 /* 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)
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, EAGAIN);
185 
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);
190 	leak(fd);
191 }
192 
193 /*
194  * If the fuse filesystem does not support posix file locks, then the kernel
195  * should fall back to local locks.
196  */
197 TEST_F(GetlkFallback, local)
198 {
199 	const char FULLPATH[] = "mountpoint/some_file.txt";
200 	const char RELPATH[] = "some_file.txt";
201 	uint64_t ino = 42;
202 	struct flock fl;
203 	int fd;
204 
205 	expect_lookup(RELPATH, ino);
206 	expect_open(ino, 0, 1);
207 
208 	fd = open(FULLPATH, O_RDWR);
209 	ASSERT_LE(0, fd) << strerror(errno);
210 	fl.l_start = 10;
211 	fl.l_len = 1000;
212 	fl.l_pid = getpid();
213 	fl.l_type = F_RDLCK;
214 	fl.l_whence = SEEK_SET;
215 	fl.l_sysid = 0;
216 	ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
217 	leak(fd);
218 }
219 
220 /*
221  * If the filesystem has no locks that fit the description, the filesystem
222  * should return F_UNLCK
223  */
224 TEST_F(Getlk, no_locks)
225 {
226 	const char FULLPATH[] = "mountpoint/some_file.txt";
227 	const char RELPATH[] = "some_file.txt";
228 	uint64_t ino = 42;
229 	struct flock fl;
230 	int fd;
231 	pid_t pid = 1234;
232 
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);
245 		}, Eq(true)),
246 		_)
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;
251 	})));
252 
253 	fd = open(FULLPATH, O_RDWR);
254 	ASSERT_LE(0, fd) << strerror(errno);
255 	fl.l_start = 10;
256 	fl.l_len = 1000;
257 	fl.l_pid = pid;
258 	fl.l_type = F_RDLCK;
259 	fl.l_whence = SEEK_SET;
260 	fl.l_sysid = 0;
261 	ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
262 	ASSERT_EQ(F_UNLCK, fl.l_type);
263 	leak(fd);
264 }
265 
266 /* A different pid does have a lock */
267 TEST_F(Getlk, lock_exists)
268 {
269 	const char FULLPATH[] = "mountpoint/some_file.txt";
270 	const char RELPATH[] = "some_file.txt";
271 	uint64_t ino = 42;
272 	struct flock fl;
273 	int fd;
274 	pid_t pid = 1234;
275 	pid_t pid2 = 1235;
276 
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);
289 		}, Eq(true)),
290 		_)
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;;
297 	})));
298 
299 	fd = open(FULLPATH, O_RDWR);
300 	ASSERT_LE(0, fd) << strerror(errno);
301 	fl.l_start = 10;
302 	fl.l_len = 1000;
303 	fl.l_pid = pid;
304 	fl.l_type = F_RDLCK;
305 	fl.l_whence = SEEK_SET;
306 	fl.l_sysid = 0;
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);
314 	leak(fd);
315 }
316 
317 /*
318  * If the fuse filesystem does not support posix file locks, then the kernel
319  * should fall back to local locks.
320  */
321 TEST_F(SetlkFallback, local)
322 {
323 	const char FULLPATH[] = "mountpoint/some_file.txt";
324 	const char RELPATH[] = "some_file.txt";
325 	uint64_t ino = 42;
326 	struct flock fl;
327 	int fd;
328 
329 	expect_lookup(RELPATH, ino);
330 	expect_open(ino, 0, 1);
331 
332 	fd = open(FULLPATH, O_RDWR);
333 	ASSERT_LE(0, fd) << strerror(errno);
334 	fl.l_start = 10;
335 	fl.l_len = 1000;
336 	fl.l_pid = getpid();
337 	fl.l_type = F_RDLCK;
338 	fl.l_whence = SEEK_SET;
339 	fl.l_sysid = 0;
340 	ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
341 	leak(fd);
342 }
343 
344 /* Set a new lock with FUSE_SETLK */
345 TEST_F(Setlk, set)
346 {
347 	const char FULLPATH[] = "mountpoint/some_file.txt";
348 	const char RELPATH[] = "some_file.txt";
349 	uint64_t ino = 42;
350 	struct flock fl;
351 	int fd;
352 	pid_t pid = 1234;
353 
354 	expect_lookup(RELPATH, ino);
355 	expect_open(ino, 0, 1);
356 	expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
357 
358 	fd = open(FULLPATH, O_RDWR);
359 	ASSERT_LE(0, fd) << strerror(errno);
360 	fl.l_start = 10;
361 	fl.l_len = 1000;
362 	fl.l_pid = pid;
363 	fl.l_type = F_RDLCK;
364 	fl.l_whence = SEEK_SET;
365 	fl.l_sysid = 0;
366 	ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
367 	leak(fd);
368 }
369 
370 /* l_len = 0 is a flag value that means to lock until EOF */
371 TEST_F(Setlk, set_eof)
372 {
373 	const char FULLPATH[] = "mountpoint/some_file.txt";
374 	const char RELPATH[] = "some_file.txt";
375 	uint64_t ino = 42;
376 	struct flock fl;
377 	int fd;
378 	pid_t pid = 1234;
379 
380 	expect_lookup(RELPATH, ino);
381 	expect_open(ino, 0, 1);
382 	expect_setlk(ino, pid, 10, OFFSET_MAX, F_RDLCK, 0);
383 
384 	fd = open(FULLPATH, O_RDWR);
385 	ASSERT_LE(0, fd) << strerror(errno);
386 	fl.l_start = 10;
387 	fl.l_len = 0;
388 	fl.l_pid = pid;
389 	fl.l_type = F_RDLCK;
390 	fl.l_whence = SEEK_SET;
391 	fl.l_sysid = 0;
392 	ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
393 	leak(fd);
394 }
395 
396 /* Fail to set a new lock with FUSE_SETLK due to a conflict */
397 TEST_F(Setlk, eagain)
398 {
399 	const char FULLPATH[] = "mountpoint/some_file.txt";
400 	const char RELPATH[] = "some_file.txt";
401 	uint64_t ino = 42;
402 	struct flock fl;
403 	int fd;
404 	pid_t pid = 1234;
405 
406 	expect_lookup(RELPATH, ino);
407 	expect_open(ino, 0, 1);
408 	expect_setlk(ino, pid, 10, 1009, F_RDLCK, EAGAIN);
409 
410 	fd = open(FULLPATH, O_RDWR);
411 	ASSERT_LE(0, fd) << strerror(errno);
412 	fl.l_start = 10;
413 	fl.l_len = 1000;
414 	fl.l_pid = pid;
415 	fl.l_type = F_RDLCK;
416 	fl.l_whence = SEEK_SET;
417 	fl.l_sysid = 0;
418 	ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl));
419 	ASSERT_EQ(EAGAIN, errno);
420 	leak(fd);
421 }
422 
423 /*
424  * If the fuse filesystem does not support posix file locks, then the kernel
425  * should fall back to local locks.
426  */
427 TEST_F(SetlkwFallback, local)
428 {
429 	const char FULLPATH[] = "mountpoint/some_file.txt";
430 	const char RELPATH[] = "some_file.txt";
431 	uint64_t ino = 42;
432 	struct flock fl;
433 	int fd;
434 
435 	expect_lookup(RELPATH, ino);
436 	expect_open(ino, 0, 1);
437 
438 	fd = open(FULLPATH, O_RDWR);
439 	ASSERT_LE(0, fd) << strerror(errno);
440 	fl.l_start = 10;
441 	fl.l_len = 1000;
442 	fl.l_pid = getpid();
443 	fl.l_type = F_RDLCK;
444 	fl.l_whence = SEEK_SET;
445 	fl.l_sysid = 0;
446 	ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
447 	leak(fd);
448 }
449 
450 /*
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
454  */
455 TEST_F(Setlkw, set)
456 {
457 	const char FULLPATH[] = "mountpoint/some_file.txt";
458 	const char RELPATH[] = "some_file.txt";
459 	uint64_t ino = 42;
460 	struct flock fl;
461 	int fd;
462 	pid_t pid = 1234;
463 
464 	expect_lookup(RELPATH, ino);
465 	expect_open(ino, 0, 1);
466 	expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
467 
468 	fd = open(FULLPATH, O_RDWR);
469 	ASSERT_LE(0, fd) << strerror(errno);
470 	fl.l_start = 10;
471 	fl.l_len = 1000;
472 	fl.l_pid = pid;
473 	fl.l_type = F_RDLCK;
474 	fl.l_whence = SEEK_SET;
475 	fl.l_sysid = 0;
476 	ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
477 	leak(fd);
478 }
479