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