1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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
31extern "C" {
32#include <fcntl.h>
33#include <sys/socket.h>
34#include <sys/un.h>
35#include <semaphore.h>
36}
37
38#include "mockfs.hh"
39#include "utils.hh"
40
41using namespace testing;
42
43#ifndef VNOVAL
44#define VNOVAL (-1)	/* Defined in sys/vnode.h */
45#endif
46
47class Mknod: public FuseTest {
48
49mode_t m_oldmask;
50const static mode_t c_umask = 022;
51
52public:
53
54Mknod() {
55	m_oldmask = umask(c_umask);
56}
57
58virtual void SetUp() {
59	if (geteuid() != 0) {
60		GTEST_SKIP() << "Only root may use most mknod(2) variations";
61	}
62	FuseTest::SetUp();
63}
64
65virtual void TearDown() {
66	FuseTest::TearDown();
67	(void)umask(m_oldmask);
68}
69
70/* Test an OK creation of a file with the given mode and device number */
71void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino,
72		mode_t mode, dev_t dev)
73{
74	EXPECT_CALL(*m_mock, process(
75		ResultOf([=](auto in) {
76			const char *name = (const char*)in.body.bytes +
77				sizeof(fuse_mknod_in);
78			return (in.header.nodeid == parent_ino &&
79				in.header.opcode == FUSE_MKNOD &&
80				in.body.mknod.mode == mode &&
81				in.body.mknod.rdev == (uint32_t)dev &&
82				in.body.mknod.umask == c_umask &&
83				(0 == strcmp(relpath, name)));
84		}, Eq(true)),
85		_)
86	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
87		SET_OUT_HEADER_LEN(out, entry);
88		out.body.entry.attr.mode = mode;
89		out.body.entry.nodeid = ino;
90		out.body.entry.entry_valid = UINT64_MAX;
91		out.body.entry.attr_valid = UINT64_MAX;
92		out.body.entry.attr.rdev = dev;
93	})));
94}
95
96};
97
98class Mknod_7_11: public FuseTest {
99public:
100virtual void SetUp() {
101	m_kernel_minor_version = 11;
102	if (geteuid() != 0) {
103		GTEST_SKIP() << "Only root may use most mknod(2) variations";
104	}
105	FuseTest::SetUp();
106}
107
108void expect_lookup(const char *relpath, uint64_t ino, uint64_t size)
109{
110	FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1);
111}
112
113/* Test an OK creation of a file with the given mode and device number */
114void expect_mknod(uint64_t parent_ino, const char* relpath, uint64_t ino,
115		mode_t mode, dev_t dev)
116{
117	EXPECT_CALL(*m_mock, process(
118		ResultOf([=](auto in) {
119			const char *name = (const char*)in.body.bytes +
120				FUSE_COMPAT_MKNOD_IN_SIZE;
121			return (in.header.nodeid == parent_ino &&
122				in.header.opcode == FUSE_MKNOD &&
123				in.body.mknod.mode == mode &&
124				in.body.mknod.rdev == (uint32_t)dev &&
125				(0 == strcmp(relpath, name)));
126		}, Eq(true)),
127		_)
128	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
129		SET_OUT_HEADER_LEN(out, entry);
130		out.body.entry.attr.mode = mode;
131		out.body.entry.nodeid = ino;
132		out.body.entry.entry_valid = UINT64_MAX;
133		out.body.entry.attr_valid = UINT64_MAX;
134		out.body.entry.attr.rdev = dev;
135	})));
136}
137
138};
139
140/*
141 * mknod(2) should be able to create block devices on a FUSE filesystem.  Even
142 * though FreeBSD doesn't use block devices, this is useful when copying media
143 * from or preparing media for other operating systems.
144 */
145TEST_F(Mknod, blk)
146{
147	const char FULLPATH[] = "mountpoint/some_node";
148	const char RELPATH[] = "some_node";
149	mode_t mode = S_IFBLK | 0755;
150	dev_t rdev = 0xfe00; /* /dev/vda's device number on Linux */
151	uint64_t ino = 42;
152
153	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
154	.WillOnce(Invoke(ReturnErrno(ENOENT)));
155	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
156
157	EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno);
158}
159
160TEST_F(Mknod, chr)
161{
162	const char FULLPATH[] = "mountpoint/some_node";
163	const char RELPATH[] = "some_node";
164	mode_t mode = S_IFCHR | 0755;
165	dev_t rdev = 54;			/* /dev/fuse's device number */
166	uint64_t ino = 42;
167
168	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
169	.WillOnce(Invoke(ReturnErrno(ENOENT)));
170	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
171
172	EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno);
173}
174
175/*
176 * The daemon is responsible for checking file permissions (unless the
177 * default_permissions mount option was used)
178 */
179TEST_F(Mknod, eperm)
180{
181	const char FULLPATH[] = "mountpoint/some_node";
182	const char RELPATH[] = "some_node";
183	mode_t mode = S_IFIFO | 0755;
184
185	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
186	.WillOnce(Invoke(ReturnErrno(ENOENT)));
187
188	EXPECT_CALL(*m_mock, process(
189		ResultOf([=](auto in) {
190			const char *name = (const char*)in.body.bytes +
191				sizeof(fuse_mknod_in);
192			return (in.header.opcode == FUSE_MKNOD &&
193				in.body.mknod.mode == mode &&
194				(0 == strcmp(RELPATH, name)));
195		}, Eq(true)),
196		_)
197	).WillOnce(Invoke(ReturnErrno(EPERM)));
198	EXPECT_NE(0, mkfifo(FULLPATH, mode));
199	EXPECT_EQ(EPERM, errno);
200}
201
202TEST_F(Mknod, fifo)
203{
204	const char FULLPATH[] = "mountpoint/some_node";
205	const char RELPATH[] = "some_node";
206	mode_t mode = S_IFIFO | 0755;
207	dev_t rdev = VNOVAL;		/* Fifos don't have device numbers */
208	uint64_t ino = 42;
209
210	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
211	.WillOnce(Invoke(ReturnErrno(ENOENT)));
212	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
213
214	EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno);
215}
216
217/*
218 * Create a unix-domain socket.
219 *
220 * This test case doesn't actually need root privileges.
221 */
222TEST_F(Mknod, socket)
223{
224	const char FULLPATH[] = "mountpoint/some_node";
225	const char RELPATH[] = "some_node";
226	mode_t mode = S_IFSOCK | 0755;
227	struct sockaddr_un sa;
228	int fd;
229	dev_t rdev = -1;	/* Really it's a don't care */
230	uint64_t ino = 42;
231
232	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
233	.WillOnce(Invoke(ReturnErrno(ENOENT)));
234	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
235
236	fd = socket(AF_UNIX, SOCK_STREAM, 0);
237	ASSERT_LE(0, fd) << strerror(errno);
238	sa.sun_family = AF_UNIX;
239	strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path));
240	sa.sun_len = sizeof(FULLPATH);
241	ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa)))
242		<< strerror(errno);
243
244	leak(fd);
245}
246
247/*
248 * Nothing bad should happen if the server returns the parent's inode number
249 * for the newly created file.  Regression test for bug 263662.
250 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662
251 */
252TEST_F(Mknod, parent_inode)
253{
254	const char FULLPATH[] = "mountpoint/parent/some_node";
255	const char PPATH[] = "parent";
256	const char RELPATH[] = "some_node";
257	mode_t mode = S_IFSOCK | 0755;
258	struct sockaddr_un sa;
259	sem_t sem;
260	int fd;
261	dev_t rdev = -1;	/* Really it's a don't care */
262	uint64_t ino = 42;
263
264	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
265
266	expect_lookup(PPATH, ino, S_IFDIR | 0755, 0, 1);
267	EXPECT_LOOKUP(ino, RELPATH)
268	.WillOnce(Invoke(ReturnErrno(ENOENT)));
269	expect_mknod(ino, RELPATH, ino, mode, rdev);
270	expect_forget(ino, 1, &sem);
271
272	fd = socket(AF_UNIX, SOCK_STREAM, 0);
273	ASSERT_LE(0, fd) << strerror(errno);
274	sa.sun_family = AF_UNIX;
275	strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path));
276	sa.sun_len = sizeof(FULLPATH);
277	ASSERT_EQ(-1, bind(fd, (struct sockaddr*)&sa, sizeof(sa)));
278	ASSERT_EQ(EIO, errno);
279
280	leak(fd);
281	sem_wait(&sem);
282	sem_destroy(&sem);
283}
284
285/*
286 * fusefs(5) lacks VOP_WHITEOUT support.  No bugzilla entry, because that's a
287 * feature, not a bug
288 */
289TEST_F(Mknod, DISABLED_whiteout)
290{
291	const char FULLPATH[] = "mountpoint/some_node";
292	const char RELPATH[] = "some_node";
293	mode_t mode = S_IFWHT | 0755;
294	dev_t rdev = VNOVAL;	/* whiteouts don't have device numbers */
295	uint64_t ino = 42;
296
297	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
298	.WillOnce(Invoke(ReturnErrno(ENOENT)));
299	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
300
301	EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno);
302}
303
304/* A server built at protocol version 7.11 or earlier can still use mknod */
305TEST_F(Mknod_7_11, fifo)
306{
307	const char FULLPATH[] = "mountpoint/some_node";
308	const char RELPATH[] = "some_node";
309	mode_t mode = S_IFIFO | 0755;
310	dev_t rdev = VNOVAL;
311	uint64_t ino = 42;
312
313	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
314	.WillOnce(Invoke(ReturnErrno(ENOENT)));
315	expect_mknod(FUSE_ROOT_ID, RELPATH, ino, mode, rdev);
316
317	EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno);
318}
319