xref: /freebsd/tests/sys/fs/fusefs/getattr.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
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 <sys/param.h>
33 }
34 
35 #include "mockfs.hh"
36 #include "utils.hh"
37 
38 using namespace testing;
39 
40 class Getattr : public FuseTest {
41 public:
42 void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
43 	uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
44 {
45 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
46 	.Times(times)
47 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
48 		SET_OUT_HEADER_LEN(out, entry);
49 		out.body.entry.attr.mode = mode;
50 		out.body.entry.nodeid = ino;
51 		out.body.entry.attr.nlink = 1;
52 		out.body.entry.attr_valid = attr_valid;
53 		out.body.entry.attr_valid_nsec = attr_valid_nsec;
54 		out.body.entry.attr.size = size;
55 		out.body.entry.entry_valid = UINT64_MAX;
56 	})));
57 }
58 };
59 
60 class Getattr_7_8: public FuseTest {
61 public:
62 virtual void SetUp() {
63 	m_kernel_minor_version = 8;
64 	FuseTest::SetUp();
65 }
66 };
67 
68 /*
69  * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
70  * should use the cached attributes, rather than query the daemon
71  */
72 TEST_F(Getattr, attr_cache)
73 {
74 	const char FULLPATH[] = "mountpoint/some_file.txt";
75 	const char RELPATH[] = "some_file.txt";
76 	const uint64_t ino = 42;
77 	struct stat sb;
78 
79 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
80 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
81 		SET_OUT_HEADER_LEN(out, entry);
82 		out.body.entry.attr.mode = S_IFREG | 0644;
83 		out.body.entry.nodeid = ino;
84 		out.body.entry.entry_valid = UINT64_MAX;
85 	})));
86 	EXPECT_CALL(*m_mock, process(
87 		ResultOf([](auto in) {
88 			return (in.header.opcode == FUSE_GETATTR &&
89 				in.header.nodeid == ino);
90 		}, Eq(true)),
91 		_)
92 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
93 		SET_OUT_HEADER_LEN(out, attr);
94 		out.body.attr.attr_valid = UINT64_MAX;
95 		out.body.attr.attr.ino = ino;	// Must match nodeid
96 		out.body.attr.attr.mode = S_IFREG | 0644;
97 	})));
98 	EXPECT_EQ(0, stat(FULLPATH, &sb));
99 	/* The second stat(2) should use cached attributes */
100 	EXPECT_EQ(0, stat(FULLPATH, &sb));
101 }
102 
103 /*
104  * If getattr returns a finite but non-zero cache timeout, then we should
105  * discard the cached attributes and requery the daemon after the timeout
106  * period passes.
107  */
108 TEST_F(Getattr, attr_cache_timeout)
109 {
110 	const char FULLPATH[] = "mountpoint/some_file.txt";
111 	const char RELPATH[] = "some_file.txt";
112 	const uint64_t ino = 42;
113 	struct stat sb;
114 
115 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
116 	EXPECT_CALL(*m_mock, process(
117 		ResultOf([](auto in) {
118 			return (in.header.opcode == FUSE_GETATTR &&
119 				in.header.nodeid == ino);
120 		}, Eq(true)),
121 		_)
122 	).Times(2)
123 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
124 		SET_OUT_HEADER_LEN(out, attr);
125 		out.body.attr.attr_valid_nsec = NAP_NS / 2;
126 		out.body.attr.attr_valid = 0;
127 		out.body.attr.attr.ino = ino;	// Must match nodeid
128 		out.body.attr.attr.mode = S_IFREG | 0644;
129 	})));
130 
131 	EXPECT_EQ(0, stat(FULLPATH, &sb));
132 	nap();
133 	/* Timeout has expired. stat(2) should requery the daemon */
134 	EXPECT_EQ(0, stat(FULLPATH, &sb));
135 }
136 
137 /*
138  * If attr.blksize is zero, then the kernel should use a default value for
139  * st_blksize
140  */
141 TEST_F(Getattr, blksize_zero)
142 {
143 	const char FULLPATH[] = "mountpoint/some_file.txt";
144 	const char RELPATH[] = "some_file.txt";
145 	const uint64_t ino = 42;
146 	struct stat sb;
147 
148 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
149 	EXPECT_CALL(*m_mock, process(
150 		ResultOf([](auto in) {
151 			return (in.header.opcode == FUSE_GETATTR &&
152 				in.header.nodeid == ino);
153 		}, Eq(true)),
154 		_)
155 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
156 		SET_OUT_HEADER_LEN(out, attr);
157 		out.body.attr.attr.mode = S_IFREG | 0644;
158 		out.body.attr.attr.ino = ino;	// Must match nodeid
159 		out.body.attr.attr.blksize = 0;
160 	})));
161 
162 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
163 	EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
164 }
165 
166 TEST_F(Getattr, enoent)
167 {
168 	const char FULLPATH[] = "mountpoint/some_file.txt";
169 	const char RELPATH[] = "some_file.txt";
170 	struct stat sb;
171 	const uint64_t ino = 42;
172 
173 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
174 	EXPECT_CALL(*m_mock, process(
175 		ResultOf([](auto in) {
176 			return (in.header.opcode == FUSE_GETATTR &&
177 				in.header.nodeid == ino);
178 		}, Eq(true)),
179 		_)
180 	).WillOnce(Invoke(ReturnErrno(ENOENT)));
181 	EXPECT_NE(0, stat(FULLPATH, &sb));
182 	EXPECT_EQ(ENOENT, errno);
183 }
184 
185 TEST_F(Getattr, ok)
186 {
187 	const char FULLPATH[] = "mountpoint/some_file.txt";
188 	const char RELPATH[] = "some_file.txt";
189 	const uint64_t ino = 42;
190 	struct stat sb;
191 
192 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
193 	EXPECT_CALL(*m_mock, process(
194 		ResultOf([](auto in) {
195 			return (in.header.opcode == FUSE_GETATTR &&
196 				in.header.nodeid == ino);
197 		}, Eq(true)),
198 		_)
199 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
200 		SET_OUT_HEADER_LEN(out, attr);
201 		out.body.attr.attr.ino = ino;	// Must match nodeid
202 		out.body.attr.attr.mode = S_IFREG | 0644;
203 		out.body.attr.attr.size = 1;
204 		out.body.attr.attr.blocks = 2;
205 		out.body.attr.attr.atime = 3;
206 		out.body.attr.attr.mtime = 4;
207 		out.body.attr.attr.ctime = 5;
208 		out.body.attr.attr.atimensec = 6;
209 		out.body.attr.attr.mtimensec = 7;
210 		out.body.attr.attr.ctimensec = 8;
211 		out.body.attr.attr.nlink = 9;
212 		out.body.attr.attr.uid = 10;
213 		out.body.attr.attr.gid = 11;
214 		out.body.attr.attr.rdev = 12;
215 		out.body.attr.attr.blksize = 12345;
216 	})));
217 
218 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
219 	EXPECT_EQ(1, sb.st_size);
220 	EXPECT_EQ(2, sb.st_blocks);
221 	EXPECT_EQ(3, sb.st_atim.tv_sec);
222 	EXPECT_EQ(6, sb.st_atim.tv_nsec);
223 	EXPECT_EQ(4, sb.st_mtim.tv_sec);
224 	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
225 	EXPECT_EQ(5, sb.st_ctim.tv_sec);
226 	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
227 	EXPECT_EQ(9ull, sb.st_nlink);
228 	EXPECT_EQ(10ul, sb.st_uid);
229 	EXPECT_EQ(11ul, sb.st_gid);
230 	EXPECT_EQ(12ul, sb.st_rdev);
231 	EXPECT_EQ((blksize_t)12345, sb.st_blksize);
232 	EXPECT_EQ(ino, sb.st_ino);
233 	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
234 
235 	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
236 	//only supported as OS-specific extensions to OSX.
237 	//EXPECT_EQ(, sb.st_birthtim);
238 	//EXPECT_EQ(, sb.st_flags);
239 
240 	//FUSE can't set st_blksize until protocol 7.9
241 }
242 
243 TEST_F(Getattr_7_8, ok)
244 {
245 	const char FULLPATH[] = "mountpoint/some_file.txt";
246 	const char RELPATH[] = "some_file.txt";
247 	const uint64_t ino = 42;
248 	struct stat sb;
249 
250 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
251 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
252 		SET_OUT_HEADER_LEN(out, entry_7_8);
253 		out.body.entry.attr.mode = S_IFREG | 0644;
254 		out.body.entry.nodeid = ino;
255 		out.body.entry.attr.nlink = 1;
256 		out.body.entry.attr.size = 1;
257 	})));
258 	EXPECT_CALL(*m_mock, process(
259 		ResultOf([](auto in) {
260 			return (in.header.opcode == FUSE_GETATTR &&
261 				in.header.nodeid == ino);
262 		}, Eq(true)),
263 		_)
264 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
265 		SET_OUT_HEADER_LEN(out, attr_7_8);
266 		out.body.attr.attr.ino = ino;	// Must match nodeid
267 		out.body.attr.attr.mode = S_IFREG | 0644;
268 		out.body.attr.attr.size = 1;
269 		out.body.attr.attr.blocks = 2;
270 		out.body.attr.attr.atime = 3;
271 		out.body.attr.attr.mtime = 4;
272 		out.body.attr.attr.ctime = 5;
273 		out.body.attr.attr.atimensec = 6;
274 		out.body.attr.attr.mtimensec = 7;
275 		out.body.attr.attr.ctimensec = 8;
276 		out.body.attr.attr.nlink = 9;
277 		out.body.attr.attr.uid = 10;
278 		out.body.attr.attr.gid = 11;
279 		out.body.attr.attr.rdev = 12;
280 	})));
281 
282 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
283 	EXPECT_EQ(1, sb.st_size);
284 	EXPECT_EQ(2, sb.st_blocks);
285 	EXPECT_EQ(3, sb.st_atim.tv_sec);
286 	EXPECT_EQ(6, sb.st_atim.tv_nsec);
287 	EXPECT_EQ(4, sb.st_mtim.tv_sec);
288 	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
289 	EXPECT_EQ(5, sb.st_ctim.tv_sec);
290 	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
291 	EXPECT_EQ(9ull, sb.st_nlink);
292 	EXPECT_EQ(10ul, sb.st_uid);
293 	EXPECT_EQ(11ul, sb.st_gid);
294 	EXPECT_EQ(12ul, sb.st_rdev);
295 	EXPECT_EQ(ino, sb.st_ino);
296 	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
297 
298 	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
299 	//only supported as OS-specific extensions to OSX.
300 }
301