Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/mknod.cc
39537 views
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
31
extern "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
41
using namespace testing;
42
43
#ifndef VNOVAL
44
#define VNOVAL (-1) /* Defined in sys/vnode.h */
45
#endif
46
47
class Mknod: public FuseTest {
48
49
mode_t m_oldmask;
50
const static mode_t c_umask = 022;
51
52
public:
53
54
Mknod() {
55
m_oldmask = umask(c_umask);
56
}
57
58
virtual void SetUp() {
59
if (geteuid() != 0) {
60
GTEST_SKIP() << "Only root may use most mknod(2) variations";
61
}
62
FuseTest::SetUp();
63
}
64
65
virtual 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 */
71
void 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
98
class Mknod_7_11: public FuseTest {
99
public:
100
virtual 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
108
void 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 */
114
void 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
*/
145
TEST_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
160
TEST_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
*/
179
TEST_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
202
TEST_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
*/
222
TEST_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
*/
252
TEST_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(4) lacks VOP_WHITEOUT support. No bugzilla entry, because that's a
287
* feature, not a bug
288
*/
289
TEST_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 */
305
TEST_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
320