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 * $FreeBSD$
31 */
32
33extern "C" {
34#include <sys/param.h>
35
36#include <semaphore.h>
37}
38
39#include "mockfs.hh"
40#include "utils.hh"
41
42using namespace testing;
43
44class Getattr : public FuseTest {
45public:
46void 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
64class Getattr_7_8: public FuseTest {
65public:
66virtual 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 */
76TEST_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 */
112TEST_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 */
145TEST_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
171TEST_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
200TEST_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
259TEST_F(Getattr_7_8, ok)
260{
261	const char FULLPATH[] = "mountpoint/some_file.txt";
262	const char RELPATH[] = "some_file.txt";
263	const uint64_t ino = 42;
264	struct stat sb;
265
266	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
267	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
268		SET_OUT_HEADER_LEN(out, entry_7_8);
269		out.body.entry.attr.mode = S_IFREG | 0644;
270		out.body.entry.nodeid = ino;
271		out.body.entry.attr.nlink = 1;
272		out.body.entry.attr.size = 1;
273	})));
274	EXPECT_CALL(*m_mock, process(
275		ResultOf([](auto in) {
276			return (in.header.opcode == FUSE_GETATTR &&
277				in.header.nodeid == ino);
278		}, Eq(true)),
279		_)
280	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
281		SET_OUT_HEADER_LEN(out, attr_7_8);
282		out.body.attr.attr.ino = ino;	// Must match nodeid
283		out.body.attr.attr.mode = S_IFREG | 0644;
284		out.body.attr.attr.size = 1;
285		out.body.attr.attr.blocks = 2;
286		out.body.attr.attr.atime = 3;
287		out.body.attr.attr.mtime = 4;
288		out.body.attr.attr.ctime = 5;
289		out.body.attr.attr.atimensec = 6;
290		out.body.attr.attr.mtimensec = 7;
291		out.body.attr.attr.ctimensec = 8;
292		out.body.attr.attr.nlink = 9;
293		out.body.attr.attr.uid = 10;
294		out.body.attr.attr.gid = 11;
295		out.body.attr.attr.rdev = 12;
296	})));
297
298	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
299	EXPECT_EQ(1, sb.st_size);
300	EXPECT_EQ(2, sb.st_blocks);
301	EXPECT_EQ(3, sb.st_atim.tv_sec);
302	EXPECT_EQ(6, sb.st_atim.tv_nsec);
303	EXPECT_EQ(4, sb.st_mtim.tv_sec);
304	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
305	EXPECT_EQ(5, sb.st_ctim.tv_sec);
306	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
307	EXPECT_EQ(9ull, sb.st_nlink);
308	EXPECT_EQ(10ul, sb.st_uid);
309	EXPECT_EQ(11ul, sb.st_gid);
310	EXPECT_EQ(12ul, sb.st_rdev);
311	EXPECT_EQ(ino, sb.st_ino);
312	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
313
314	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
315	//only supported as OS-specific extensions to OSX.
316}
317