xref: /freebsd/tests/sys/fs/fusefs/getattr.cc (revision 55b80e2ca52c4b27c4920d372a6e71ac9ab7da9e)
19821f1d3SAlan Somers /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
31e7f73af1SAlan Somers extern "C" {
32e7f73af1SAlan Somers #include <sys/param.h>
3327537990SAlan Somers 
3427537990SAlan Somers #include <semaphore.h>
35e7f73af1SAlan Somers }
36e7f73af1SAlan Somers 
379821f1d3SAlan Somers #include "mockfs.hh"
389821f1d3SAlan Somers #include "utils.hh"
399821f1d3SAlan Somers 
409821f1d3SAlan Somers using namespace testing;
419821f1d3SAlan Somers 
42cad67791SAlan Somers class Getattr : public FuseTest {
43cad67791SAlan Somers 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)44cad67791SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
45cad67791SAlan Somers 	uint64_t size, int times, uint64_t attr_valid, uint32_t attr_valid_nsec)
46cad67791SAlan Somers {
47a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
48cad67791SAlan Somers 	.Times(times)
4929edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
50cad67791SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
5129edc611SAlan Somers 		out.body.entry.attr.mode = mode;
5229edc611SAlan Somers 		out.body.entry.nodeid = ino;
5329edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
5429edc611SAlan Somers 		out.body.entry.attr_valid = attr_valid;
5529edc611SAlan Somers 		out.body.entry.attr_valid_nsec = attr_valid_nsec;
5629edc611SAlan Somers 		out.body.entry.attr.size = size;
5729edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
58cad67791SAlan Somers 	})));
59cad67791SAlan Somers }
60cad67791SAlan Somers };
61cad67791SAlan Somers 
6216bd2d47SAlan Somers class Getattr_7_8: public FuseTest {
6316bd2d47SAlan Somers public:
SetUp()6416bd2d47SAlan Somers virtual void SetUp() {
6516bd2d47SAlan Somers 	m_kernel_minor_version = 8;
6616bd2d47SAlan Somers 	FuseTest::SetUp();
6716bd2d47SAlan Somers }
6816bd2d47SAlan Somers };
699821f1d3SAlan Somers 
709821f1d3SAlan Somers /*
719821f1d3SAlan Somers  * If getattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
729821f1d3SAlan Somers  * should use the cached attributes, rather than query the daemon
739821f1d3SAlan Somers  */
TEST_F(Getattr,attr_cache)74cad67791SAlan Somers TEST_F(Getattr, attr_cache)
759821f1d3SAlan Somers {
769821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
779821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
789821f1d3SAlan Somers 	const uint64_t ino = 42;
799821f1d3SAlan Somers 	struct stat sb;
809821f1d3SAlan Somers 
81a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
8229edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
839821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
8429edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
8529edc611SAlan Somers 		out.body.entry.nodeid = ino;
8629edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
879821f1d3SAlan Somers 	})));
889821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
899821f1d3SAlan Somers 		ResultOf([](auto in) {
9029edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
9129edc611SAlan Somers 				in.header.nodeid == ino);
929821f1d3SAlan Somers 		}, Eq(true)),
939821f1d3SAlan Somers 		_)
9429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
959821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
9629edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
9729edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
9829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
999821f1d3SAlan Somers 	})));
1009821f1d3SAlan Somers 	EXPECT_EQ(0, stat(FULLPATH, &sb));
1019821f1d3SAlan Somers 	/* The second stat(2) should use cached attributes */
1029821f1d3SAlan Somers 	EXPECT_EQ(0, stat(FULLPATH, &sb));
1039821f1d3SAlan Somers }
1049821f1d3SAlan Somers 
1059821f1d3SAlan Somers /*
1069821f1d3SAlan Somers  * If getattr returns a finite but non-zero cache timeout, then we should
1079821f1d3SAlan Somers  * discard the cached attributes and requery the daemon after the timeout
1089821f1d3SAlan Somers  * period passes.
1099821f1d3SAlan Somers  */
TEST_F(Getattr,attr_cache_timeout)1103f2c630cSAlan Somers TEST_F(Getattr, attr_cache_timeout)
1119821f1d3SAlan Somers {
1129821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1139821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1149821f1d3SAlan Somers 	const uint64_t ino = 42;
1159821f1d3SAlan Somers 	struct stat sb;
1169821f1d3SAlan Somers 
1173f2c630cSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
1189821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1199821f1d3SAlan Somers 		ResultOf([](auto in) {
12029edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
12129edc611SAlan Somers 				in.header.nodeid == ino);
1229821f1d3SAlan Somers 		}, Eq(true)),
1239821f1d3SAlan Somers 		_)
1249821f1d3SAlan Somers 	).Times(2)
12529edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
1269821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
12729edc611SAlan Somers 		out.body.attr.attr_valid_nsec = NAP_NS / 2;
12829edc611SAlan Somers 		out.body.attr.attr_valid = 0;
12929edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
13029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
1319821f1d3SAlan Somers 	})));
1323f2c630cSAlan Somers 
1339821f1d3SAlan Somers 	EXPECT_EQ(0, stat(FULLPATH, &sb));
134a87257acSAlan Somers 	nap();
1353f2c630cSAlan Somers 	/* Timeout has expired. stat(2) should requery the daemon */
1369821f1d3SAlan Somers 	EXPECT_EQ(0, stat(FULLPATH, &sb));
1379821f1d3SAlan Somers }
1389821f1d3SAlan Somers 
139e7f73af1SAlan Somers /*
140e7f73af1SAlan Somers  * If attr.blksize is zero, then the kernel should use a default value for
141e7f73af1SAlan Somers  * st_blksize
142e7f73af1SAlan Somers  */
TEST_F(Getattr,blksize_zero)143e7f73af1SAlan Somers TEST_F(Getattr, blksize_zero)
144e7f73af1SAlan Somers {
145e7f73af1SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
146e7f73af1SAlan Somers 	const char RELPATH[] = "some_file.txt";
147e7f73af1SAlan Somers 	const uint64_t ino = 42;
148e7f73af1SAlan Somers 	struct stat sb;
149e7f73af1SAlan Somers 
150e7f73af1SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
151e7f73af1SAlan Somers 	EXPECT_CALL(*m_mock, process(
152e7f73af1SAlan Somers 		ResultOf([](auto in) {
15329edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
15429edc611SAlan Somers 				in.header.nodeid == ino);
155e7f73af1SAlan Somers 		}, Eq(true)),
156e7f73af1SAlan Somers 		_)
15729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
158e7f73af1SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
15929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
16029edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
16129edc611SAlan Somers 		out.body.attr.attr.blksize = 0;
162b0ecfb42SAlan Somers 		out.body.attr.attr.size = 1;
163e7f73af1SAlan Somers 	})));
164e7f73af1SAlan Somers 
165e7f73af1SAlan Somers 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
166e7f73af1SAlan Somers 	EXPECT_EQ((blksize_t)PAGE_SIZE, sb.st_blksize);
167e7f73af1SAlan Somers }
168e7f73af1SAlan Somers 
TEST_F(Getattr,enoent)1699821f1d3SAlan Somers TEST_F(Getattr, enoent)
1709821f1d3SAlan Somers {
1719821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1729821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1739821f1d3SAlan Somers 	struct stat sb;
1749821f1d3SAlan Somers 	const uint64_t ino = 42;
17527537990SAlan Somers 	sem_t sem;
17627537990SAlan Somers 
17727537990SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
1789821f1d3SAlan Somers 
179cad67791SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1, 0, 0);
1809821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1819821f1d3SAlan Somers 		ResultOf([](auto in) {
18229edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
18329edc611SAlan Somers 				in.header.nodeid == ino);
1849821f1d3SAlan Somers 		}, Eq(true)),
1859821f1d3SAlan Somers 		_)
1869821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(ENOENT)));
18727537990SAlan Somers 	// Since FUSE_GETATTR returns ENOENT, the kernel will reclaim the vnode
18827537990SAlan Somers 	// and send a FUSE_FORGET
18927537990SAlan Somers 	expect_forget(ino, 1, &sem);
19027537990SAlan Somers 
1919821f1d3SAlan Somers 	EXPECT_NE(0, stat(FULLPATH, &sb));
1929821f1d3SAlan Somers 	EXPECT_EQ(ENOENT, errno);
19327537990SAlan Somers 
19427537990SAlan Somers 	sem_wait(&sem);
19527537990SAlan Somers 	sem_destroy(&sem);
1969821f1d3SAlan Somers }
1979821f1d3SAlan Somers 
TEST_F(Getattr,ok)1989821f1d3SAlan Somers TEST_F(Getattr, ok)
1999821f1d3SAlan Somers {
2009821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2019821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2029821f1d3SAlan Somers 	const uint64_t ino = 42;
2039821f1d3SAlan Somers 	struct stat sb;
2049821f1d3SAlan Somers 
205cad67791SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 1, 1, 0, 0);
2069821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
2079821f1d3SAlan Somers 		ResultOf([](auto in) {
20829edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
209bf507497SAlan Somers 				in.body.getattr.getattr_flags == 0 &&
21029edc611SAlan Somers 				in.header.nodeid == ino);
2119821f1d3SAlan Somers 		}, Eq(true)),
2129821f1d3SAlan Somers 		_)
21329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
2149821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
21529edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
21629edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
21729edc611SAlan Somers 		out.body.attr.attr.size = 1;
21829edc611SAlan Somers 		out.body.attr.attr.blocks = 2;
21929edc611SAlan Somers 		out.body.attr.attr.atime = 3;
22029edc611SAlan Somers 		out.body.attr.attr.mtime = 4;
22129edc611SAlan Somers 		out.body.attr.attr.ctime = 5;
22229edc611SAlan Somers 		out.body.attr.attr.atimensec = 6;
22329edc611SAlan Somers 		out.body.attr.attr.mtimensec = 7;
22429edc611SAlan Somers 		out.body.attr.attr.ctimensec = 8;
22529edc611SAlan Somers 		out.body.attr.attr.nlink = 9;
22629edc611SAlan Somers 		out.body.attr.attr.uid = 10;
22729edc611SAlan Somers 		out.body.attr.attr.gid = 11;
22829edc611SAlan Somers 		out.body.attr.attr.rdev = 12;
22929edc611SAlan Somers 		out.body.attr.attr.blksize = 12345;
2309821f1d3SAlan Somers 	})));
2319821f1d3SAlan Somers 
2329821f1d3SAlan Somers 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
2339821f1d3SAlan Somers 	EXPECT_EQ(1, sb.st_size);
2349821f1d3SAlan Somers 	EXPECT_EQ(2, sb.st_blocks);
2359821f1d3SAlan Somers 	EXPECT_EQ(3, sb.st_atim.tv_sec);
2369821f1d3SAlan Somers 	EXPECT_EQ(6, sb.st_atim.tv_nsec);
2379821f1d3SAlan Somers 	EXPECT_EQ(4, sb.st_mtim.tv_sec);
2389821f1d3SAlan Somers 	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
2399821f1d3SAlan Somers 	EXPECT_EQ(5, sb.st_ctim.tv_sec);
2409821f1d3SAlan Somers 	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
2419821f1d3SAlan Somers 	EXPECT_EQ(9ull, sb.st_nlink);
2429821f1d3SAlan Somers 	EXPECT_EQ(10ul, sb.st_uid);
2439821f1d3SAlan Somers 	EXPECT_EQ(11ul, sb.st_gid);
2449821f1d3SAlan Somers 	EXPECT_EQ(12ul, sb.st_rdev);
245e7f73af1SAlan Somers 	EXPECT_EQ((blksize_t)12345, sb.st_blksize);
2469821f1d3SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
2479821f1d3SAlan Somers 	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
2489821f1d3SAlan Somers 
249*55b80e2cSAlan Somers 	/*
250*55b80e2cSAlan Somers 	 * st_birthtim and st_flags are not supported by the fuse protocol.
251*55b80e2cSAlan Somers 	 * They're only supported as OS-specific extensions to OSX.  For
252*55b80e2cSAlan Somers 	 * birthtime, the convention for "not supported" is "negative one
253*55b80e2cSAlan Somers 	 * second".
254*55b80e2cSAlan Somers 	 */
255*55b80e2cSAlan Somers 	EXPECT_EQ(-1, sb.st_birthtim.tv_sec);
256*55b80e2cSAlan Somers 	EXPECT_EQ(0, sb.st_birthtim.tv_nsec);
257*55b80e2cSAlan Somers 	EXPECT_EQ(0u, sb.st_flags);
2589821f1d3SAlan Somers }
25916bd2d47SAlan Somers 
26025927e06SAlan Somers /*
26125927e06SAlan Somers  * FUSE_GETATTR returns a different file type, even though the entry cache
26225927e06SAlan Somers  * hasn't expired.  This is a server bug!  It probably means that the server
26325927e06SAlan Somers  * removed the file and recreated it with the same inode but a different vtyp.
26425927e06SAlan Somers  * The best thing fusefs can do is return ENOENT to the caller.  After all, the
26525927e06SAlan Somers  * entry must not have existed recently.
26625927e06SAlan Somers  */
TEST_F(Getattr,vtyp_conflict)26725927e06SAlan Somers TEST_F(Getattr, vtyp_conflict)
26825927e06SAlan Somers {
26925927e06SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
27025927e06SAlan Somers 	const char RELPATH[] = "some_file.txt";
27125927e06SAlan Somers 	const uint64_t ino = 42;
27225927e06SAlan Somers 	struct stat sb;
27325927e06SAlan Somers 	sem_t sem;
27425927e06SAlan Somers 
27525927e06SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
27625927e06SAlan Somers 
27725927e06SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
27825927e06SAlan Somers 	.WillOnce(Invoke(
27925927e06SAlan Somers 		ReturnImmediate([=](auto in __unused, auto& out) {
28025927e06SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
28125927e06SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
28225927e06SAlan Somers 		out.body.entry.nodeid = ino;
28325927e06SAlan Somers 		out.body.entry.attr.nlink = 1;
28425927e06SAlan Somers 		out.body.entry.attr_valid = 0;
28525927e06SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
28625927e06SAlan Somers 	})));
28725927e06SAlan Somers 	EXPECT_CALL(*m_mock, process(
28825927e06SAlan Somers 		ResultOf([](auto in) {
28925927e06SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
29025927e06SAlan Somers 				in.body.getattr.getattr_flags == 0 &&
29125927e06SAlan Somers 				in.header.nodeid == ino);
29225927e06SAlan Somers 		}, Eq(true)),
29325927e06SAlan Somers 		_)
29425927e06SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
29525927e06SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
29625927e06SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
29725927e06SAlan Somers 		out.body.attr.attr.mode = S_IFDIR | 0755;	// Changed!
29825927e06SAlan Somers 		out.body.attr.attr.nlink = 2;
29925927e06SAlan Somers 	})));
30025927e06SAlan Somers 	// We should reclaim stale vnodes
30125927e06SAlan Somers 	expect_forget(ino, 1, &sem);
30225927e06SAlan Somers 
30325927e06SAlan Somers 	ASSERT_NE(0, stat(FULLPATH, &sb));
30425927e06SAlan Somers 	EXPECT_EQ(errno, ENOENT);
30525927e06SAlan Somers 
30625927e06SAlan Somers 	sem_wait(&sem);
30725927e06SAlan Somers 	sem_destroy(&sem);
30825927e06SAlan Somers }
30925927e06SAlan Somers 
TEST_F(Getattr_7_8,ok)31016bd2d47SAlan Somers TEST_F(Getattr_7_8, ok)
31116bd2d47SAlan Somers {
31216bd2d47SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
31316bd2d47SAlan Somers 	const char RELPATH[] = "some_file.txt";
31416bd2d47SAlan Somers 	const uint64_t ino = 42;
31516bd2d47SAlan Somers 	struct stat sb;
31616bd2d47SAlan Somers 
317a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
31829edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
31916bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, entry_7_8);
32029edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
32129edc611SAlan Somers 		out.body.entry.nodeid = ino;
32229edc611SAlan Somers 		out.body.entry.attr.nlink = 1;
32329edc611SAlan Somers 		out.body.entry.attr.size = 1;
32416bd2d47SAlan Somers 	})));
32516bd2d47SAlan Somers 	EXPECT_CALL(*m_mock, process(
32616bd2d47SAlan Somers 		ResultOf([](auto in) {
32729edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
32829edc611SAlan Somers 				in.header.nodeid == ino);
32916bd2d47SAlan Somers 		}, Eq(true)),
33016bd2d47SAlan Somers 		_)
33129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
33216bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, attr_7_8);
33329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
33429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
33529edc611SAlan Somers 		out.body.attr.attr.size = 1;
33629edc611SAlan Somers 		out.body.attr.attr.blocks = 2;
33729edc611SAlan Somers 		out.body.attr.attr.atime = 3;
33829edc611SAlan Somers 		out.body.attr.attr.mtime = 4;
33929edc611SAlan Somers 		out.body.attr.attr.ctime = 5;
34029edc611SAlan Somers 		out.body.attr.attr.atimensec = 6;
34129edc611SAlan Somers 		out.body.attr.attr.mtimensec = 7;
34229edc611SAlan Somers 		out.body.attr.attr.ctimensec = 8;
34329edc611SAlan Somers 		out.body.attr.attr.nlink = 9;
34429edc611SAlan Somers 		out.body.attr.attr.uid = 10;
34529edc611SAlan Somers 		out.body.attr.attr.gid = 11;
34629edc611SAlan Somers 		out.body.attr.attr.rdev = 12;
34716bd2d47SAlan Somers 	})));
34816bd2d47SAlan Somers 
34916bd2d47SAlan Somers 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
35016bd2d47SAlan Somers 	EXPECT_EQ(1, sb.st_size);
35116bd2d47SAlan Somers 	EXPECT_EQ(2, sb.st_blocks);
35216bd2d47SAlan Somers 	EXPECT_EQ(3, sb.st_atim.tv_sec);
35316bd2d47SAlan Somers 	EXPECT_EQ(6, sb.st_atim.tv_nsec);
35416bd2d47SAlan Somers 	EXPECT_EQ(4, sb.st_mtim.tv_sec);
35516bd2d47SAlan Somers 	EXPECT_EQ(7, sb.st_mtim.tv_nsec);
35616bd2d47SAlan Somers 	EXPECT_EQ(5, sb.st_ctim.tv_sec);
35716bd2d47SAlan Somers 	EXPECT_EQ(8, sb.st_ctim.tv_nsec);
35816bd2d47SAlan Somers 	EXPECT_EQ(9ull, sb.st_nlink);
35916bd2d47SAlan Somers 	EXPECT_EQ(10ul, sb.st_uid);
36016bd2d47SAlan Somers 	EXPECT_EQ(11ul, sb.st_gid);
36116bd2d47SAlan Somers 	EXPECT_EQ(12ul, sb.st_rdev);
36216bd2d47SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
36316bd2d47SAlan Somers 	EXPECT_EQ(S_IFREG | 0644, sb.st_mode);
36416bd2d47SAlan Somers 
36516bd2d47SAlan Somers 	//st_birthtim and st_flags are not supported by protocol 7.8.  They're
36616bd2d47SAlan Somers 	//only supported as OS-specific extensions to OSX.
36716bd2d47SAlan Somers }
368