1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
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/param.h>
33
34 #include <semaphore.h>
35 }
36
37 #include "mockfs.hh"
38 #include "utils.hh"
39
40 using namespace testing;
41
42 class Getattr : public FuseTest {
43 public:
expect_lookup(const char * relpath,uint64_t ino,mode_t mode,uint64_t size,int times,uint64_t attr_valid,uint32_t attr_valid_nsec)44 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
45 uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
46 {
47 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
48 .Times(times)
49 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
50 SET_OUT_HEADER_LEN(out, entry);
51 out.body.entry.attr.mode = mode;
52 out.body.entry.nodeid = ino;
53 out.body.entry.attr.nlink = 1;
54 out.body.entry.attr_valid = attr_valid;
55 out.body.entry.attr_valid_nsec = attr_valid_nsec;
56 out.body.entry.attr.size = size;
57 out.body.entry.entry_valid = UINT64_MAX;
58 })));
59 }
60 };
61
62 class Getattr_7_8: public FuseTest {
63 public:
SetUp()64 virtual void SetUp() {
65 m_kernel_minor_version = 8;
66 FuseTest::SetUp();
67 }
68 };
69
70 /*
71 * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
72 * should use the cached attributes, rather than query the daemon
73 */
TEST_F(Getattr,attr_cache)74 TEST_F(Getattr, attr_cache)
75 {
76 const char FULLPATH[] = "mountpoint/some_file.txt";
77 const char RELPATH[] = "some_file.txt";
78 const uint64_t ino = 42;
79 struct stat sb;
80
81 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
82 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
83 SET_OUT_HEADER_LEN(out, entry);
84 out.body.entry.attr.mode = S_IFREG | 0644;
85 out.body.entry.nodeid = ino;
86 out.body.entry.entry_valid = UINT64_MAX;
87 })));
88 EXPECT_CALL(*m_mock, process(
89 ResultOf([](auto in) {
90 return (in.header.opcode == FUSE_GETATTR &&
91 in.header.nodeid == ino);
92 }, Eq(true)),
93 _)
94 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
95 SET_OUT_HEADER_LEN(out, attr);
96 out.body.attr.attr_valid = UINT64_MAX;
97 out.body.attr.attr.ino = ino; // Must match nodeid
98 out.body.attr.attr.mode = S_IFREG | 0644;
99 })));
100 EXPECT_EQ(0, stat(FULLPATH, &sb));
101 /* The second stat(2) should use cached attributes */
102 EXPECT_EQ(0, stat(FULLPATH, &sb));
103 }
104
105 /*
106 * If getattr returns a finite but non-zero cache timeout, then we should
107 * discard the cached attributes and requery the daemon after the timeout
108 * period passes.
109 */
TEST_F(Getattr,attr_cache_timeout)110 TEST_F(Getattr, 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 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
118 EXPECT_CALL(*m_mock, process(
119 ResultOf([](auto in) {
120 return (in.header.opcode == FUSE_GETATTR &&
121 in.header.nodeid == ino);
122 }, Eq(true)),
123 _)
124 ).Times(2)
125 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
126 SET_OUT_HEADER_LEN(out, attr);
127 out.body.attr.attr_valid_nsec = NAP_NS / 2;
128 out.body.attr.attr_valid = 0;
129 out.body.attr.attr.ino = ino; // Must match nodeid
130 out.body.attr.attr.mode = S_IFREG | 0644;
131 })));
132
133 EXPECT_EQ(0, stat(FULLPATH, &sb));
134 nap();
135 /* Timeout has expired. stat(2) should requery the daemon */
136 EXPECT_EQ(0, stat(FULLPATH, &sb));
137 }
138
139 /*
140 * If attr.blksize is zero, then the kernel should use a default value for
141 * st_blksize
142 */
TEST_F(Getattr,blksize_zero)143 TEST_F(Getattr, blksize_zero)
144 {
145 const char FULLPATH[] = "mountpoint/some_file.txt";
146 const char RELPATH[] = "some_file.txt";
147 const uint64_t ino = 42;
148 struct stat sb;
149
150 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
151 EXPECT_CALL(*m_mock, process(
152 ResultOf([](auto in) {
153 return (in.header.opcode == FUSE_GETATTR &&
154 in.header.nodeid == ino);
155 }, Eq(true)),
156 _)
157 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
158 SET_OUT_HEADER_LEN(out, attr);
159 out.body.attr.attr.mode = S_IFREG | 0644;
160 out.body.attr.attr.ino = ino; // Must match nodeid
161 out.body.attr.attr.blksize = 0;
162 out.body.attr.attr.size = 1;
163 })));
164
165 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
166 EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
167 }
168
TEST_F(Getattr,enoent)169 TEST_F(Getattr, enoent)
170 {
171 const char FULLPATH[] = "mountpoint/some_file.txt";
172 const char RELPATH[] = "some_file.txt";
173 struct stat sb;
174 const uint64_t ino = 42;
175 sem_t sem;
176
177 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
178
179 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
180 EXPECT_CALL(*m_mock, process(
181 ResultOf([](auto in) {
182 return (in.header.opcode == FUSE_GETATTR &&
183 in.header.nodeid == ino);
184 }, Eq(true)),
185 _)
186 ).WillOnce(Invoke(ReturnErrno(ENOENT)));
187 // Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
188 // and send a FUSE_FORGET
189 expect_forget(ino, 1, &sem);
190
191 EXPECT_NE(0, stat(FULLPATH, &sb));
192 EXPECT_EQ(ENOENT, errno);
193
194 sem_wait(&sem);
195 sem_destroy(&sem);
196 }
197
TEST_F(Getattr,ok)198 TEST_F(Getattr, ok)
199 {
200 const char FULLPATH[] = "mountpoint/some_file.txt";
201 const char RELPATH[] = "some_file.txt";
202 const uint64_t ino = 42;
203 struct stat sb;
204
205 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
206 EXPECT_CALL(*m_mock, process(
207 ResultOf([](auto in) {
208 return (in.header.opcode == FUSE_GETATTR &&
209 in.body.getattr.getattr_flags == 0 &&
210 in.header.nodeid == ino);
211 }, Eq(true)),
212 _)
213 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
214 SET_OUT_HEADER_LEN(out, attr);
215 out.body.attr.attr.ino = ino; // Must match nodeid
216 out.body.attr.attr.mode = S_IFREG | 0644;
217 out.body.attr.attr.size = 1;
218 out.body.attr.attr.blocks = 2;
219 out.body.attr.attr.atime = 3;
220 out.body.attr.attr.mtime = 4;
221 out.body.attr.attr.ctime = 5;
222 out.body.attr.attr.atimensec = 6;
223 out.body.attr.attr.mtimensec = 7;
224 out.body.attr.attr.ctimensec = 8;
225 out.body.attr.attr.nlink = 9;
226 out.body.attr.attr.uid = 10;
227 out.body.attr.attr.gid = 11;
228 out.body.attr.attr.rdev = 12;
229 out.body.attr.attr.blksize = 12345;
230 })));
231
232 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
233 EXPECT_EQ(1, sb.st_size);
234 EXPECT_EQ(2, sb.st_blocks);
235 EXPECT_EQ(3, sb.st_atim.tv_sec);
236 EXPECT_EQ(6, sb.st_atim.tv_nsec);
237 EXPECT_EQ(4, sb.st_mtim.tv_sec);
238 EXPECT_EQ(7, sb.st_mtim.tv_nsec);
239 EXPECT_EQ(5, sb.st_ctim.tv_sec);
240 EXPECT_EQ(8, sb.st_ctim.tv_nsec);
241 EXPECT_EQ(9ull, sb.st_nlink);
242 EXPECT_EQ(10ul, sb.st_uid);
243 EXPECT_EQ(11ul, sb.st_gid);
244 EXPECT_EQ(12ul, sb.st_rdev);
245 EXPECT_EQ((blksize_t)12345, sb.st_blksize);
246 EXPECT_EQ(ino, sb.st_ino);
247 EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
248
249 /*
250 * st_birthtim and st_flags are not supported by the fuse protocol.
251 * They're only supported as OS-specific extensions to OSX. For
252 * birthtime, the convention for "not supported" is "negative one
253 * second".
254 */
255 EXPECT_EQ(-1, sb.st_birthtim.tv_sec);
256 EXPECT_EQ(0, sb.st_birthtim.tv_nsec);
257 EXPECT_EQ(0u, sb.st_flags);
258 }
259
260 /*
261 * FUSE_GETATTR returns a different file type, even though the entry cache
262 * hasn't expired. This is a server bug! It probably means that the server
263 * removed the file and recreated it with the same inode but a different vtyp.
264 * The best thing fusefs can do is return ENOENT to the caller. After all, the
265 * entry must not have existed recently.
266 */
TEST_F(Getattr,vtyp_conflict)267 TEST_F(Getattr, vtyp_conflict)
268 {
269 const char FULLPATH[] = "mountpoint/some_file.txt";
270 const char RELPATH[] = "some_file.txt";
271 const uint64_t ino = 42;
272 struct stat sb;
273 sem_t sem;
274
275 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
276
277 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
278 .WillOnce(Invoke(
279 ReturnImmediate([=](auto in __unused, auto& out) {
280 SET_OUT_HEADER_LEN(out, entry);
281 out.body.entry.attr.mode = S_IFREG | 0644;
282 out.body.entry.nodeid = ino;
283 out.body.entry.attr.nlink = 1;
284 out.body.entry.attr_valid = 0;
285 out.body.entry.entry_valid = UINT64_MAX;
286 })));
287 EXPECT_CALL(*m_mock, process(
288 ResultOf([](auto in) {
289 return (in.header.opcode == FUSE_GETATTR &&
290 in.body.getattr.getattr_flags == 0 &&
291 in.header.nodeid == ino);
292 }, Eq(true)),
293 _)
294 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
295 SET_OUT_HEADER_LEN(out, attr);
296 out.body.attr.attr.ino = ino; // Must match nodeid
297 out.body.attr.attr.mode = S_IFDIR | 0755; // Changed!
298 out.body.attr.attr.nlink = 2;
299 })));
300 // We should reclaim stale vnodes
301 expect_forget(ino, 1, &sem);
302
303 ASSERT_NE(0, stat(FULLPATH, &sb));
304 EXPECT_EQ(errno, ENOENT);
305
306 sem_wait(&sem);
307 sem_destroy(&sem);
308 }
309
TEST_F(Getattr_7_8,ok)310 TEST_F(Getattr_7_8, ok)
311 {
312 const char FULLPATH[] = "mountpoint/some_file.txt";
313 const char RELPATH[] = "some_file.txt";
314 const uint64_t ino = 42;
315 struct stat sb;
316
317 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
318 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
319 SET_OUT_HEADER_LEN(out, entry_7_8);
320 out.body.entry.attr.mode = S_IFREG | 0644;
321 out.body.entry.nodeid = ino;
322 out.body.entry.attr.nlink = 1;
323 out.body.entry.attr.size = 1;
324 })));
325 EXPECT_CALL(*m_mock, process(
326 ResultOf([](auto in) {
327 return (in.header.opcode == FUSE_GETATTR &&
328 in.header.nodeid == ino);
329 }, Eq(true)),
330 _)
331 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
332 SET_OUT_HEADER_LEN(out, attr_7_8);
333 out.body.attr.attr.ino = ino; // Must match nodeid
334 out.body.attr.attr.mode = S_IFREG | 0644;
335 out.body.attr.attr.size = 1;
336 out.body.attr.attr.blocks = 2;
337 out.body.attr.attr.atime = 3;
338 out.body.attr.attr.mtime = 4;
339 out.body.attr.attr.ctime = 5;
340 out.body.attr.attr.atimensec = 6;
341 out.body.attr.attr.mtimensec = 7;
342 out.body.attr.attr.ctimensec = 8;
343 out.body.attr.attr.nlink = 9;
344 out.body.attr.attr.uid = 10;
345 out.body.attr.attr.gid = 11;
346 out.body.attr.attr.rdev = 12;
347 })));
348
349 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
350 EXPECT_EQ(1, sb.st_size);
351 EXPECT_EQ(2, sb.st_blocks);
352 EXPECT_EQ(3, sb.st_atim.tv_sec);
353 EXPECT_EQ(6, sb.st_atim.tv_nsec);
354 EXPECT_EQ(4, sb.st_mtim.tv_sec);
355 EXPECT_EQ(7, sb.st_mtim.tv_nsec);
356 EXPECT_EQ(5, sb.st_ctim.tv_sec);
357 EXPECT_EQ(8, sb.st_ctim.tv_nsec);
358 EXPECT_EQ(9ull, sb.st_nlink);
359 EXPECT_EQ(10ul, sb.st_uid);
360 EXPECT_EQ(11ul, sb.st_gid);
361 EXPECT_EQ(12ul, sb.st_rdev);
362 EXPECT_EQ(ino, sb.st_ino);
363 EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
364
365 //st_birthtim and st_flags are not supported by protocol 7.8. They're
366 //only supported as OS-specific extensions to OSX.
367 }
368