Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/unlink.cc
39536 views
1
/*-
2
* Copyright (c) 2019 The FreeBSD Foundation
3
*
4
* This software was developed by BFF Storage Systems, LLC under sponsorship
5
* from the FreeBSD Foundation.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
*
16
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
* SUCH DAMAGE.
27
*/
28
29
extern "C" {
30
#include <fcntl.h>
31
#include <semaphore.h>
32
}
33
34
#include "mockfs.hh"
35
#include "utils.hh"
36
37
using namespace testing;
38
39
class Unlink: public FuseTest {
40
public:
41
void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1)
42
{
43
EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
44
.Times(times)
45
.WillRepeatedly(Invoke(
46
ReturnImmediate([=](auto in __unused, auto& out) {
47
SET_OUT_HEADER_LEN(out, entry);
48
out.body.entry.attr.mode = S_IFREG | 0644;
49
out.body.entry.nodeid = ino;
50
out.body.entry.attr.nlink = nlink;
51
out.body.entry.attr_valid = UINT64_MAX;
52
out.body.entry.attr.size = 0;
53
})));
54
}
55
56
};
57
58
/*
59
* Unlinking a multiply linked file should update its ctime and nlink. This
60
* could be handled simply by invalidating the attributes, necessitating a new
61
* GETATTR, but we implement it in-kernel for efficiency's sake.
62
*/
63
TEST_F(Unlink, attr_cache)
64
{
65
const char FULLPATH0[] = "mountpoint/some_file.txt";
66
const char RELPATH0[] = "some_file.txt";
67
const char FULLPATH1[] = "mountpoint/other_file.txt";
68
const char RELPATH1[] = "other_file.txt";
69
uint64_t ino = 42;
70
struct stat sb_old, sb_new;
71
int fd1;
72
73
expect_lookup(RELPATH0, ino, 1, 2);
74
expect_lookup(RELPATH1, ino, 1, 2);
75
expect_open(ino, 0, 1);
76
expect_unlink(1, RELPATH0, 0);
77
78
fd1 = open(FULLPATH1, O_RDONLY);
79
ASSERT_LE(0, fd1) << strerror(errno);
80
81
ASSERT_EQ(0, fstat(fd1, &sb_old)) << strerror(errno);
82
ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
83
ASSERT_EQ(0, fstat(fd1, &sb_new)) << strerror(errno);
84
EXPECT_NE(sb_old.st_ctime, sb_new.st_ctime);
85
EXPECT_EQ(1u, sb_new.st_nlink);
86
87
leak(fd1);
88
}
89
90
/*
91
* A successful unlink should clear the parent directory's attribute cache,
92
* because the fuse daemon should update its mtime and ctime
93
*/
94
TEST_F(Unlink, parent_attr_cache)
95
{
96
const char FULLPATH[] = "mountpoint/some_file.txt";
97
const char RELPATH[] = "some_file.txt";
98
struct stat sb;
99
uint64_t ino = 42;
100
Sequence seq;
101
102
/* Use nlink=2 so we don't get a FUSE_FORGET */
103
expect_lookup(RELPATH, ino, 1, 2);
104
EXPECT_CALL(*m_mock, process(
105
ResultOf([=](auto in) {
106
return (in.header.opcode == FUSE_UNLINK &&
107
0 == strcmp(RELPATH, in.body.unlink) &&
108
in.header.nodeid == FUSE_ROOT_ID);
109
}, Eq(true)),
110
_)
111
).InSequence(seq)
112
.WillOnce(Invoke(ReturnErrno(0)));
113
EXPECT_CALL(*m_mock, process(
114
ResultOf([=](auto in) {
115
return (in.header.opcode == FUSE_GETATTR &&
116
in.header.nodeid == FUSE_ROOT_ID);
117
}, Eq(true)),
118
_)
119
).InSequence(seq)
120
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
121
SET_OUT_HEADER_LEN(out, attr);
122
out.body.attr.attr.ino = FUSE_ROOT_ID;
123
out.body.attr.attr.mode = S_IFDIR | 0755;
124
out.body.attr.attr_valid = UINT64_MAX;
125
})));
126
127
ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
128
EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
129
}
130
131
TEST_F(Unlink, eperm)
132
{
133
const char FULLPATH[] = "mountpoint/some_file.txt";
134
const char RELPATH[] = "some_file.txt";
135
uint64_t ino = 42;
136
137
expect_lookup(RELPATH, ino, 1);
138
expect_unlink(1, RELPATH, EPERM);
139
140
ASSERT_NE(0, unlink(FULLPATH));
141
ASSERT_EQ(EPERM, errno);
142
}
143
144
/*
145
* Unlinking a file should expire its entry cache, even if it's multiply linked
146
*/
147
TEST_F(Unlink, entry_cache)
148
{
149
const char FULLPATH[] = "mountpoint/some_file.txt";
150
const char RELPATH[] = "some_file.txt";
151
uint64_t ino = 42;
152
153
expect_lookup(RELPATH, ino, 2, 2);
154
expect_unlink(1, RELPATH, 0);
155
156
ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
157
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
158
}
159
160
/*
161
* Unlink a multiply-linked file. There should be no FUSE_FORGET because the
162
* file is still linked.
163
*/
164
TEST_F(Unlink, multiply_linked)
165
{
166
const char FULLPATH0[] = "mountpoint/some_file.txt";
167
const char RELPATH0[] = "some_file.txt";
168
const char FULLPATH1[] = "mountpoint/other_file.txt";
169
const char RELPATH1[] = "other_file.txt";
170
uint64_t ino = 42;
171
172
expect_lookup(RELPATH0, ino, 1, 2);
173
expect_unlink(1, RELPATH0, 0);
174
EXPECT_CALL(*m_mock, process(
175
ResultOf([=](auto in) {
176
return (in.header.opcode == FUSE_FORGET &&
177
in.header.nodeid == ino);
178
}, Eq(true)),
179
_)
180
).Times(0);
181
expect_lookup(RELPATH1, ino, 1, 1);
182
183
ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
184
185
/*
186
* The final syscall simply ensures that no FUSE_FORGET was ever sent,
187
* by scheduling an arbitrary different operation after a FUSE_FORGET
188
* would've been sent.
189
*/
190
ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
191
}
192
193
TEST_F(Unlink, ok)
194
{
195
const char FULLPATH[] = "mountpoint/some_file.txt";
196
const char RELPATH[] = "some_file.txt";
197
uint64_t ino = 42;
198
sem_t sem;
199
200
ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
201
202
expect_lookup(RELPATH, ino, 1);
203
expect_unlink(1, RELPATH, 0);
204
expect_forget(ino, 1, &sem);
205
206
ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
207
sem_wait(&sem);
208
sem_destroy(&sem);
209
}
210
211
/* Unlink an open file */
212
TEST_F(Unlink, open_but_deleted)
213
{
214
const char FULLPATH0[] = "mountpoint/some_file.txt";
215
const char RELPATH0[] = "some_file.txt";
216
const char FULLPATH1[] = "mountpoint/other_file.txt";
217
const char RELPATH1[] = "other_file.txt";
218
uint64_t ino = 42;
219
int fd;
220
221
expect_lookup(RELPATH0, ino, 2);
222
expect_open(ino, 0, 1);
223
expect_unlink(1, RELPATH0, 0);
224
expect_lookup(RELPATH1, ino, 1, 1);
225
226
fd = open(FULLPATH0, O_RDWR);
227
ASSERT_LE(0, fd) << strerror(errno);
228
ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno);
229
230
/*
231
* The final syscall simply ensures that no FUSE_FORGET was ever sent,
232
* by scheduling an arbitrary different operation after a FUSE_FORGET
233
* would've been sent.
234
*/
235
ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno);
236
leak(fd);
237
}
238
239