xref: /freebsd/tests/sys/fs/fusefs/nfs.cc (revision a34cdd26d02ffdb4134e5945b902921697b6ceff)
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