1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2019 The FreeBSD Foundation
5 *
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 * $FreeBSD$
31 */
32
33/*
34 * This file tests different polling methods for the /dev/fuse device
35 */
36
37extern "C" {
38#include <fcntl.h>
39#include <semaphore.h>
40#include <unistd.h>
41}
42
43#include "mockfs.hh"
44#include "utils.hh"
45
46using namespace testing;
47
48const char FULLPATH[] = "mountpoint/some_file.txt";
49const char RELPATH[] = "some_file.txt";
50const uint64_t ino = 42;
51const mode_t access_mode = R_OK;
52
53/*
54 * Translate a poll method's string representation to the enum value.
55 * Using strings with ::testing::Values gives better output with
56 * --gtest_list_tests
57 */
58enum poll_method poll_method_from_string(const char *s)
59{
60	if (0 == strcmp("BLOCKING", s))
61		return BLOCKING;
62	else if (0 == strcmp("KQ", s))
63		return KQ;
64	else if (0 == strcmp("POLL", s))
65		return POLL;
66	else
67		return SELECT;
68}
69
70class DevFusePoll: public FuseTest, public WithParamInterface<const char *> {
71	virtual void SetUp() {
72		m_pm = poll_method_from_string(GetParam());
73		FuseTest::SetUp();
74	}
75};
76
77class Kqueue: public FuseTest {
78	virtual void SetUp() {
79		m_pm = KQ;
80		FuseTest::SetUp();
81	}
82};
83
84TEST_P(DevFusePoll, access)
85{
86	expect_access(FUSE_ROOT_ID, X_OK, 0);
87	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
88	expect_access(ino, access_mode, 0);
89
90	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
91}
92
93/* Ensure that we wake up pollers during unmount */
94TEST_P(DevFusePoll, destroy)
95{
96	expect_forget(FUSE_ROOT_ID, 1);
97	expect_destroy(0);
98
99	m_mock->unmount();
100}
101
102INSTANTIATE_TEST_CASE_P(PM, DevFusePoll,
103		::testing::Values("BLOCKING", "KQ", "POLL", "SELECT"));
104
105static void* statter(void* arg) {
106	const char *name;
107	struct stat sb;
108
109	name = (const char*)arg;
110	return ((void*)(intptr_t)stat(name, &sb));
111}
112
113/*
114 * A kevent's data field should contain the number of operations available to
115 * be immediately read.
116 */
117TEST_F(Kqueue, data)
118{
119	pthread_t th0, th1, th2;
120	sem_t sem0, sem1;
121	int nready0, nready1, nready2;
122	uint64_t foo_ino = 42;
123	uint64_t bar_ino = 43;
124	uint64_t baz_ino = 44;
125	Sequence seq;
126	void *th_ret;
127
128	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
129	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
130
131	EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
132	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
133		SET_OUT_HEADER_LEN(out, entry);
134		out.body.entry.entry_valid = UINT64_MAX;
135		out.body.entry.attr.mode = S_IFREG | 0644;
136		out.body.entry.nodeid = foo_ino;
137	})));
138	EXPECT_LOOKUP(FUSE_ROOT_ID, "bar")
139	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
140		SET_OUT_HEADER_LEN(out, entry);
141		out.body.entry.entry_valid = UINT64_MAX;
142		out.body.entry.attr.mode = S_IFREG | 0644;
143		out.body.entry.nodeid = bar_ino;
144	})));
145	EXPECT_LOOKUP(FUSE_ROOT_ID, "baz")
146	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
147		SET_OUT_HEADER_LEN(out, entry);
148		out.body.entry.entry_valid = UINT64_MAX;
149		out.body.entry.attr.mode = S_IFREG | 0644;
150		out.body.entry.nodeid = baz_ino;
151	})));
152
153	EXPECT_CALL(*m_mock, process(
154		ResultOf([=](auto in) {
155			return (in.header.opcode == FUSE_GETATTR &&
156				in.header.nodeid == foo_ino);
157		}, Eq(true)),
158		_)
159	)
160	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
161		nready0 = m_mock->m_nready;
162
163		sem_post(&sem0);
164		// Block the daemon so we can accumulate a few more ops
165		sem_wait(&sem1);
166
167		out.header.unique = in.header.unique;
168		out.header.error = -EIO;
169		out.header.len = sizeof(out.header);
170	})));
171
172	EXPECT_CALL(*m_mock, process(
173		ResultOf([=](auto in) {
174			return (in.header.opcode == FUSE_GETATTR &&
175				(in.header.nodeid == bar_ino ||
176				 in.header.nodeid == baz_ino));
177		}, Eq(true)),
178		_)
179	).InSequence(seq)
180	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
181		nready1 = m_mock->m_nready;
182		out.header.unique = in.header.unique;
183		out.header.error = -EIO;
184		out.header.len = sizeof(out.header);
185	})));
186	EXPECT_CALL(*m_mock, process(
187		ResultOf([=](auto in) {
188			return (in.header.opcode == FUSE_GETATTR &&
189				(in.header.nodeid == bar_ino ||
190				 in.header.nodeid == baz_ino));
191		}, Eq(true)),
192		_)
193	).InSequence(seq)
194	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
195		nready2 = m_mock->m_nready;
196		out.header.unique = in.header.unique;
197		out.header.error = -EIO;
198		out.header.len = sizeof(out.header);
199	})));
200
201	/*
202	 * Create cached lookup entries for these files.  It seems that only
203	 * one thread at a time can be in VOP_LOOKUP for a given directory
204	 */
205	access("mountpoint/foo", F_OK);
206	access("mountpoint/bar", F_OK);
207	access("mountpoint/baz", F_OK);
208	ASSERT_EQ(0, pthread_create(&th0, NULL, statter,
209		__DECONST(void*, "mountpoint/foo"))) << strerror(errno);
210	EXPECT_EQ(0, sem_wait(&sem0)) << strerror(errno);
211	ASSERT_EQ(0, pthread_create(&th1, NULL, statter,
212		__DECONST(void*, "mountpoint/bar"))) << strerror(errno);
213	ASSERT_EQ(0, pthread_create(&th2, NULL, statter,
214		__DECONST(void*, "mountpoint/baz"))) << strerror(errno);
215
216	nap();		// Allow th1 and th2 to send their ops to the daemon
217	EXPECT_EQ(0, sem_post(&sem1)) << strerror(errno);
218
219	pthread_join(th0, &th_ret);
220	ASSERT_EQ(-1, (intptr_t)th_ret);
221	pthread_join(th1, &th_ret);
222	ASSERT_EQ(-1, (intptr_t)th_ret);
223	pthread_join(th2, &th_ret);
224	ASSERT_EQ(-1, (intptr_t)th_ret);
225
226	EXPECT_EQ(1, nready0);
227	EXPECT_EQ(2, nready1);
228	EXPECT_EQ(1, nready2);
229
230	sem_destroy(&sem0);
231	sem_destroy(&sem1);
232}
233