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