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: 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: 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 */ 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 "." */ 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 ".". */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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