]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/locks.cc
fusefs: annotate deliberate file descriptor leaks in the tests
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / locks.cc
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 }