2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
34 #include <sys/param.h>
36 #include <semaphore.h>
42 using namespace testing;
44 class Getattr : public FuseTest {
46 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
47 uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
49 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
51 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
52 SET_OUT_HEADER_LEN(out, entry);
53 out.body.entry.attr.mode = mode;
54 out.body.entry.nodeid = ino;
55 out.body.entry.attr.nlink = 1;
56 out.body.entry.attr_valid = attr_valid;
57 out.body.entry.attr_valid_nsec = attr_valid_nsec;
58 out.body.entry.attr.size = size;
59 out.body.entry.entry_valid = UINT64_MAX;
64 class Getattr_7_8: public FuseTest {
66 virtual void SetUp() {
67 m_kernel_minor_version = 8;
73 * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
74 * should use the cached attributes, rather than query the daemon
76 TEST_F(Getattr, attr_cache)
78 const char FULLPATH[] = "mountpoint/some_file.txt";
79 const char RELPATH[] = "some_file.txt";
80 const uint64_t ino = 42;
83 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
84 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
85 SET_OUT_HEADER_LEN(out, entry);
86 out.body.entry.attr.mode = S_IFREG | 0644;
87 out.body.entry.nodeid = ino;
88 out.body.entry.entry_valid = UINT64_MAX;
90 EXPECT_CALL(*m_mock, process(
91 ResultOf([](auto in) {
92 return (in.header.opcode == FUSE_GETATTR &&
93 in.header.nodeid == ino);
96 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
97 SET_OUT_HEADER_LEN(out, attr);
98 out.body.attr.attr_valid = UINT64_MAX;
99 out.body.attr.attr.ino = ino; // Must match nodeid
100 out.body.attr.attr.mode = S_IFREG | 0644;
102 EXPECT_EQ(0, stat(FULLPATH, &sb));
103 /* The second stat(2) should use cached attributes */
104 EXPECT_EQ(0, stat(FULLPATH, &sb));
108 * If getattr returns a finite but non-zero cache timeout, then we should
109 * discard the cached attributes and requery the daemon after the timeout
112 TEST_F(Getattr, attr_cache_timeout)
114 const char FULLPATH[] = "mountpoint/some_file.txt";
115 const char RELPATH[] = "some_file.txt";
116 const uint64_t ino = 42;
119 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
120 EXPECT_CALL(*m_mock, process(
121 ResultOf([](auto in) {
122 return (in.header.opcode == FUSE_GETATTR &&
123 in.header.nodeid == ino);
127 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
128 SET_OUT_HEADER_LEN(out, attr);
129 out.body.attr.attr_valid_nsec = NAP_NS / 2;
130 out.body.attr.attr_valid = 0;
131 out.body.attr.attr.ino = ino; // Must match nodeid
132 out.body.attr.attr.mode = S_IFREG | 0644;
135 EXPECT_EQ(0, stat(FULLPATH, &sb));
137 /* Timeout has expired. stat(2) should requery the daemon */
138 EXPECT_EQ(0, stat(FULLPATH, &sb));
142 * If attr.blksize is zero, then the kernel should use a default value for
145 TEST_F(Getattr, blksize_zero)
147 const char FULLPATH[] = "mountpoint/some_file.txt";
148 const char RELPATH[] = "some_file.txt";
149 const uint64_t ino = 42;
152 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
153 EXPECT_CALL(*m_mock, process(
154 ResultOf([](auto in) {
155 return (in.header.opcode == FUSE_GETATTR &&
156 in.header.nodeid == ino);
159 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
160 SET_OUT_HEADER_LEN(out, attr);
161 out.body.attr.attr.mode = S_IFREG | 0644;
162 out.body.attr.attr.ino = ino; // Must match nodeid
163 out.body.attr.attr.blksize = 0;
164 out.body.attr.attr.size = 1;
167 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
168 EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
171 TEST_F(Getattr, enoent)
173 const char FULLPATH[] = "mountpoint/some_file.txt";
174 const char RELPATH[] = "some_file.txt";
176 const uint64_t ino = 42;
179 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
181 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
182 EXPECT_CALL(*m_mock, process(
183 ResultOf([](auto in) {
184 return (in.header.opcode == FUSE_GETATTR &&
185 in.header.nodeid == ino);
188 ).WillOnce(Invoke(ReturnErrno(ENOENT)));
189 // Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
190 // and send a FUSE_FORGET
191 expect_forget(ino, 1, &sem);
193 EXPECT_NE(0, stat(FULLPATH, &sb));
194 EXPECT_EQ(ENOENT, errno);
202 const char FULLPATH[] = "mountpoint/some_file.txt";
203 const char RELPATH[] = "some_file.txt";
204 const uint64_t ino = 42;
207 expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
208 EXPECT_CALL(*m_mock, process(
209 ResultOf([](auto in) {
210 return (in.header.opcode == FUSE_GETATTR &&
211 in.body.getattr.getattr_flags == 0 &&
212 in.header.nodeid == ino);
215 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
216 SET_OUT_HEADER_LEN(out, attr);
217 out.body.attr.attr.ino = ino; // Must match nodeid
218 out.body.attr.attr.mode = S_IFREG | 0644;
219 out.body.attr.attr.size = 1;
220 out.body.attr.attr.blocks = 2;
221 out.body.attr.attr.atime = 3;
222 out.body.attr.attr.mtime = 4;
223 out.body.attr.attr.ctime = 5;
224 out.body.attr.attr.atimensec = 6;
225 out.body.attr.attr.mtimensec = 7;
226 out.body.attr.attr.ctimensec = 8;
227 out.body.attr.attr.nlink = 9;
228 out.body.attr.attr.uid = 10;
229 out.body.attr.attr.gid = 11;
230 out.body.attr.attr.rdev = 12;
231 out.body.attr.attr.blksize = 12345;
234 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
235 EXPECT_EQ(1, sb.st_size);
236 EXPECT_EQ(2, sb.st_blocks);
237 EXPECT_EQ(3, sb.st_atim.tv_sec);
238 EXPECT_EQ(6, sb.st_atim.tv_nsec);
239 EXPECT_EQ(4, sb.st_mtim.tv_sec);
240 EXPECT_EQ(7, sb.st_mtim.tv_nsec);
241 EXPECT_EQ(5, sb.st_ctim.tv_sec);
242 EXPECT_EQ(8, sb.st_ctim.tv_nsec);
243 EXPECT_EQ(9ull, sb.st_nlink);
244 EXPECT_EQ(10ul, sb.st_uid);
245 EXPECT_EQ(11ul, sb.st_gid);
246 EXPECT_EQ(12ul, sb.st_rdev);
247 EXPECT_EQ((blksize_t)12345, sb.st_blksize);
248 EXPECT_EQ(ino, sb.st_ino);
249 EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
251 //st_birthtim and st_flags are not supported by protocol 7.8. They're
252 //only supported as OS-specific extensions to OSX.
253 //EXPECT_EQ(, sb.st_birthtim);
254 //EXPECT_EQ(, sb.st_flags);
256 //FUSE can't set st_blksize until protocol 7.9
260 * FUSE_GETATTR returns a different file type, even though the entry cache
261 * hasn't expired. This is a server bug! It probably means that the server
262 * removed the file and recreated it with the same inode but a different vtyp.
263 * The best thing fusefs can do is return ENOENT to the caller. After all, the
264 * entry must not have existed recently.
266 TEST_F(Getattr, vtyp_conflict)
268 const char FULLPATH[] = "mountpoint/some_file.txt";
269 const char RELPATH[] = "some_file.txt";
270 const uint64_t ino = 42;
274 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
276 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
278 ReturnImmediate([=](auto in __unused, auto& out) {
279 SET_OUT_HEADER_LEN(out, entry);
280 out.body.entry.attr.mode = S_IFREG | 0644;
281 out.body.entry.nodeid = ino;
282 out.body.entry.attr.nlink = 1;
283 out.body.entry.attr_valid = 0;
284 out.body.entry.entry_valid = UINT64_MAX;
286 EXPECT_CALL(*m_mock, process(
287 ResultOf([](auto in) {
288 return (in.header.opcode == FUSE_GETATTR &&
289 in.body.getattr.getattr_flags == 0 &&
290 in.header.nodeid == ino);
293 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
294 SET_OUT_HEADER_LEN(out, attr);
295 out.body.attr.attr.ino = ino; // Must match nodeid
296 out.body.attr.attr.mode = S_IFDIR | 0755; // Changed!
297 out.body.attr.attr.nlink = 2;
299 // We should reclaim stale vnodes
300 expect_forget(ino, 1, &sem);
302 ASSERT_NE(0, stat(FULLPATH, &sb));
303 EXPECT_EQ(errno, ENOENT);
309 TEST_F(Getattr_7_8, ok)
311 const char FULLPATH[] = "mountpoint/some_file.txt";
312 const char RELPATH[] = "some_file.txt";
313 const uint64_t ino = 42;
316 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
317 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
318 SET_OUT_HEADER_LEN(out, entry_7_8);
319 out.body.entry.attr.mode = S_IFREG | 0644;
320 out.body.entry.nodeid = ino;
321 out.body.entry.attr.nlink = 1;
322 out.body.entry.attr.size = 1;
324 EXPECT_CALL(*m_mock, process(
325 ResultOf([](auto in) {
326 return (in.header.opcode == FUSE_GETATTR &&
327 in.header.nodeid == ino);
330 ).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
331 SET_OUT_HEADER_LEN(out, attr_7_8);
332 out.body.attr.attr.ino = ino; // Must match nodeid
333 out.body.attr.attr.mode = S_IFREG | 0644;
334 out.body.attr.attr.size = 1;
335 out.body.attr.attr.blocks = 2;
336 out.body.attr.attr.atime = 3;
337 out.body.attr.attr.mtime = 4;
338 out.body.attr.attr.ctime = 5;
339 out.body.attr.attr.atimensec = 6;
340 out.body.attr.attr.mtimensec = 7;
341 out.body.attr.attr.ctimensec = 8;
342 out.body.attr.attr.nlink = 9;
343 out.body.attr.attr.uid = 10;
344 out.body.attr.attr.gid = 11;
345 out.body.attr.attr.rdev = 12;
348 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
349 EXPECT_EQ(1, sb.st_size);
350 EXPECT_EQ(2, sb.st_blocks);
351 EXPECT_EQ(3, sb.st_atim.tv_sec);
352 EXPECT_EQ(6, sb.st_atim.tv_nsec);
353 EXPECT_EQ(4, sb.st_mtim.tv_sec);
354 EXPECT_EQ(7, sb.st_mtim.tv_nsec);
355 EXPECT_EQ(5, sb.st_ctim.tv_sec);
356 EXPECT_EQ(8, sb.st_ctim.tv_nsec);
357 EXPECT_EQ(9ull, sb.st_nlink);
358 EXPECT_EQ(10ul, sb.st_uid);
359 EXPECT_EQ(11ul, sb.st_gid);
360 EXPECT_EQ(12ul, sb.st_rdev);
361 EXPECT_EQ(ino, sb.st_ino);
362 EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
364 //st_birthtim and st_flags are not supported by protocol 7.8. They're
365 //only supported as OS-specific extensions to OSX.