Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/destroy.cc
105174 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 <sys/param.h>
33
#include <sys/mount.h>
34
35
#include <fcntl.h>
36
#include <pthread.h>
37
#include <semaphore.h>
38
}
39
40
#include "mockfs.hh"
41
#include "utils.hh"
42
43
using namespace testing;
44
45
/* Tests for orderly unmounts */
46
class Destroy: public FuseTest {};
47
48
/* Tests for unexpected deaths of the server */
49
class Death: public FuseTest{};
50
51
/* Tests for the auto_unmount mount option*/
52
class AutoUnmount: public FuseTest {
53
virtual void SetUp() {
54
m_auto_unmount = true;
55
FuseTest::SetUp();
56
}
57
58
protected:
59
/* Unmounting fusefs might be asynchronous with close, so use a retry loop */
60
void assert_unmounted() {
61
struct statfs statbuf;
62
63
for (int retry = 100; retry > 0; retry--) {
64
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
65
if (strcmp("fusefs", statbuf.f_fstypename) != 0 &&
66
strcmp("/dev/fuse", statbuf.f_mntfromname) != 0)
67
return;
68
nap();
69
}
70
FAIL() << "fusefs is still mounted";
71
}
72
73
};
74
75
static void* open_th(void* arg) {
76
int fd;
77
const char *path = (const char*)arg;
78
79
fd = open(path, O_RDONLY);
80
EXPECT_EQ(-1, fd);
81
EXPECT_EQ(ENOTCONN, errno);
82
return 0;
83
}
84
85
/*
86
* With the auto_unmount mount option, the kernel will automatically unmount
87
* the file system when the server dies.
88
*/
89
TEST_F(AutoUnmount, auto_unmount)
90
{
91
/* Kill the daemon */
92
m_mock->kill_daemon();
93
94
/* Use statfs to check that the file system is no longer mounted */
95
assert_unmounted();
96
}
97
98
/*
99
* When -o auto_unmount is used, the kernel should _not_ unmount the file
100
* system when any /dev/fuse file descriptor is closed, but only for the last
101
* one.
102
*/
103
TEST_F(AutoUnmount, dup)
104
{
105
struct statfs statbuf;
106
int fuse2;
107
108
EXPECT_CALL(*m_mock, process(
109
ResultOf([](auto in) {
110
return (in.header.opcode == FUSE_STATFS);
111
}, Eq(true)),
112
_)
113
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
114
SET_OUT_HEADER_LEN(out, statfs);
115
})));
116
117
fuse2 = dup_dev_fuse();
118
119
/*
120
* Close one of the /dev/fuse file descriptors. Close the duplicate
121
* first so the daemon thread doesn't freak out when it gets a bunch of
122
* EBADF errors.
123
*/
124
close(fuse2);
125
126
/* Use statfs to check that the file system is still mounted */
127
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
128
EXPECT_EQ(0, strcmp("fusefs", statbuf.f_fstypename));
129
EXPECT_EQ(0, strcmp("/dev/fuse", statbuf.f_mntfromname));
130
131
/*
132
* Close the original file descriptor too. Now the file system should be
133
* unmounted at last.
134
*/
135
m_mock->kill_daemon();
136
assert_unmounted();
137
}
138
139
/*
140
* The server dies with unsent operations still on the message queue.
141
* Check for any memory leaks like this:
142
* 1) kldunload fusefs, if necessary
143
* 2) kldload fusefs
144
* 3) ./destroy --gtest_filter=Death.unsent_operations
145
* 4) kldunload fusefs
146
* 5) check /var/log/messages for anything like this:
147
Freed UMA keg (fuse_ticket) was not empty (31 items). Lost 2 pages of memory.
148
Warning: memory type fuse_msgbuf leaked memory on destroy (68 allocations, 428800 bytes leaked).
149
*/
150
TEST_F(Death, unsent_operations)
151
{
152
const char FULLPATH0[] = "mountpoint/some_file.txt";
153
const char FULLPATH1[] = "mountpoint/other_file.txt";
154
const char RELPATH0[] = "some_file.txt";
155
const char RELPATH1[] = "other_file.txt";
156
pthread_t th0, th1;
157
ino_t ino0 = 42, ino1 = 43;
158
sem_t sem;
159
mode_t mode = S_IFREG | 0644;
160
161
sem_init(&sem, 0, 0);
162
163
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
164
.WillRepeatedly(Invoke(
165
ReturnImmediate([=](auto in __unused, auto& out) {
166
SET_OUT_HEADER_LEN(out, entry);
167
out.body.entry.attr.mode = mode;
168
out.body.entry.nodeid = ino0;
169
out.body.entry.attr.nlink = 1;
170
})));
171
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
172
.WillRepeatedly(Invoke(
173
ReturnImmediate([=](auto in __unused, auto& out) {
174
SET_OUT_HEADER_LEN(out, entry);
175
out.body.entry.attr.mode = mode;
176
out.body.entry.nodeid = ino1;
177
out.body.entry.attr.nlink = 1;
178
})));
179
180
EXPECT_CALL(*m_mock, process(
181
ResultOf([&](auto in) {
182
return (in.header.opcode == FUSE_OPEN);
183
}, Eq(true)),
184
_)
185
).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
186
sem_post(&sem);
187
pause();
188
}));
189
190
/*
191
* One thread's operation will be sent to the daemon and block, and the
192
* other's will be stuck in the message queue.
193
*/
194
ASSERT_EQ(0, pthread_create(&th0, NULL, open_th,
195
__DECONST(void*, FULLPATH0))) << strerror(errno);
196
ASSERT_EQ(0, pthread_create(&th1, NULL, open_th,
197
__DECONST(void*, FULLPATH1))) << strerror(errno);
198
199
/* Wait for the first thread to block */
200
sem_wait(&sem);
201
/* Give the second thread time to block */
202
nap();
203
204
m_mock->kill_daemon();
205
206
pthread_join(th0, NULL);
207
pthread_join(th1, NULL);
208
209
sem_destroy(&sem);
210
}
211
212
/*
213
* On unmount the kernel should send a FUSE_DESTROY operation. It should also
214
* send FUSE_FORGET operations for all inodes with lookup_count > 0.
215
*/
216
TEST_F(Destroy, ok)
217
{
218
const char FULLPATH[] = "mountpoint/some_file.txt";
219
const char RELPATH[] = "some_file.txt";
220
uint64_t ino = 42;
221
222
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2);
223
expect_forget(ino, 2);
224
expect_destroy(0);
225
226
/*
227
* access(2) the file to force a lookup. Access it twice to double its
228
* lookup count.
229
*/
230
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
231
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
232
233
/*
234
* Unmount, triggering a FUSE_DESTROY and also causing a VOP_RECLAIM
235
* for every vnode on this mp, triggering FUSE_FORGET for each of them.
236
*/
237
m_mock->unmount();
238
}
239
240