xref: /freebsd/tests/sys/fs/fusefs/getattr.cc (revision 9f44a47fd07924afc035991af15d84e6585dea4f)
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  * $FreeBSD$
31  */
32 
33 extern "C" {
34 #include <sys/param.h>
35 
36 #include <semaphore.h>
37 }
38 
39 #include "mockfs.hh"
40 #include "utils.hh"
41 
42 using namespace testing;
43 
44 class Getattr : public FuseTest {
45 public:
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)
48 {
49 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
50 	.Times(times)
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;
60 	})));
61 }
62 };
63 
64 class Getattr_7_8: public FuseTest {
65 public:
66 virtual void SetUp() {
67 	m_kernel_minor_version = 8;
68 	FuseTest::SetUp();
69 }
70 };
71 
72 /*
73  * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
74  * should use the cached attributes, rather than query the daemon
75  */
76 TEST_F(Getattr, attr_cache)
77 {
78 	const char FULLPATH[] = "mountpoint/some_file.txt";
79 	const char RELPATH[] = "some_file.txt";
80 	const uint64_t ino = 42;
81 	struct stat sb;
82 
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;
89 	})));
90 	EXPECT_CALL(*m_mock, process(
91 		ResultOf([](auto in) {
92 			return (in.header.opcode == FUSE_GETATTR &&
93 				in.header.nodeid == ino);
94 		}, Eq(true)),
95 		_)
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;
101 	})));
102 	EXPECT_EQ(0, stat(FULLPATH, &sb));
103 	/* The second stat(2) should use cached attributes */
104 	EXPECT_EQ(0, stat(FULLPATH, &sb));
105 }
106 
107 /*
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
110  * period passes.
111  */
112 TEST_F(Getattr, attr_cache_timeout)
113 {
114 	const char FULLPATH[] = "mountpoint/some_file.txt";
115 	const char RELPATH[] = "some_file.txt";
116 	const uint64_t ino = 42;
117 	struct stat sb;
118 
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);
124 		}, Eq(true)),
125 		_)
126 	).Times(2)
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;
133 	})));
134 
135 	EXPECT_EQ(0, stat(FULLPATH, &sb));
136 	nap();
137 	/* Timeout has expired. stat(2) should requery the daemon */
138 	EXPECT_EQ(0, stat(FULLPATH, &sb));
139 }
140 
141 /*
142  * If attr.blksize is zero, then the kernel should use a default value for
143  * st_blksize
144  */
145 TEST_F(Getattr, blksize_zero)
146 {
147 	const char FULLPATH[] = "mountpoint/some_file.txt";
148 	const char RELPATH[] = "some_file.txt";
149 	const uint64_t ino = 42;
150 	struct stat sb;
151 
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);
157 		}, Eq(true)),
158 		_)
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;
165 	})));
166 
167 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
168 	EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
169 }
170 
171 TEST_F(Getattr, enoent)
172 {
173 	const char FULLPATH[] = "mountpoint/some_file.txt";
174 	const char RELPATH[] = "some_file.txt";
175 	struct stat sb;
176 	const uint64_t ino = 42;
177 	sem_t sem;
178 
179 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
180 
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);
186 		}, Eq(true)),
187 		_)
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);
192 
193 	EXPECT_NE(0, stat(FULLPATH, &sb));
194 	EXPECT_EQ(ENOENT, errno);
195 
196 	sem_wait(&sem);
197 	sem_destroy(&sem);
198 }
199 
200 TEST_F(Getattr, ok)
201 {
202 	const char FULLPATH[] = "mountpoint/some_file.txt";
203 	const char RELPATH[] = "some_file.txt";
204 	const uint64_t ino = 42;
205 	struct stat sb;
206 
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);
213 		}, Eq(true)),
214 		_)
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;
232 	})));
233 
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);
250 
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);
255 
256 	//FUSE can't set st_blksize until protocol 7.9
257 }
258 
259 /*
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.
265  */
266 TEST_F(Getattr, vtyp_conflict)
267 {
268 	const char FULLPATH[] = "mountpoint/some_file.txt";
269 	const char RELPATH[] = "some_file.txt";
270 	const uint64_t ino = 42;
271 	struct stat sb;
272 	sem_t sem;
273 
274 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
275 
276 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
277 	.WillOnce(Invoke(
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;
285 	})));
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);
291 		}, Eq(true)),
292 		_)
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;
298 	})));
299 	// We should reclaim stale vnodes
300 	expect_forget(ino, 1, &sem);
301 
302 	ASSERT_NE(0, stat(FULLPATH, &sb));
303 	EXPECT_EQ(errno, ENOENT);
304 
305 	sem_wait(&sem);
306 	sem_destroy(&sem);
307 }
308 
309 TEST_F(Getattr_7_8, ok)
310 {
311 	const char FULLPATH[] = "mountpoint/some_file.txt";
312 	const char RELPATH[] = "some_file.txt";
313 	const uint64_t ino = 42;
314 	struct stat sb;
315 
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;
323 	})));
324 	EXPECT_CALL(*m_mock, process(
325 		ResultOf([](auto in) {
326 			return (in.header.opcode == FUSE_GETATTR &&
327 				in.header.nodeid == ino);
328 		}, Eq(true)),
329 		_)
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;
346 	})));
347 
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);
363 
364 	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
365 	//only supported as OS-specific extensions to OSX.
366 }
367