xref: /freebsd/tests/sys/fs/fusefs/nfs.cc (revision 4d846d260e2b9a3d4d0a701462568268cbfe7a5b)
1e5b50fe7SAlan Somers /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3e5b50fe7SAlan Somers  *
4e5b50fe7SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
5e5b50fe7SAlan Somers  *
6e5b50fe7SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
7e5b50fe7SAlan Somers  * from the FreeBSD Foundation.
8e5b50fe7SAlan Somers  *
9e5b50fe7SAlan Somers  * Redistribution and use in source and binary forms, with or without
10e5b50fe7SAlan Somers  * modification, are permitted provided that the following conditions
11e5b50fe7SAlan Somers  * are met:
12e5b50fe7SAlan Somers  * 1. Redistributions of source code must retain the above copyright
13e5b50fe7SAlan Somers  *    notice, this list of conditions and the following disclaimer.
14e5b50fe7SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
15e5b50fe7SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
16e5b50fe7SAlan Somers  *    documentation and/or other materials provided with the distribution.
17e5b50fe7SAlan Somers  *
18e5b50fe7SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19e5b50fe7SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20e5b50fe7SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21e5b50fe7SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22e5b50fe7SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23e5b50fe7SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24e5b50fe7SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25e5b50fe7SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26e5b50fe7SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27e5b50fe7SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28e5b50fe7SAlan Somers  * SUCH DAMAGE.
291fa8ebfbSAlan Somers  *
301fa8ebfbSAlan Somers  * $FreeBSD$
31e5b50fe7SAlan Somers  */
32e5b50fe7SAlan Somers 
33e5b50fe7SAlan Somers /* This file tests functionality needed by NFS servers */
34e5b50fe7SAlan Somers extern "C" {
35e5b50fe7SAlan Somers #include <sys/param.h>
36e5b50fe7SAlan Somers #include <sys/mount.h>
37e5b50fe7SAlan Somers 
38e5b50fe7SAlan Somers #include <dirent.h>
39e5b50fe7SAlan Somers #include <fcntl.h>
40e5b50fe7SAlan Somers #include <unistd.h>
41e5b50fe7SAlan Somers }
42e5b50fe7SAlan Somers 
43e5b50fe7SAlan Somers #include "mockfs.hh"
44e5b50fe7SAlan Somers #include "utils.hh"
45e5b50fe7SAlan Somers 
46e5b50fe7SAlan Somers using namespace std;
47e5b50fe7SAlan Somers using namespace testing;
48e5b50fe7SAlan Somers 
49e5b50fe7SAlan Somers 
50e5b50fe7SAlan Somers class Nfs: public FuseTest {
51e5b50fe7SAlan Somers public:
52e5b50fe7SAlan Somers virtual void SetUp() {
53e5b50fe7SAlan Somers 	if (geteuid() != 0)
54e5b50fe7SAlan Somers                 GTEST_SKIP() << "This test requires a privileged user";
55e5b50fe7SAlan Somers 	FuseTest::SetUp();
56e5b50fe7SAlan Somers }
57e5b50fe7SAlan Somers };
58e5b50fe7SAlan Somers 
59e5b50fe7SAlan Somers class Exportable: public Nfs {
60e5b50fe7SAlan Somers public:
61e5b50fe7SAlan Somers virtual void SetUp() {
62e5b50fe7SAlan Somers 	m_init_flags = FUSE_EXPORT_SUPPORT;
63e5b50fe7SAlan Somers 	Nfs::SetUp();
64e5b50fe7SAlan Somers }
65e5b50fe7SAlan Somers };
66e5b50fe7SAlan Somers 
67e5b50fe7SAlan Somers class Fhstat: public Exportable {};
68e5b50fe7SAlan Somers class FhstatNotExportable: public Nfs {};
69e5b50fe7SAlan Somers class Getfh: public Exportable {};
70e5b50fe7SAlan Somers class Readdir: public Exportable {};
71e5b50fe7SAlan Somers 
72e5b50fe7SAlan Somers /* If the server returns a different generation number, then file is stale */
73e5b50fe7SAlan Somers TEST_F(Fhstat, estale)
74e5b50fe7SAlan Somers {
75e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
76e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
77e5b50fe7SAlan Somers 	fhandle_t fhp;
78e5b50fe7SAlan Somers 	struct stat sb;
79e5b50fe7SAlan Somers 	const uint64_t ino = 42;
80e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
81e5b50fe7SAlan Somers 	Sequence seq;
82e5b50fe7SAlan Somers 
83a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
84e5b50fe7SAlan Somers 	.InSequence(seq)
8529edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
86e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
8729edc611SAlan Somers 		out.body.entry.attr.mode = mode;
8829edc611SAlan Somers 		out.body.entry.nodeid = ino;
8929edc611SAlan Somers 		out.body.entry.generation = 1;
9029edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
9129edc611SAlan Somers 		out.body.entry.entry_valid = 0;
92e5b50fe7SAlan Somers 	})));
93e5b50fe7SAlan Somers 
94e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
95e5b50fe7SAlan Somers 	.InSequence(seq)
9629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
97e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
9829edc611SAlan Somers 		out.body.entry.attr.mode = mode;
9929edc611SAlan Somers 		out.body.entry.nodeid = ino;
10029edc611SAlan Somers 		out.body.entry.generation = 2;
10129edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
10229edc611SAlan Somers 		out.body.entry.entry_valid = 0;
103e5b50fe7SAlan Somers 	})));
104e5b50fe7SAlan Somers 
105e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
106e5b50fe7SAlan Somers 	ASSERT_EQ(-1, fhstat(&fhp, &sb));
107e5b50fe7SAlan Somers 	EXPECT_EQ(ESTALE, errno);
108e5b50fe7SAlan Somers }
109e5b50fe7SAlan Somers 
110e5b50fe7SAlan Somers /* If we must lookup an entry from the server, send a LOOKUP request for "." */
111e5b50fe7SAlan Somers TEST_F(Fhstat, lookup_dot)
112e5b50fe7SAlan Somers {
113e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
114e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
115e5b50fe7SAlan Somers 	fhandle_t fhp;
116e5b50fe7SAlan Somers 	struct stat sb;
117e5b50fe7SAlan Somers 	const uint64_t ino = 42;
118e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
119e5b50fe7SAlan Somers 	const uid_t uid = 12345;
120e5b50fe7SAlan Somers 
121a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
12229edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
123e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
12429edc611SAlan Somers 		out.body.entry.attr.mode = mode;
12529edc611SAlan Somers 		out.body.entry.nodeid = ino;
12629edc611SAlan Somers 		out.body.entry.generation = 1;
12729edc611SAlan Somers 		out.body.entry.attr.uid = uid;
12829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
12929edc611SAlan Somers 		out.body.entry.entry_valid = 0;
130e5b50fe7SAlan Somers 	})));
131e5b50fe7SAlan Somers 
132e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
13329edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
134e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
13529edc611SAlan Somers 		out.body.entry.attr.mode = mode;
13629edc611SAlan Somers 		out.body.entry.nodeid = ino;
13729edc611SAlan Somers 		out.body.entry.generation = 1;
13829edc611SAlan Somers 		out.body.entry.attr.uid = uid;
13929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
14029edc611SAlan Somers 		out.body.entry.entry_valid = 0;
141e5b50fe7SAlan Somers 	})));
142e5b50fe7SAlan Somers 
143e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
144e5b50fe7SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
145e5b50fe7SAlan Somers 	EXPECT_EQ(uid, sb.st_uid);
146e5b50fe7SAlan Somers 	EXPECT_EQ(mode, sb.st_mode);
147e5b50fe7SAlan Somers }
148e5b50fe7SAlan Somers 
149e5b50fe7SAlan Somers /* Use a file handle whose entry is still cached */
1500d2bf489SAlan Somers TEST_F(Fhstat, cached)
151e5b50fe7SAlan Somers {
152e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
153e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
154e5b50fe7SAlan Somers 	fhandle_t fhp;
155e5b50fe7SAlan Somers 	struct stat sb;
156e5b50fe7SAlan Somers 	const uint64_t ino = 42;
157e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
158e5b50fe7SAlan Somers 
159a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
16029edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
161e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
16229edc611SAlan Somers 		out.body.entry.attr.mode = mode;
16329edc611SAlan Somers 		out.body.entry.nodeid = ino;
16429edc611SAlan Somers 		out.body.entry.generation = 1;
1650d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
16629edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
16729edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
168e5b50fe7SAlan Somers 	})));
169e5b50fe7SAlan Somers 
170e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
171e5b50fe7SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
1720d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
1730d2bf489SAlan Somers }
1740d2bf489SAlan Somers 
1750d2bf489SAlan Somers /* File handle entries should expire from the cache, too */
1760d2bf489SAlan Somers TEST_F(Fhstat, cache_expired)
1770d2bf489SAlan Somers {
1780d2bf489SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
1790d2bf489SAlan Somers 	const char RELDIRPATH[] = "some_dir";
1800d2bf489SAlan Somers 	fhandle_t fhp;
1810d2bf489SAlan Somers 	struct stat sb;
1820d2bf489SAlan Somers 	const uint64_t ino = 42;
1830d2bf489SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
1840d2bf489SAlan Somers 
1850d2bf489SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
1860d2bf489SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1870d2bf489SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
1880d2bf489SAlan Somers 		out.body.entry.attr.mode = mode;
1890d2bf489SAlan Somers 		out.body.entry.nodeid = ino;
1900d2bf489SAlan Somers 		out.body.entry.generation = 1;
1910d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
1920d2bf489SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
1930d2bf489SAlan Somers 		out.body.entry.entry_valid_nsec = NAP_NS / 2;
1940d2bf489SAlan Somers 	})));
1950d2bf489SAlan Somers 
1960d2bf489SAlan Somers 	EXPECT_LOOKUP(ino, ".")
1970d2bf489SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1980d2bf489SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
1990d2bf489SAlan Somers 		out.body.entry.attr.mode = mode;
2000d2bf489SAlan Somers 		out.body.entry.nodeid = ino;
2010d2bf489SAlan Somers 		out.body.entry.generation = 1;
2020d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
2030d2bf489SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
2040d2bf489SAlan Somers 		out.body.entry.entry_valid = 0;
2050d2bf489SAlan Somers 	})));
2060d2bf489SAlan Somers 
2070d2bf489SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
2080d2bf489SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
2090d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
2100d2bf489SAlan Somers 
2110d2bf489SAlan Somers 	nap();
2120d2bf489SAlan Somers 
2130d2bf489SAlan Somers 	/* Cache should be expired; fuse should issue a FUSE_LOOKUP */
2140d2bf489SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
2150d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
216e5b50fe7SAlan Somers }
217e5b50fe7SAlan Somers 
218e5b50fe7SAlan Somers /*
219e5b50fe7SAlan Somers  * If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
220e5b50fe7SAlan Somers  * lookups
221e5b50fe7SAlan Somers  */
222e5b50fe7SAlan Somers TEST_F(FhstatNotExportable, lookup_dot)
223e5b50fe7SAlan Somers {
224e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
225e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
226e5b50fe7SAlan Somers 	fhandle_t fhp;
227e5b50fe7SAlan Somers 	const uint64_t ino = 42;
228e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
229e5b50fe7SAlan Somers 
230a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
23129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
232e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
23329edc611SAlan Somers 		out.body.entry.attr.mode = mode;
23429edc611SAlan Somers 		out.body.entry.nodeid = ino;
23529edc611SAlan Somers 		out.body.entry.generation = 1;
23629edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
23729edc611SAlan Somers 		out.body.entry.entry_valid = 0;
238e5b50fe7SAlan Somers 	})));
239e5b50fe7SAlan Somers 
240e5b50fe7SAlan Somers 	ASSERT_EQ(-1, getfh(FULLPATH, &fhp));
241e5b50fe7SAlan Somers 	ASSERT_EQ(EOPNOTSUPP, errno);
242e5b50fe7SAlan Somers }
243e5b50fe7SAlan Somers 
244e5b50fe7SAlan Somers /* FreeBSD's fid struct doesn't have enough space for 64-bit generations */
245e5b50fe7SAlan Somers TEST_F(Getfh, eoverflow)
246e5b50fe7SAlan Somers {
247e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
248e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
249e5b50fe7SAlan Somers 	fhandle_t fhp;
250e5b50fe7SAlan Somers 	uint64_t ino = 42;
251e5b50fe7SAlan Somers 
252a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
25329edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
254e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
25529edc611SAlan Somers 		out.body.entry.attr.mode = S_IFDIR | 0755;
25629edc611SAlan Somers 		out.body.entry.nodeid = ino;
25729edc611SAlan Somers 		out.body.entry.generation = (uint64_t)UINT32_MAX + 1;
25829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
25929edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
260e5b50fe7SAlan Somers 	})));
261e5b50fe7SAlan Somers 
262e5b50fe7SAlan Somers 	ASSERT_NE(0, getfh(FULLPATH, &fhp));
263e5b50fe7SAlan Somers 	EXPECT_EQ(EOVERFLOW, errno);
264e5b50fe7SAlan Somers }
265e5b50fe7SAlan Somers 
266e5b50fe7SAlan Somers /* Get an NFS file handle */
267e5b50fe7SAlan Somers TEST_F(Getfh, ok)
268e5b50fe7SAlan Somers {
269e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
270e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
271e5b50fe7SAlan Somers 	fhandle_t fhp;
272e5b50fe7SAlan Somers 	uint64_t ino = 42;
273e5b50fe7SAlan Somers 
274a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
27529edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
276e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
27729edc611SAlan Somers 		out.body.entry.attr.mode = S_IFDIR | 0755;
27829edc611SAlan Somers 		out.body.entry.nodeid = ino;
27929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
28029edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
281e5b50fe7SAlan Somers 	})));
282e5b50fe7SAlan Somers 
283e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
284e5b50fe7SAlan Somers }
285e5b50fe7SAlan Somers 
286e5b50fe7SAlan Somers /*
287e5b50fe7SAlan Somers  * Call readdir via a file handle.
288e5b50fe7SAlan Somers  *
289e5b50fe7SAlan Somers  * This is how a userspace nfs server like nfs-ganesha or unfs3 would call
290e5b50fe7SAlan Somers  * readdir.  The in-kernel NFS server never does any equivalent of open.  I
291e5b50fe7SAlan Somers  * haven't discovered a way to mimic nfsd's behavior short of actually running
292e5b50fe7SAlan Somers  * nfsd.
293e5b50fe7SAlan Somers  */
294e5b50fe7SAlan Somers TEST_F(Readdir, getdirentries)
295e5b50fe7SAlan Somers {
296e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir";
297e5b50fe7SAlan Somers 	const char RELPATH[] = "some_dir";
298e5b50fe7SAlan Somers 	uint64_t ino = 42;
299e5b50fe7SAlan Somers 	mode_t mode = S_IFDIR | 0755;
300e5b50fe7SAlan Somers 	fhandle_t fhp;
301e5b50fe7SAlan Somers 	int fd;
302e5b50fe7SAlan Somers 	char buf[8192];
303e5b50fe7SAlan Somers 	ssize_t r;
304e5b50fe7SAlan Somers 
305a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
30629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
307e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
30829edc611SAlan Somers 		out.body.entry.attr.mode = mode;
30929edc611SAlan Somers 		out.body.entry.nodeid = ino;
31029edc611SAlan Somers 		out.body.entry.generation = 1;
31129edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
31229edc611SAlan Somers 		out.body.entry.entry_valid = 0;
313e5b50fe7SAlan Somers 	})));
314e5b50fe7SAlan Somers 
315e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
31629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
317e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
31829edc611SAlan Somers 		out.body.entry.attr.mode = mode;
31929edc611SAlan Somers 		out.body.entry.nodeid = ino;
32029edc611SAlan Somers 		out.body.entry.generation = 1;
32129edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
32229edc611SAlan Somers 		out.body.entry.entry_valid = 0;
323e5b50fe7SAlan Somers 	})));
324e5b50fe7SAlan Somers 
325e5b50fe7SAlan Somers 	expect_opendir(ino);
326e5b50fe7SAlan Somers 
327e5b50fe7SAlan Somers 	EXPECT_CALL(*m_mock, process(
328e5b50fe7SAlan Somers 		ResultOf([=](auto in) {
32929edc611SAlan Somers 			return (in.header.opcode == FUSE_READDIR &&
33029edc611SAlan Somers 				in.header.nodeid == ino &&
33129edc611SAlan Somers 				in.body.readdir.size == sizeof(buf));
332e5b50fe7SAlan Somers 		}, Eq(true)),
333e5b50fe7SAlan Somers 		_)
33429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
33529edc611SAlan Somers 		out.header.error = 0;
33629edc611SAlan Somers 		out.header.len = sizeof(out.header);
337e5b50fe7SAlan Somers 	})));
338e5b50fe7SAlan Somers 
339e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
340e5b50fe7SAlan Somers 	fd = fhopen(&fhp, O_DIRECTORY);
341e5b50fe7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
342e5b50fe7SAlan Somers 	r = getdirentries(fd, buf, sizeof(buf), 0);
343e5b50fe7SAlan Somers 	ASSERT_EQ(0, r) << strerror(errno);
344e5b50fe7SAlan Somers 
3457fc0921dSAlan Somers 	leak(fd);
346e5b50fe7SAlan Somers }
347