Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/release.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 <sys/socket.h>
33
#include <sys/un.h>
34
35
#include <fcntl.h>
36
#include <unistd.h>
37
}
38
39
#include "mockfs.hh"
40
#include "utils.hh"
41
42
using namespace testing;
43
44
class Release: public FuseTest {
45
46
public:
47
void expect_lookup(const char *relpath, uint64_t ino, int times)
48
{
49
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times);
50
}
51
52
void expect_release(uint64_t ino, uint64_t lock_owner,
53
uint32_t flags, int error)
54
{
55
EXPECT_CALL(*m_mock, process(
56
ResultOf([=](auto in) {
57
return (in.header.opcode == FUSE_RELEASE &&
58
in.header.nodeid == ino &&
59
in.body.release.lock_owner == lock_owner &&
60
in.body.release.fh == FH &&
61
in.body.release.flags == flags);
62
}, Eq(true)),
63
_)
64
).WillOnce(Invoke(ReturnErrno(error)))
65
.RetiresOnSaturation();
66
}
67
};
68
69
class ReleaseWithLocks: public Release {
70
virtual void SetUp() {
71
m_init_flags = FUSE_POSIX_LOCKS;
72
Release::SetUp();
73
}
74
};
75
76
77
/* If a file descriptor is duplicated, only the last close causes RELEASE */
78
TEST_F(Release, dup)
79
{
80
const char FULLPATH[] = "mountpoint/some_file.txt";
81
const char RELPATH[] = "some_file.txt";
82
uint64_t ino = 42;
83
int fd, fd2;
84
85
expect_lookup(RELPATH, ino, 1);
86
expect_open(ino, 0, 1);
87
expect_flush(ino, 1, ReturnErrno(0));
88
expect_release(ino, getpid(), O_RDONLY, 0);
89
90
fd = open(FULLPATH, O_RDONLY);
91
ASSERT_LE(0, fd) << strerror(errno);
92
93
fd2 = dup(fd);
94
ASSERT_LE(0, fd2) << strerror(errno);
95
96
ASSERT_EQ(0, close(fd2)) << strerror(errno);
97
ASSERT_EQ(0, close(fd)) << strerror(errno);
98
}
99
100
/*
101
* Some FUSE filesystem cache data internally and flush it on release. Such
102
* filesystems may generate errors during release. On Linux, these get
103
* returned by close(2). However, POSIX does not require close(2) to return
104
* this error. FreeBSD's fuse(4) should return EIO if it returns an error at
105
* all.
106
*/
107
/* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */
108
TEST_F(Release, eio)
109
{
110
const char FULLPATH[] = "mountpoint/some_file.txt";
111
const char RELPATH[] = "some_file.txt";
112
uint64_t ino = 42;
113
int fd;
114
115
expect_lookup(RELPATH, ino, 1);
116
expect_open(ino, 0, 1);
117
expect_flush(ino, 1, ReturnErrno(0));
118
expect_release(ino, getpid(), O_WRONLY, EIO);
119
120
fd = open(FULLPATH, O_WRONLY);
121
ASSERT_LE(0, fd) << strerror(errno);
122
123
ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno);
124
}
125
126
/*
127
* FUSE_RELEASE should contain the same flags used for FUSE_OPEN
128
*/
129
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */
130
TEST_F(Release, DISABLED_flags)
131
{
132
const char FULLPATH[] = "mountpoint/some_file.txt";
133
const char RELPATH[] = "some_file.txt";
134
uint64_t ino = 42;
135
int fd;
136
137
expect_lookup(RELPATH, ino, 1);
138
expect_open(ino, 0, 1);
139
expect_flush(ino, 1, ReturnErrno(0));
140
expect_release(ino, getpid(), O_RDWR | O_APPEND, 0);
141
142
fd = open(FULLPATH, O_RDWR | O_APPEND);
143
ASSERT_LE(0, fd) << strerror(errno);
144
145
ASSERT_EQ(0, close(fd)) << strerror(errno);
146
}
147
148
/*
149
* fuse(4) will issue multiple FUSE_OPEN operations for the same file if it's
150
* opened with different modes. Each FUSE_OPEN should get its own
151
* FUSE_RELEASE.
152
*/
153
TEST_F(Release, multiple_opens)
154
{
155
const char FULLPATH[] = "mountpoint/some_file.txt";
156
const char RELPATH[] = "some_file.txt";
157
uint64_t ino = 42;
158
int fd, fd2;
159
160
expect_lookup(RELPATH, ino, 2);
161
expect_open(ino, 0, 2);
162
expect_flush(ino, 2, ReturnErrno(0));
163
expect_release(ino, getpid(), O_RDONLY, 0);
164
165
fd = open(FULLPATH, O_RDONLY);
166
ASSERT_LE(0, fd) << strerror(errno);
167
168
expect_release(ino, getpid(), O_WRONLY, 0);
169
fd2 = open(FULLPATH, O_WRONLY);
170
ASSERT_LE(0, fd2) << strerror(errno);
171
172
ASSERT_EQ(0, close(fd2)) << strerror(errno);
173
ASSERT_EQ(0, close(fd)) << strerror(errno);
174
}
175
176
TEST_F(Release, ok)
177
{
178
const char FULLPATH[] = "mountpoint/some_file.txt";
179
const char RELPATH[] = "some_file.txt";
180
uint64_t ino = 42;
181
int fd;
182
183
expect_lookup(RELPATH, ino, 1);
184
expect_open(ino, 0, 1);
185
expect_flush(ino, 1, ReturnErrno(0));
186
expect_release(ino, getpid(), O_RDONLY, 0);
187
188
fd = open(FULLPATH, O_RDONLY);
189
ASSERT_LE(0, fd) << strerror(errno);
190
191
ASSERT_EQ(0, close(fd)) << strerror(errno);
192
}
193
194
/*
195
* Nothing bad should happen when closing a Unix-domain named socket that
196
* contains a fusefs file descriptor within its receive buffer.
197
* Regression test for
198
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=289686
199
*/
200
TEST_F(Release, scm_rights)
201
{
202
const char FULLPATH[] = "mountpoint/some_file.txt";
203
const char RELPATH[] = "some_file.txt";
204
struct msghdr msg;
205
struct iovec iov;
206
char message[CMSG_SPACE(sizeof(int))];
207
uint64_t ino = 42;
208
int fd;
209
int s[2];
210
union {
211
char buf[CMSG_SPACE(sizeof(fd))];
212
struct cmsghdr align;
213
} u;
214
215
expect_lookup(RELPATH, ino, 1);
216
expect_open(ino, 0, 1);
217
expect_flush(ino, 1, ReturnErrno(0));
218
expect_release(ino, getpid(), O_RDONLY, 0);
219
220
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, s)) << strerror(errno);
221
222
fd = open(FULLPATH, O_RDONLY);
223
ASSERT_LE(0, fd) << strerror(errno);
224
225
memset(&message, 0, sizeof(message));
226
memset(&msg, 0, sizeof(msg));
227
iov.iov_base = NULL;
228
iov.iov_len = 0;
229
msg.msg_iov = &iov;
230
msg.msg_iovlen = 1;
231
msg.msg_control = u.buf,
232
msg.msg_controllen = sizeof(u.buf);
233
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
234
cmsg->cmsg_level = SOL_SOCKET;
235
cmsg->cmsg_type = SCM_RIGHTS;
236
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
237
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
238
ASSERT_GE(sendmsg(s[0], &msg, 0), 0) << strerror(errno);
239
240
close(fd); // Close fd within our process
241
close(s[0]);
242
close(s[1]); // The last copy of fd is within this socket's rcvbuf
243
}
244
245
/* When closing a file with a POSIX file lock, release should release the lock*/
246
TEST_F(ReleaseWithLocks, unlock_on_close)
247
{
248
const char FULLPATH[] = "mountpoint/some_file.txt";
249
const char RELPATH[] = "some_file.txt";
250
uint64_t ino = 42;
251
int fd;
252
struct flock fl;
253
pid_t pid = getpid();
254
255
expect_lookup(RELPATH, ino, 1);
256
expect_open(ino, 0, 1);
257
EXPECT_CALL(*m_mock, process(
258
ResultOf([=](auto in) {
259
return (in.header.opcode == FUSE_SETLK &&
260
in.header.nodeid == ino &&
261
in.body.setlk.lk.type == F_RDLCK &&
262
in.body.setlk.fh == FH);
263
}, Eq(true)),
264
_)
265
).WillOnce(Invoke(ReturnErrno(0)));
266
EXPECT_CALL(*m_mock, process(
267
ResultOf([=](auto in) {
268
return (in.header.opcode == FUSE_SETLK &&
269
in.header.nodeid == ino &&
270
in.body.setlk.lk.type == F_UNLCK &&
271
in.body.setlk.fh == FH);
272
}, Eq(true)),
273
_)
274
).WillOnce(Invoke(ReturnErrno(0)));
275
expect_flush(ino, 1, ReturnErrno(0));
276
expect_release(ino, static_cast<uint64_t>(pid), O_RDWR, 0);
277
278
fd = open(FULLPATH, O_RDWR);
279
ASSERT_LE(0, fd) << strerror(errno);
280
fl.l_start = 0;
281
fl.l_len = 0;
282
fl.l_pid = pid;
283
fl.l_type = F_RDLCK;
284
fl.l_whence = SEEK_SET;
285
fl.l_sysid = 0;
286
ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno);
287
288
ASSERT_EQ(0, close(fd)) << strerror(errno);
289
}
290
291