xref: /freebsd/tests/sys/fs/fusefs/nfs.cc (revision 969d1aa4dbfcbccd8de965f7761203208bf04e46)
1e5b50fe7SAlan Somers /*-
24d846d26SWarner 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.
29e5b50fe7SAlan Somers  */
30e5b50fe7SAlan Somers 
31e5b50fe7SAlan Somers /* This file tests functionality needed by NFS servers */
32e5b50fe7SAlan Somers extern "C" {
33e5b50fe7SAlan Somers #include <sys/param.h>
34e5b50fe7SAlan Somers #include <sys/mount.h>
35e5b50fe7SAlan Somers 
36e5b50fe7SAlan Somers #include <dirent.h>
37e5b50fe7SAlan Somers #include <fcntl.h>
38e5b50fe7SAlan Somers #include <unistd.h>
39e5b50fe7SAlan Somers }
40e5b50fe7SAlan Somers 
41e5b50fe7SAlan Somers #include "mockfs.hh"
42e5b50fe7SAlan Somers #include "utils.hh"
43e5b50fe7SAlan Somers 
44e5b50fe7SAlan Somers using namespace std;
45e5b50fe7SAlan Somers using namespace testing;
46e5b50fe7SAlan Somers 
47e5b50fe7SAlan Somers 
48e5b50fe7SAlan Somers class Nfs: public FuseTest {
49e5b50fe7SAlan Somers public:
SetUp()50e5b50fe7SAlan Somers virtual void SetUp() {
51e5b50fe7SAlan Somers 	if (geteuid() != 0)
52e5b50fe7SAlan Somers                 GTEST_SKIP() << "This test requires a privileged user";
53e5b50fe7SAlan Somers 	FuseTest::SetUp();
54e5b50fe7SAlan Somers }
55e5b50fe7SAlan Somers };
56e5b50fe7SAlan Somers 
57e5b50fe7SAlan Somers class Exportable: public Nfs {
58e5b50fe7SAlan Somers public:
SetUp()59e5b50fe7SAlan Somers virtual void SetUp() {
60e5b50fe7SAlan Somers 	m_init_flags = FUSE_EXPORT_SUPPORT;
61e5b50fe7SAlan Somers 	Nfs::SetUp();
62e5b50fe7SAlan Somers }
63e5b50fe7SAlan Somers };
64e5b50fe7SAlan Somers 
65e5b50fe7SAlan Somers class Fhstat: public Exportable {};
66e5b50fe7SAlan Somers class FhstatNotExportable: public Nfs {};
67e5b50fe7SAlan Somers class Getfh: public Exportable {};
68e5b50fe7SAlan Somers class Readdir: public Exportable {};
69e5b50fe7SAlan Somers 
70e5b50fe7SAlan Somers /* If the server returns a different generation number, then file is stale */
TEST_F(Fhstat,estale)71e5b50fe7SAlan Somers TEST_F(Fhstat, estale)
72e5b50fe7SAlan Somers {
73e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
74e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
75e5b50fe7SAlan Somers 	fhandle_t fhp;
76e5b50fe7SAlan Somers 	struct stat sb;
77e5b50fe7SAlan Somers 	const uint64_t ino = 42;
78e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
79e5b50fe7SAlan Somers 	Sequence seq;
80e5b50fe7SAlan Somers 
81a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
82e5b50fe7SAlan Somers 	.InSequence(seq)
8329edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
84e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
8529edc611SAlan Somers 		out.body.entry.attr.mode = mode;
8629edc611SAlan Somers 		out.body.entry.nodeid = ino;
8729edc611SAlan Somers 		out.body.entry.generation = 1;
8829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
8929edc611SAlan Somers 		out.body.entry.entry_valid = 0;
90e5b50fe7SAlan Somers 	})));
91e5b50fe7SAlan Somers 
92e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
93e5b50fe7SAlan Somers 	.InSequence(seq)
9429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
95e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
9629edc611SAlan Somers 		out.body.entry.attr.mode = mode;
9729edc611SAlan Somers 		out.body.entry.nodeid = ino;
9829edc611SAlan Somers 		out.body.entry.generation = 2;
9929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
10029edc611SAlan Somers 		out.body.entry.entry_valid = 0;
101e5b50fe7SAlan Somers 	})));
102e5b50fe7SAlan Somers 
103e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
104e5b50fe7SAlan Somers 	ASSERT_EQ(-1, fhstat(&fhp, &sb));
105e5b50fe7SAlan Somers 	EXPECT_EQ(ESTALE, errno);
106e5b50fe7SAlan Somers }
107e5b50fe7SAlan Somers 
108e5b50fe7SAlan Somers /* If we must lookup an entry from the server, send a LOOKUP request for "." */
TEST_F(Fhstat,lookup_dot)109e5b50fe7SAlan Somers TEST_F(Fhstat, lookup_dot)
110e5b50fe7SAlan Somers {
111e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
112e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
113e5b50fe7SAlan Somers 	fhandle_t fhp;
114e5b50fe7SAlan Somers 	struct stat sb;
115e5b50fe7SAlan Somers 	const uint64_t ino = 42;
116e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
117e5b50fe7SAlan Somers 	const uid_t uid = 12345;
118e5b50fe7SAlan Somers 
119a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
12029edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
121e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
12229edc611SAlan Somers 		out.body.entry.attr.mode = mode;
12329edc611SAlan Somers 		out.body.entry.nodeid = ino;
12429edc611SAlan Somers 		out.body.entry.generation = 1;
12529edc611SAlan Somers 		out.body.entry.attr.uid = uid;
12629edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
12729edc611SAlan Somers 		out.body.entry.entry_valid = 0;
128e5b50fe7SAlan Somers 	})));
129e5b50fe7SAlan Somers 
130e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
13129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
132e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
13329edc611SAlan Somers 		out.body.entry.attr.mode = mode;
13429edc611SAlan Somers 		out.body.entry.nodeid = ino;
13529edc611SAlan Somers 		out.body.entry.generation = 1;
13629edc611SAlan Somers 		out.body.entry.attr.uid = uid;
13729edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
13829edc611SAlan Somers 		out.body.entry.entry_valid = 0;
139e5b50fe7SAlan Somers 	})));
140e5b50fe7SAlan Somers 
141e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
142e5b50fe7SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
143e5b50fe7SAlan Somers 	EXPECT_EQ(uid, sb.st_uid);
144e5b50fe7SAlan Somers 	EXPECT_EQ(mode, sb.st_mode);
145e5b50fe7SAlan Somers }
146e5b50fe7SAlan Somers 
147*969d1aa4SAlan Somers /* Gracefully handle failures to lookup ".". */
TEST_F(Fhstat,lookup_dot_error)148*969d1aa4SAlan Somers TEST_F(Fhstat, lookup_dot_error)
149*969d1aa4SAlan Somers {
150*969d1aa4SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
151*969d1aa4SAlan Somers 	const char RELDIRPATH[] = "some_dir";
152*969d1aa4SAlan Somers 	fhandle_t fhp;
153*969d1aa4SAlan Somers 	struct stat sb;
154*969d1aa4SAlan Somers 	const uint64_t ino = 42;
155*969d1aa4SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
156*969d1aa4SAlan Somers 	const uid_t uid = 12345;
157*969d1aa4SAlan Somers 
158*969d1aa4SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
159*969d1aa4SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
160*969d1aa4SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
161*969d1aa4SAlan Somers 		out.body.entry.attr.mode = mode;
162*969d1aa4SAlan Somers 		out.body.entry.nodeid = ino;
163*969d1aa4SAlan Somers 		out.body.entry.generation = 1;
164*969d1aa4SAlan Somers 		out.body.entry.attr.uid = uid;
165*969d1aa4SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
166*969d1aa4SAlan Somers 		out.body.entry.entry_valid = 0;
167*969d1aa4SAlan Somers 	})));
168*969d1aa4SAlan Somers 
169*969d1aa4SAlan Somers 	EXPECT_LOOKUP(ino, ".")
170*969d1aa4SAlan Somers 	.WillOnce(Invoke(ReturnErrno(EDOOFUS)));
171*969d1aa4SAlan Somers 
172*969d1aa4SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
173*969d1aa4SAlan Somers 	ASSERT_EQ(-1, fhstat(&fhp, &sb));
174*969d1aa4SAlan Somers 	EXPECT_EQ(EDOOFUS, errno);
175*969d1aa4SAlan Somers }
176*969d1aa4SAlan Somers 
177e5b50fe7SAlan Somers /* Use a file handle whose entry is still cached */
TEST_F(Fhstat,cached)1780d2bf489SAlan Somers TEST_F(Fhstat, cached)
179e5b50fe7SAlan Somers {
180e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
181e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
182e5b50fe7SAlan Somers 	fhandle_t fhp;
183e5b50fe7SAlan Somers 	struct stat sb;
184e5b50fe7SAlan Somers 	const uint64_t ino = 42;
185e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
186e5b50fe7SAlan Somers 
187a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
18829edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
189e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
19029edc611SAlan Somers 		out.body.entry.attr.mode = mode;
19129edc611SAlan Somers 		out.body.entry.nodeid = ino;
19229edc611SAlan Somers 		out.body.entry.generation = 1;
1930d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
19429edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
19529edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
196e5b50fe7SAlan Somers 	})));
197e5b50fe7SAlan Somers 
198e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
199e5b50fe7SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
2000d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
2010d2bf489SAlan Somers }
2020d2bf489SAlan Somers 
2030d2bf489SAlan Somers /* File handle entries should expire from the cache, too */
TEST_F(Fhstat,cache_expired)2040d2bf489SAlan Somers TEST_F(Fhstat, cache_expired)
2050d2bf489SAlan Somers {
2060d2bf489SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
2070d2bf489SAlan Somers 	const char RELDIRPATH[] = "some_dir";
2080d2bf489SAlan Somers 	fhandle_t fhp;
2090d2bf489SAlan Somers 	struct stat sb;
2100d2bf489SAlan Somers 	const uint64_t ino = 42;
2110d2bf489SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
2120d2bf489SAlan Somers 
2130d2bf489SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
2140d2bf489SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2150d2bf489SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
2160d2bf489SAlan Somers 		out.body.entry.attr.mode = mode;
2170d2bf489SAlan Somers 		out.body.entry.nodeid = ino;
2180d2bf489SAlan Somers 		out.body.entry.generation = 1;
2190d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
2200d2bf489SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
2210d2bf489SAlan Somers 		out.body.entry.entry_valid_nsec = NAP_NS / 2;
2220d2bf489SAlan Somers 	})));
2230d2bf489SAlan Somers 
2240d2bf489SAlan Somers 	EXPECT_LOOKUP(ino, ".")
2250d2bf489SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2260d2bf489SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
2270d2bf489SAlan Somers 		out.body.entry.attr.mode = mode;
2280d2bf489SAlan Somers 		out.body.entry.nodeid = ino;
2290d2bf489SAlan Somers 		out.body.entry.generation = 1;
2300d2bf489SAlan Somers 		out.body.entry.attr.ino = ino;
2310d2bf489SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
2320d2bf489SAlan Somers 		out.body.entry.entry_valid = 0;
2330d2bf489SAlan Somers 	})));
2340d2bf489SAlan Somers 
2350d2bf489SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
2360d2bf489SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
2370d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
2380d2bf489SAlan Somers 
2390d2bf489SAlan Somers 	nap();
2400d2bf489SAlan Somers 
2410d2bf489SAlan Somers 	/* Cache should be expired; fuse should issue a FUSE_LOOKUP */
2420d2bf489SAlan Somers 	ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno);
2430d2bf489SAlan Somers 	EXPECT_EQ(ino, sb.st_ino);
244e5b50fe7SAlan Somers }
245e5b50fe7SAlan Somers 
246e5b50fe7SAlan Somers /*
247e5b50fe7SAlan Somers  * If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style
248e5b50fe7SAlan Somers  * lookups
249e5b50fe7SAlan Somers  */
TEST_F(FhstatNotExportable,lookup_dot)250e5b50fe7SAlan Somers TEST_F(FhstatNotExportable, lookup_dot)
251e5b50fe7SAlan Somers {
252e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
253e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
254e5b50fe7SAlan Somers 	fhandle_t fhp;
255e5b50fe7SAlan Somers 	const uint64_t ino = 42;
256e5b50fe7SAlan Somers 	const mode_t mode = S_IFDIR | 0755;
257e5b50fe7SAlan Somers 
258a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
25929edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
260e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
26129edc611SAlan Somers 		out.body.entry.attr.mode = mode;
26229edc611SAlan Somers 		out.body.entry.nodeid = ino;
26329edc611SAlan Somers 		out.body.entry.generation = 1;
26429edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
26529edc611SAlan Somers 		out.body.entry.entry_valid = 0;
266e5b50fe7SAlan Somers 	})));
267e5b50fe7SAlan Somers 
268e5b50fe7SAlan Somers 	ASSERT_EQ(-1, getfh(FULLPATH, &fhp));
269e5b50fe7SAlan Somers 	ASSERT_EQ(EOPNOTSUPP, errno);
270e5b50fe7SAlan Somers }
271e5b50fe7SAlan Somers 
272e5b50fe7SAlan Somers /* FreeBSD's fid struct doesn't have enough space for 64-bit generations */
TEST_F(Getfh,eoverflow)273e5b50fe7SAlan Somers TEST_F(Getfh, eoverflow)
274e5b50fe7SAlan Somers {
275e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
276e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
277e5b50fe7SAlan Somers 	fhandle_t fhp;
278e5b50fe7SAlan Somers 	uint64_t ino = 42;
279e5b50fe7SAlan Somers 
280a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
28129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
282e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
28329edc611SAlan Somers 		out.body.entry.attr.mode = S_IFDIR | 0755;
28429edc611SAlan Somers 		out.body.entry.nodeid = ino;
28529edc611SAlan Somers 		out.body.entry.generation = (uint64_t)UINT32_MAX + 1;
28629edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
28729edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
288e5b50fe7SAlan Somers 	})));
289e5b50fe7SAlan Somers 
290e5b50fe7SAlan Somers 	ASSERT_NE(0, getfh(FULLPATH, &fhp));
291e5b50fe7SAlan Somers 	EXPECT_EQ(EOVERFLOW, errno);
292e5b50fe7SAlan Somers }
293e5b50fe7SAlan Somers 
294e5b50fe7SAlan Somers /* Get an NFS file handle */
TEST_F(Getfh,ok)295e5b50fe7SAlan Somers TEST_F(Getfh, ok)
296e5b50fe7SAlan Somers {
297e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/.";
298e5b50fe7SAlan Somers 	const char RELDIRPATH[] = "some_dir";
299e5b50fe7SAlan Somers 	fhandle_t fhp;
300e5b50fe7SAlan Somers 	uint64_t ino = 42;
301e5b50fe7SAlan Somers 
302a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH)
30329edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
304e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
30529edc611SAlan Somers 		out.body.entry.attr.mode = S_IFDIR | 0755;
30629edc611SAlan Somers 		out.body.entry.nodeid = ino;
30729edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
30829edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
309e5b50fe7SAlan Somers 	})));
310e5b50fe7SAlan Somers 
311e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
312e5b50fe7SAlan Somers }
313e5b50fe7SAlan Somers 
314e5b50fe7SAlan Somers /*
315e5b50fe7SAlan Somers  * Call readdir via a file handle.
316e5b50fe7SAlan Somers  *
317e5b50fe7SAlan Somers  * This is how a userspace nfs server like nfs-ganesha or unfs3 would call
318e5b50fe7SAlan Somers  * readdir.  The in-kernel NFS server never does any equivalent of open.  I
319e5b50fe7SAlan Somers  * haven't discovered a way to mimic nfsd's behavior short of actually running
320e5b50fe7SAlan Somers  * nfsd.
321e5b50fe7SAlan Somers  */
TEST_F(Readdir,getdirentries)322e5b50fe7SAlan Somers TEST_F(Readdir, getdirentries)
323e5b50fe7SAlan Somers {
324e5b50fe7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir";
325e5b50fe7SAlan Somers 	const char RELPATH[] = "some_dir";
326e5b50fe7SAlan Somers 	uint64_t ino = 42;
327e5b50fe7SAlan Somers 	mode_t mode = S_IFDIR | 0755;
328e5b50fe7SAlan Somers 	fhandle_t fhp;
329e5b50fe7SAlan Somers 	int fd;
330e5b50fe7SAlan Somers 	char buf[8192];
331e5b50fe7SAlan Somers 	ssize_t r;
332e5b50fe7SAlan Somers 
333a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
33429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
335e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
33629edc611SAlan Somers 		out.body.entry.attr.mode = mode;
33729edc611SAlan Somers 		out.body.entry.nodeid = ino;
33829edc611SAlan Somers 		out.body.entry.generation = 1;
33929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
34029edc611SAlan Somers 		out.body.entry.entry_valid = 0;
341e5b50fe7SAlan Somers 	})));
342e5b50fe7SAlan Somers 
343e5b50fe7SAlan Somers 	EXPECT_LOOKUP(ino, ".")
34429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
345e5b50fe7SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
34629edc611SAlan Somers 		out.body.entry.attr.mode = mode;
34729edc611SAlan Somers 		out.body.entry.nodeid = ino;
34829edc611SAlan Somers 		out.body.entry.generation = 1;
34929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
35029edc611SAlan Somers 		out.body.entry.entry_valid = 0;
351e5b50fe7SAlan Somers 	})));
352e5b50fe7SAlan Somers 
353e5b50fe7SAlan Somers 	expect_opendir(ino);
354e5b50fe7SAlan Somers 
355e5b50fe7SAlan Somers 	EXPECT_CALL(*m_mock, process(
356e5b50fe7SAlan Somers 		ResultOf([=](auto in) {
35729edc611SAlan Somers 			return (in.header.opcode == FUSE_READDIR &&
35829edc611SAlan Somers 				in.header.nodeid == ino &&
35929edc611SAlan Somers 				in.body.readdir.size == sizeof(buf));
360e5b50fe7SAlan Somers 		}, Eq(true)),
361e5b50fe7SAlan Somers 		_)
36229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
36329edc611SAlan Somers 		out.header.error = 0;
36429edc611SAlan Somers 		out.header.len = sizeof(out.header);
365e5b50fe7SAlan Somers 	})));
366e5b50fe7SAlan Somers 
367e5b50fe7SAlan Somers 	ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno);
368e5b50fe7SAlan Somers 	fd = fhopen(&fhp, O_DIRECTORY);
369e5b50fe7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
370e5b50fe7SAlan Somers 	r = getdirentries(fd, buf, sizeof(buf), 0);
371e5b50fe7SAlan Somers 	ASSERT_EQ(0, r) << strerror(errno);
372e5b50fe7SAlan Somers 
3737fc0921dSAlan Somers 	leak(fd);
374e5b50fe7SAlan Somers }
375