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