1e5b50fe7SAlan Somers /*- 2e5b50fe7SAlan Somers * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 81*a34cdd26SAlan 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 119*a34cdd26SAlan 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 147e5b50fe7SAlan Somers /* Use a file handle whose entry is still cached */ 148e5b50fe7SAlan Somers /* 149e5b50fe7SAlan Somers * Disabled because fuse_vfsop_vget doesn't yet check the entry cache. No PR 150e5b50fe7SAlan Somers * because that's a feature request, not a bug 151e5b50fe7SAlan Somers */ 152e5b50fe7SAlan Somers TEST_F(Fhstat, DISABLED_cached) 153e5b50fe7SAlan Somers { 154e5b50fe7SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/."; 155e5b50fe7SAlan Somers const char RELDIRPATH[] = "some_dir"; 156e5b50fe7SAlan Somers fhandle_t fhp; 157e5b50fe7SAlan Somers struct stat sb; 158e5b50fe7SAlan Somers const uint64_t ino = 42; 159e5b50fe7SAlan Somers const mode_t mode = S_IFDIR | 0755; 160e5b50fe7SAlan Somers const uid_t uid = 12345; 161e5b50fe7SAlan Somers 162*a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 16329edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 164e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 16529edc611SAlan Somers out.body.entry.attr.mode = mode; 16629edc611SAlan Somers out.body.entry.nodeid = ino; 16729edc611SAlan Somers out.body.entry.generation = 1; 16829edc611SAlan Somers out.body.entry.attr.uid = uid; 16929edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 17029edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 171e5b50fe7SAlan Somers }))); 172e5b50fe7SAlan Somers 173e5b50fe7SAlan Somers ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); 174e5b50fe7SAlan Somers ASSERT_EQ(0, fhstat(&fhp, &sb)) << strerror(errno); 175e5b50fe7SAlan Somers EXPECT_EQ(uid, sb.st_uid); 176e5b50fe7SAlan Somers EXPECT_EQ(mode, sb.st_mode); 177e5b50fe7SAlan Somers } 178e5b50fe7SAlan Somers 179e5b50fe7SAlan Somers /* 180e5b50fe7SAlan Somers * If the server doesn't set FUSE_EXPORT_SUPPORT, then we can't do NFS-style 181e5b50fe7SAlan Somers * lookups 182e5b50fe7SAlan Somers */ 183e5b50fe7SAlan Somers TEST_F(FhstatNotExportable, lookup_dot) 184e5b50fe7SAlan Somers { 185e5b50fe7SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/."; 186e5b50fe7SAlan Somers const char RELDIRPATH[] = "some_dir"; 187e5b50fe7SAlan Somers fhandle_t fhp; 188e5b50fe7SAlan Somers const uint64_t ino = 42; 189e5b50fe7SAlan Somers const mode_t mode = S_IFDIR | 0755; 190e5b50fe7SAlan Somers 191*a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 19229edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 193e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 19429edc611SAlan Somers out.body.entry.attr.mode = mode; 19529edc611SAlan Somers out.body.entry.nodeid = ino; 19629edc611SAlan Somers out.body.entry.generation = 1; 19729edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 19829edc611SAlan Somers out.body.entry.entry_valid = 0; 199e5b50fe7SAlan Somers }))); 200e5b50fe7SAlan Somers 201e5b50fe7SAlan Somers ASSERT_EQ(-1, getfh(FULLPATH, &fhp)); 202e5b50fe7SAlan Somers ASSERT_EQ(EOPNOTSUPP, errno); 203e5b50fe7SAlan Somers } 204e5b50fe7SAlan Somers 205e5b50fe7SAlan Somers /* FreeBSD's fid struct doesn't have enough space for 64-bit generations */ 206e5b50fe7SAlan Somers TEST_F(Getfh, eoverflow) 207e5b50fe7SAlan Somers { 208e5b50fe7SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/."; 209e5b50fe7SAlan Somers const char RELDIRPATH[] = "some_dir"; 210e5b50fe7SAlan Somers fhandle_t fhp; 211e5b50fe7SAlan Somers uint64_t ino = 42; 212e5b50fe7SAlan Somers 213*a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 21429edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 215e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 21629edc611SAlan Somers out.body.entry.attr.mode = S_IFDIR | 0755; 21729edc611SAlan Somers out.body.entry.nodeid = ino; 21829edc611SAlan Somers out.body.entry.generation = (uint64_t)UINT32_MAX + 1; 21929edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 22029edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 221e5b50fe7SAlan Somers }))); 222e5b50fe7SAlan Somers 223e5b50fe7SAlan Somers ASSERT_NE(0, getfh(FULLPATH, &fhp)); 224e5b50fe7SAlan Somers EXPECT_EQ(EOVERFLOW, errno); 225e5b50fe7SAlan Somers } 226e5b50fe7SAlan Somers 227e5b50fe7SAlan Somers /* Get an NFS file handle */ 228e5b50fe7SAlan Somers TEST_F(Getfh, ok) 229e5b50fe7SAlan Somers { 230e5b50fe7SAlan Somers const char FULLPATH[] = "mountpoint/some_dir/."; 231e5b50fe7SAlan Somers const char RELDIRPATH[] = "some_dir"; 232e5b50fe7SAlan Somers fhandle_t fhp; 233e5b50fe7SAlan Somers uint64_t ino = 42; 234e5b50fe7SAlan Somers 235*a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 23629edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 237e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 23829edc611SAlan Somers out.body.entry.attr.mode = S_IFDIR | 0755; 23929edc611SAlan Somers out.body.entry.nodeid = ino; 24029edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 24129edc611SAlan Somers out.body.entry.entry_valid = UINT64_MAX; 242e5b50fe7SAlan Somers }))); 243e5b50fe7SAlan Somers 244e5b50fe7SAlan Somers ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); 245e5b50fe7SAlan Somers } 246e5b50fe7SAlan Somers 247e5b50fe7SAlan Somers /* 248e5b50fe7SAlan Somers * Call readdir via a file handle. 249e5b50fe7SAlan Somers * 250e5b50fe7SAlan Somers * This is how a userspace nfs server like nfs-ganesha or unfs3 would call 251e5b50fe7SAlan Somers * readdir. The in-kernel NFS server never does any equivalent of open. I 252e5b50fe7SAlan Somers * haven't discovered a way to mimic nfsd's behavior short of actually running 253e5b50fe7SAlan Somers * nfsd. 254e5b50fe7SAlan Somers */ 255e5b50fe7SAlan Somers TEST_F(Readdir, getdirentries) 256e5b50fe7SAlan Somers { 257e5b50fe7SAlan Somers const char FULLPATH[] = "mountpoint/some_dir"; 258e5b50fe7SAlan Somers const char RELPATH[] = "some_dir"; 259e5b50fe7SAlan Somers uint64_t ino = 42; 260e5b50fe7SAlan Somers mode_t mode = S_IFDIR | 0755; 261e5b50fe7SAlan Somers fhandle_t fhp; 262e5b50fe7SAlan Somers int fd; 263e5b50fe7SAlan Somers char buf[8192]; 264e5b50fe7SAlan Somers ssize_t r; 265e5b50fe7SAlan Somers 266*a34cdd26SAlan Somers EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 26729edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 268e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 26929edc611SAlan Somers out.body.entry.attr.mode = mode; 27029edc611SAlan Somers out.body.entry.nodeid = ino; 27129edc611SAlan Somers out.body.entry.generation = 1; 27229edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 27329edc611SAlan Somers out.body.entry.entry_valid = 0; 274e5b50fe7SAlan Somers }))); 275e5b50fe7SAlan Somers 276e5b50fe7SAlan Somers EXPECT_LOOKUP(ino, ".") 27729edc611SAlan Somers .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 278e5b50fe7SAlan Somers SET_OUT_HEADER_LEN(out, entry); 27929edc611SAlan Somers out.body.entry.attr.mode = mode; 28029edc611SAlan Somers out.body.entry.nodeid = ino; 28129edc611SAlan Somers out.body.entry.generation = 1; 28229edc611SAlan Somers out.body.entry.attr_valid = UINT64_MAX; 28329edc611SAlan Somers out.body.entry.entry_valid = 0; 284e5b50fe7SAlan Somers }))); 285e5b50fe7SAlan Somers 286e5b50fe7SAlan Somers expect_opendir(ino); 287e5b50fe7SAlan Somers 288e5b50fe7SAlan Somers EXPECT_CALL(*m_mock, process( 289e5b50fe7SAlan Somers ResultOf([=](auto in) { 29029edc611SAlan Somers return (in.header.opcode == FUSE_READDIR && 29129edc611SAlan Somers in.header.nodeid == ino && 29229edc611SAlan Somers in.body.readdir.size == sizeof(buf)); 293e5b50fe7SAlan Somers }, Eq(true)), 294e5b50fe7SAlan Somers _) 29529edc611SAlan Somers ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 29629edc611SAlan Somers out.header.error = 0; 29729edc611SAlan Somers out.header.len = sizeof(out.header); 298e5b50fe7SAlan Somers }))); 299e5b50fe7SAlan Somers 300e5b50fe7SAlan Somers ASSERT_EQ(0, getfh(FULLPATH, &fhp)) << strerror(errno); 301e5b50fe7SAlan Somers fd = fhopen(&fhp, O_DIRECTORY); 302e5b50fe7SAlan Somers ASSERT_LE(0, fd) << strerror(errno); 303e5b50fe7SAlan Somers r = getdirentries(fd, buf, sizeof(buf), 0); 304e5b50fe7SAlan Somers ASSERT_EQ(0, r) << strerror(errno); 305e5b50fe7SAlan Somers 306e5b50fe7SAlan Somers /* Deliberately leak fd. RELEASEDIR will be tested separately */ 307e5b50fe7SAlan Somers } 308