]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/lookup.cc
fuse(4): add tests for FUSE_INTERRUPT
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / lookup.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 <unistd.h>
33 }
34
35 #include "mockfs.hh"
36 #include "utils.hh"
37
38 using namespace testing;
39
40 class Lookup: public FuseTest {};
41
42 /*
43  * If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs
44  * should use the cached attributes, rather than query the daemon
45  */
46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
47 TEST_F(Lookup, DISABLED_attr_cache)
48 {
49         const char FULLPATH[] = "mountpoint/some_file.txt";
50         const char RELPATH[] = "some_file.txt";
51         const uint64_t ino = 42;
52         const uint64_t generation = 13;
53         struct stat sb;
54
55         EXPECT_LOOKUP(1, RELPATH)
56         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
57                 out->header.unique = in->header.unique;
58                 SET_OUT_HEADER_LEN(out, entry);
59                 out->body.entry.nodeid = ino;
60                 out->body.entry.attr_valid = UINT64_MAX;
61                 out->body.entry.attr.ino = ino; // Must match nodeid
62                 out->body.entry.attr.mode = S_IFREG | 0644;
63                 out->body.entry.attr.size = 1;
64                 out->body.entry.attr.blocks = 2;
65                 out->body.entry.attr.atime = 3;
66                 out->body.entry.attr.mtime = 4;
67                 out->body.entry.attr.ctime = 5;
68                 out->body.entry.attr.atimensec = 6;
69                 out->body.entry.attr.mtimensec = 7;
70                 out->body.entry.attr.ctimensec = 8;
71                 out->body.entry.attr.nlink = 9;
72                 out->body.entry.attr.uid = 10;
73                 out->body.entry.attr.gid = 11;
74                 out->body.entry.attr.rdev = 12;
75                 out->body.entry.generation = generation;
76         })));
77         /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */
78         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
79         EXPECT_EQ(1, sb.st_size);
80         EXPECT_EQ(2, sb.st_blocks);
81         EXPECT_EQ(3, sb.st_atim.tv_sec);
82         EXPECT_EQ(6, sb.st_atim.tv_nsec);
83         EXPECT_EQ(4, sb.st_mtim.tv_sec);
84         EXPECT_EQ(7, sb.st_mtim.tv_nsec);
85         EXPECT_EQ(5, sb.st_ctim.tv_sec);
86         EXPECT_EQ(8, sb.st_ctim.tv_nsec);
87         EXPECT_EQ(9ull, sb.st_nlink);
88         EXPECT_EQ(10ul, sb.st_uid);
89         EXPECT_EQ(11ul, sb.st_gid);
90         EXPECT_EQ(12ul, sb.st_rdev);
91         EXPECT_EQ(ino, sb.st_ino);
92         EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
93
94         // fuse(4) does not _yet_ support inode generations
95         //EXPECT_EQ(generation, sb.st_gen);
96
97         //st_birthtim and st_flags are not supported by protocol 7.8.  They're
98         //only supported as OS-specific extensions to OSX.
99         //EXPECT_EQ(, sb.st_birthtim);
100         //EXPECT_EQ(, sb.st_flags);
101         
102         //FUSE can't set st_blksize until protocol 7.9
103 }
104
105 /*
106  * If lookup returns a finite but non-zero cache timeout, then we should discard
107  * the cached attributes and requery the daemon.
108  */
109 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */
110 TEST_F(Lookup, attr_cache_timeout)
111 {
112         const char FULLPATH[] = "mountpoint/some_file.txt";
113         const char RELPATH[] = "some_file.txt";
114         const uint64_t ino = 42;
115         struct stat sb;
116         /* 
117          * The timeout should be longer than the longest plausible time the
118          * daemon would take to complete a write(2) to /dev/fuse, but no longer.
119          */
120         long timeout_ns = 250'000'000;
121
122         EXPECT_LOOKUP(1, RELPATH)
123         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
124                 out->header.unique = in->header.unique;
125                 SET_OUT_HEADER_LEN(out, entry);
126                 out->body.entry.nodeid = ino;
127                 out->body.entry.attr_valid_nsec = timeout_ns;
128                 out->body.entry.attr.ino = ino; // Must match nodeid
129                 out->body.entry.attr.mode = S_IFREG | 0644;
130         })));
131         expect_getattr(ino, 0);
132
133         /* access(2) will issue a VOP_LOOKUP but not a VOP_GETATTR */
134         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
135         usleep(2 * timeout_ns / 1000);
136         /* The cache has timed out; VOP_GETATTR should query the daemon*/
137         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
138 }
139
140 TEST_F(Lookup, enoent)
141 {
142         const char FULLPATH[] = "mountpoint/does_not_exist";
143         const char RELPATH[] = "does_not_exist";
144
145         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
146         EXPECT_NE(0, access(FULLPATH, F_OK));
147         EXPECT_EQ(ENOENT, errno);
148 }
149
150 /*
151  * If lookup returns a non-zero entry timeout, then subsequent VOP_LOOKUPs
152  * should use the cached inode rather than requery the daemon
153  */
154 TEST_F(Lookup, entry_cache)
155 {
156         const char FULLPATH[] = "mountpoint/some_file.txt";
157         const char RELPATH[] = "some_file.txt";
158
159         EXPECT_LOOKUP(1, RELPATH)
160         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
161                 out->header.unique = in->header.unique;
162                 SET_OUT_HEADER_LEN(out, entry);
163                 out->body.entry.entry_valid = UINT64_MAX;
164                 out->body.entry.attr.mode = S_IFREG | 0644;
165                 out->body.entry.nodeid = 14;
166         })));
167         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
168         /* The second access(2) should use the cache */
169         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
170 }
171
172 /* 
173  * If the daemon returns an error of 0 and an inode of 0, that's a flag for
174  * "ENOENT and cache it" with the given entry_timeout
175  */
176 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */
177 TEST_F(Lookup, DISABLED_entry_cache_negative)
178 {
179         struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
180
181         EXPECT_LOOKUP(1, "does_not_exist").Times(1)
182         .WillOnce(Invoke(ReturnNegativeCache(&entry_valid)));
183
184         EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
185         EXPECT_EQ(ENOENT, errno);
186         EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK));
187         EXPECT_EQ(ENOENT, errno);
188 }
189
190 /* Negative entry caches should timeout, too */
191 TEST_F(Lookup, entry_cache_negative_timeout)
192 {
193         const char *RELPATH = "does_not_exist";
194         const char *FULLPATH = "mountpoint/does_not_exist";
195         /* 
196          * The timeout should be longer than the longest plausible time the
197          * daemon would take to complete a write(2) to /dev/fuse, but no longer.
198          */
199         struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 250'000'000};
200
201         EXPECT_LOOKUP(1, RELPATH).Times(2)
202         .WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid)));
203
204         EXPECT_NE(0, access(FULLPATH, F_OK));
205         EXPECT_EQ(ENOENT, errno);
206
207         usleep(2 * entry_valid.tv_nsec / 1000);
208
209         /* The cache has timed out; VOP_LOOKUP should requery the daemon*/
210         EXPECT_NE(0, access(FULLPATH, F_OK));
211         EXPECT_EQ(ENOENT, errno);
212 }
213
214 /*
215  * If lookup returns a finite but non-zero entry cache timeout, then we should
216  * discard the cached inode and requery the daemon
217  */
218 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235773 */
219 TEST_F(Lookup, DISABLED_entry_cache_timeout)
220 {
221         const char FULLPATH[] = "mountpoint/some_file.txt";
222         const char RELPATH[] = "some_file.txt";
223         /* 
224          * The timeout should be longer than the longest plausible time the
225          * daemon would take to complete a write(2) to /dev/fuse, but no longer.
226          */
227         long timeout_ns = 250'000'000;
228
229         EXPECT_LOOKUP(1, RELPATH).Times(2)
230         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto out) {
231                 out->header.unique = in->header.unique;
232                 SET_OUT_HEADER_LEN(out, entry);
233                 out->body.entry.entry_valid_nsec = timeout_ns;
234                 out->body.entry.attr.mode = S_IFREG | 0644;
235                 out->body.entry.nodeid = 14;
236         })));
237         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
238         usleep(2 * timeout_ns / 1000);
239         /* The cache has timed out; VOP_LOOKUP should query the daemon*/
240         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
241 }
242
243 // TODO: export_support
244 // After upgrading the protocol to 7.10, check that the kernel will only
245 // attempt to lookup "." and ".." if the filesystem sets FUSE_EXPORT_SUPPORT in
246 // the init flags.  If not, then all lookups for those entries will return
247 // ESTALE.
248
249 TEST_F(Lookup, ok)
250 {
251         const char FULLPATH[] = "mountpoint/some_file.txt";
252         const char RELPATH[] = "some_file.txt";
253
254         EXPECT_LOOKUP(1, RELPATH)
255         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
256                 out->header.unique = in->header.unique;
257                 SET_OUT_HEADER_LEN(out, entry);
258                 out->body.entry.attr.mode = S_IFREG | 0644;
259                 out->body.entry.nodeid = 14;
260         })));
261         /*
262          * access(2) is one of the few syscalls that will not (always) follow
263          * up a successful VOP_LOOKUP with another VOP.
264          */
265         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
266 }
267
268 // Lookup in a subdirectory of the fuse mount
269 TEST_F(Lookup, subdir)
270 {
271         const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
272         const char DIRPATH[] = "some_dir";
273         const char RELPATH[] = "some_file.txt";
274         uint64_t dir_ino = 2;
275         uint64_t file_ino = 3;
276
277         EXPECT_LOOKUP(1, DIRPATH)
278         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
279                 out->header.unique = in->header.unique;
280                 SET_OUT_HEADER_LEN(out, entry);
281                 out->body.entry.attr.mode = S_IFDIR | 0755;
282                 out->body.entry.nodeid = dir_ino;
283         })));
284         EXPECT_LOOKUP(dir_ino, RELPATH)
285         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
286                 out->header.unique = in->header.unique;
287                 SET_OUT_HEADER_LEN(out, entry);
288                 out->body.entry.attr.mode = S_IFREG | 0644;
289                 out->body.entry.nodeid = file_ino;
290         })));
291         /*
292          * access(2) is one of the few syscalls that will not (always) follow
293          * up a successful VOP_LOOKUP with another VOP.
294          */
295         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
296 }
297
298