]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/locks.cc
THIS BRANCH IS OBSOLETE, PLEASE READ:
[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  * $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
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 {
59         virtual void SetUp() {
60                 m_init_flags = FUSE_POSIX_LOCKS;
61                 Fallback::SetUp();
62         }
63 };
64
65 class Fcntl: public Locks {
66 public:
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.setlkw.owner == (uint32_t)pid &&
76                                 in.body.setlkw.lk.start == start &&
77                                 in.body.setlkw.lk.end == end &&
78                                 in.body.setlkw.lk.type == type &&
79                                 in.body.setlkw.lk.pid == (uint64_t)pid);
80                 }, Eq(true)),
81                 _)
82         ).WillOnce(Invoke(ReturnErrno(err)));
83 }
84 };
85
86 class Flock: public Locks {
87 public:
88 void expect_setlk(uint64_t ino, uint32_t type, int err)
89 {
90         EXPECT_CALL(*m_mock, process(
91                 ResultOf([=](auto in) {
92                         return (in.header.opcode == FUSE_SETLK &&
93                                 in.header.nodeid == ino &&
94                                 in.body.setlk.fh == FH &&
95                                 /* 
96                                  * The owner should be set to the address of
97                                  * the vnode.  That's hard to verify.
98                                  */
99                                 /* in.body.setlk.owner == ??? && */
100                                 in.body.setlk.lk.type == type);
101                 }, Eq(true)),
102                 _)
103         ).WillOnce(Invoke(ReturnErrno(err)));
104 }
105 };
106
107 class FlockFallback: public Fallback {};
108 class GetlkFallback: public Fallback {};
109 class Getlk: public Fcntl {};
110 class SetlkFallback: public Fallback {};
111 class Setlk: public Fcntl {};
112 class SetlkwFallback: public Fallback {};
113 class Setlkw: public Fcntl {};
114
115 /*
116  * If the fuse filesystem does not support flock locks, then the kernel should
117  * fall back to local locks.
118  */
119 TEST_F(FlockFallback, local)
120 {
121         const char FULLPATH[] = "mountpoint/some_file.txt";
122         const char RELPATH[] = "some_file.txt";
123         uint64_t ino = 42;
124         int fd;
125
126         expect_lookup(RELPATH, ino);
127         expect_open(ino, 0, 1);
128
129         fd = open(FULLPATH, O_RDWR);
130         ASSERT_LE(0, fd) << strerror(errno);
131         ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
132         leak(fd);
133 }
134
135 /*
136  * Even if the fuse file system supports POSIX locks, we must implement flock
137  * locks locally until protocol 7.17.  Protocol 7.9 added partial buggy support
138  * but we won't implement that.
139  */
140 TEST_F(Flock, local)
141 {
142         const char FULLPATH[] = "mountpoint/some_file.txt";
143         const char RELPATH[] = "some_file.txt";
144         uint64_t ino = 42;
145         int fd;
146
147         expect_lookup(RELPATH, ino);
148         expect_open(ino, 0, 1);
149
150         fd = open(FULLPATH, O_RDWR);
151         ASSERT_LE(0, fd) << strerror(errno);
152         ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
153         leak(fd);
154 }
155
156 /* Set a new flock lock with FUSE_SETLK */
157 /* TODO: enable after upgrading to protocol 7.17 */
158 TEST_F(Flock, DISABLED_set)
159 {
160         const char FULLPATH[] = "mountpoint/some_file.txt";
161         const char RELPATH[] = "some_file.txt";
162         uint64_t ino = 42;
163         int fd;
164
165         expect_lookup(RELPATH, ino);
166         expect_open(ino, 0, 1);
167         expect_setlk(ino, F_WRLCK, 0);
168
169         fd = open(FULLPATH, O_RDWR);
170         ASSERT_LE(0, fd) << strerror(errno);
171         ASSERT_EQ(0, flock(fd, LOCK_EX)) << strerror(errno);
172         leak(fd);
173 }
174
175 /* Fail to set a flock lock in non-blocking mode */
176 /* TODO: enable after upgrading to protocol 7.17 */
177 TEST_F(Flock, DISABLED_eagain)
178 {
179         const char FULLPATH[] = "mountpoint/some_file.txt";
180         const char RELPATH[] = "some_file.txt";
181         uint64_t ino = 42;
182         int fd;
183
184         expect_lookup(RELPATH, ino);
185         expect_open(ino, 0, 1);
186         expect_setlk(ino, F_WRLCK, EAGAIN);
187
188         fd = open(FULLPATH, O_RDWR);
189         ASSERT_LE(0, fd) << strerror(errno);
190         ASSERT_NE(0, flock(fd, LOCK_EX | LOCK_NB));
191         ASSERT_EQ(EAGAIN, errno);
192         leak(fd);
193 }
194
195 /*
196  * If the fuse filesystem does not support posix file locks, then the kernel
197  * should fall back to local locks.
198  */
199 TEST_F(GetlkFallback, local)
200 {
201         const char FULLPATH[] = "mountpoint/some_file.txt";
202         const char RELPATH[] = "some_file.txt";
203         uint64_t ino = 42;
204         struct flock fl;
205         int fd;
206
207         expect_lookup(RELPATH, ino);
208         expect_open(ino, 0, 1);
209
210         fd = open(FULLPATH, O_RDWR);
211         ASSERT_LE(0, fd) << strerror(errno);
212         fl.l_start = 10;
213         fl.l_len = 1000;
214         fl.l_pid = getpid();
215         fl.l_type = F_RDLCK;
216         fl.l_whence = SEEK_SET;
217         fl.l_sysid = 0;
218         ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
219         leak(fd);
220 }
221
222 /* 
223  * If the filesystem has no locks that fit the description, the filesystem
224  * should return F_UNLCK
225  */
226 TEST_F(Getlk, no_locks)
227 {
228         const char FULLPATH[] = "mountpoint/some_file.txt";
229         const char RELPATH[] = "some_file.txt";
230         uint64_t ino = 42;
231         struct flock fl;
232         int fd;
233         pid_t pid = 1234;
234
235         expect_lookup(RELPATH, ino);
236         expect_open(ino, 0, 1);
237         EXPECT_CALL(*m_mock, process(
238                 ResultOf([=](auto in) {
239                         return (in.header.opcode == FUSE_GETLK &&
240                                 in.header.nodeid == ino &&
241                                 in.body.getlk.fh == FH &&
242                                 in.body.getlk.owner == (uint32_t)pid &&
243                                 in.body.getlk.lk.start == 10 &&
244                                 in.body.getlk.lk.end == 1009 &&
245                                 in.body.getlk.lk.type == F_RDLCK &&
246                                 in.body.getlk.lk.pid == (uint64_t)pid);
247                 }, Eq(true)),
248                 _)
249         ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
250                 SET_OUT_HEADER_LEN(out, getlk);
251                 out.body.getlk.lk = in.body.getlk.lk;
252                 out.body.getlk.lk.type = F_UNLCK;
253         })));
254
255         fd = open(FULLPATH, O_RDWR);
256         ASSERT_LE(0, fd) << strerror(errno);
257         fl.l_start = 10;
258         fl.l_len = 1000;
259         fl.l_pid = pid;
260         fl.l_type = F_RDLCK;
261         fl.l_whence = SEEK_SET;
262         fl.l_sysid = 0;
263         ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
264         ASSERT_EQ(F_UNLCK, fl.l_type);
265         leak(fd);
266 }
267
268 /* A different pid does have a lock */
269 TEST_F(Getlk, lock_exists)
270 {
271         const char FULLPATH[] = "mountpoint/some_file.txt";
272         const char RELPATH[] = "some_file.txt";
273         uint64_t ino = 42;
274         struct flock fl;
275         int fd;
276         pid_t pid = 1234;
277         pid_t pid2 = 1235;
278
279         expect_lookup(RELPATH, ino);
280         expect_open(ino, 0, 1);
281         EXPECT_CALL(*m_mock, process(
282                 ResultOf([=](auto in) {
283                         return (in.header.opcode == FUSE_GETLK &&
284                                 in.header.nodeid == ino &&
285                                 in.body.getlk.fh == FH &&
286                                 in.body.getlk.owner == (uint32_t)pid &&
287                                 in.body.getlk.lk.start == 10 &&
288                                 in.body.getlk.lk.end == 1009 &&
289                                 in.body.getlk.lk.type == F_RDLCK &&
290                                 in.body.getlk.lk.pid == (uint64_t)pid);
291                 }, Eq(true)),
292                 _)
293         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
294                 SET_OUT_HEADER_LEN(out, getlk);
295                 out.body.getlk.lk.start = 100;
296                 out.body.getlk.lk.end = 199;
297                 out.body.getlk.lk.type = F_WRLCK;
298                 out.body.getlk.lk.pid = (uint32_t)pid2;;
299         })));
300
301         fd = open(FULLPATH, O_RDWR);
302         ASSERT_LE(0, fd) << strerror(errno);
303         fl.l_start = 10;
304         fl.l_len = 1000;
305         fl.l_pid = pid;
306         fl.l_type = F_RDLCK;
307         fl.l_whence = SEEK_SET;
308         fl.l_sysid = 0;
309         ASSERT_NE(-1, fcntl(fd, F_GETLK, &fl)) << strerror(errno);
310         EXPECT_EQ(100, fl.l_start);
311         EXPECT_EQ(100, fl.l_len);
312         EXPECT_EQ(pid2, fl.l_pid);
313         EXPECT_EQ(F_WRLCK, fl.l_type);
314         EXPECT_EQ(SEEK_SET, fl.l_whence);
315         EXPECT_EQ(0, fl.l_sysid);
316         leak(fd);
317 }
318
319 /*
320  * If the fuse filesystem does not support posix file locks, then the kernel
321  * should fall back to local locks.
322  */
323 TEST_F(SetlkFallback, local)
324 {
325         const char FULLPATH[] = "mountpoint/some_file.txt";
326         const char RELPATH[] = "some_file.txt";
327         uint64_t ino = 42;
328         struct flock fl;
329         int fd;
330
331         expect_lookup(RELPATH, ino);
332         expect_open(ino, 0, 1);
333
334         fd = open(FULLPATH, O_RDWR);
335         ASSERT_LE(0, fd) << strerror(errno);
336         fl.l_start = 10;
337         fl.l_len = 1000;
338         fl.l_pid = getpid();
339         fl.l_type = F_RDLCK;
340         fl.l_whence = SEEK_SET;
341         fl.l_sysid = 0;
342         ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
343         leak(fd);
344 }
345
346 /* Set a new lock with FUSE_SETLK */
347 TEST_F(Setlk, set)
348 {
349         const char FULLPATH[] = "mountpoint/some_file.txt";
350         const char RELPATH[] = "some_file.txt";
351         uint64_t ino = 42;
352         struct flock fl;
353         int fd;
354         pid_t pid = 1234;
355
356         expect_lookup(RELPATH, ino);
357         expect_open(ino, 0, 1);
358         expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
359
360         fd = open(FULLPATH, O_RDWR);
361         ASSERT_LE(0, fd) << strerror(errno);
362         fl.l_start = 10;
363         fl.l_len = 1000;
364         fl.l_pid = pid;
365         fl.l_type = F_RDLCK;
366         fl.l_whence = SEEK_SET;
367         fl.l_sysid = 0;
368         ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
369         leak(fd);
370 }
371
372 /* l_len = 0 is a flag value that means to lock until EOF */
373 TEST_F(Setlk, set_eof)
374 {
375         const char FULLPATH[] = "mountpoint/some_file.txt";
376         const char RELPATH[] = "some_file.txt";
377         uint64_t ino = 42;
378         struct flock fl;
379         int fd;
380         pid_t pid = 1234;
381
382         expect_lookup(RELPATH, ino);
383         expect_open(ino, 0, 1);
384         expect_setlk(ino, pid, 10, OFFSET_MAX, F_RDLCK, 0);
385
386         fd = open(FULLPATH, O_RDWR);
387         ASSERT_LE(0, fd) << strerror(errno);
388         fl.l_start = 10;
389         fl.l_len = 0;
390         fl.l_pid = pid;
391         fl.l_type = F_RDLCK;
392         fl.l_whence = SEEK_SET;
393         fl.l_sysid = 0;
394         ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
395         leak(fd);
396 }
397
398 /* Fail to set a new lock with FUSE_SETLK due to a conflict */
399 TEST_F(Setlk, eagain)
400 {
401         const char FULLPATH[] = "mountpoint/some_file.txt";
402         const char RELPATH[] = "some_file.txt";
403         uint64_t ino = 42;
404         struct flock fl;
405         int fd;
406         pid_t pid = 1234;
407
408         expect_lookup(RELPATH, ino);
409         expect_open(ino, 0, 1);
410         expect_setlk(ino, pid, 10, 1009, F_RDLCK, EAGAIN);
411
412         fd = open(FULLPATH, O_RDWR);
413         ASSERT_LE(0, fd) << strerror(errno);
414         fl.l_start = 10;
415         fl.l_len = 1000;
416         fl.l_pid = pid;
417         fl.l_type = F_RDLCK;
418         fl.l_whence = SEEK_SET;
419         fl.l_sysid = 0;
420         ASSERT_EQ(-1, fcntl(fd, F_SETLK, &fl));
421         ASSERT_EQ(EAGAIN, errno);
422         leak(fd);
423 }
424
425 /*
426  * If the fuse filesystem does not support posix file locks, then the kernel
427  * should fall back to local locks.
428  */
429 TEST_F(SetlkwFallback, local)
430 {
431         const char FULLPATH[] = "mountpoint/some_file.txt";
432         const char RELPATH[] = "some_file.txt";
433         uint64_t ino = 42;
434         struct flock fl;
435         int fd;
436
437         expect_lookup(RELPATH, ino);
438         expect_open(ino, 0, 1);
439
440         fd = open(FULLPATH, O_RDWR);
441         ASSERT_LE(0, fd) << strerror(errno);
442         fl.l_start = 10;
443         fl.l_len = 1000;
444         fl.l_pid = getpid();
445         fl.l_type = F_RDLCK;
446         fl.l_whence = SEEK_SET;
447         fl.l_sysid = 0;
448         ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
449         leak(fd);
450 }
451
452 /*
453  * Set a new lock with FUSE_SETLK.  If the lock is not available, then the
454  * command should block.  But to the kernel, that's the same as just being
455  * slow, so we don't need a separate test method
456  */
457 TEST_F(Setlkw, set)
458 {
459         const char FULLPATH[] = "mountpoint/some_file.txt";
460         const char RELPATH[] = "some_file.txt";
461         uint64_t ino = 42;
462         struct flock fl;
463         int fd;
464         pid_t pid = 1234;
465
466         expect_lookup(RELPATH, ino);
467         expect_open(ino, 0, 1);
468         expect_setlk(ino, pid, 10, 1009, F_RDLCK, 0);
469
470         fd = open(FULLPATH, O_RDWR);
471         ASSERT_LE(0, fd) << strerror(errno);
472         fl.l_start = 10;
473         fl.l_len = 1000;
474         fl.l_pid = pid;
475         fl.l_type = F_RDLCK;
476         fl.l_whence = SEEK_SET;
477         fl.l_sysid = 0;
478         ASSERT_NE(-1, fcntl(fd, F_SETLKW, &fl)) << strerror(errno);
479         leak(fd);
480 }