Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/bmap.cc
103381 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/ioctl.h>
34
#include <sys/filio.h>
35
36
#include <fcntl.h>
37
}
38
39
#include "mockfs.hh"
40
#include "utils.hh"
41
42
using namespace testing;
43
44
const static char FULLPATH[] = "mountpoint/foo";
45
const static char RELPATH[] = "foo";
46
47
class Bmap: public FuseTest {
48
public:
49
virtual void SetUp() {
50
m_maxreadahead = UINT32_MAX;
51
FuseTest::SetUp();
52
}
53
void expect_bmap(uint64_t ino, uint64_t lbn, uint32_t blocksize, uint64_t pbn)
54
{
55
EXPECT_CALL(*m_mock, process(
56
ResultOf([=](auto in) {
57
return (in.header.opcode == FUSE_BMAP &&
58
in.header.nodeid == ino &&
59
in.body.bmap.block == lbn &&
60
in.body.bmap.blocksize == blocksize);
61
}, Eq(true)),
62
_)
63
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
64
SET_OUT_HEADER_LEN(out, bmap);
65
out.body.bmap.block = pbn;
66
})));
67
}
68
69
void expect_lookup(const char *relpath, uint64_t ino, off_t size)
70
{
71
FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, size, 1,
72
UINT64_MAX);
73
}
74
};
75
76
class BmapEof: public Bmap, public WithParamInterface<int> {};
77
78
/*
79
* Test FUSE_BMAP
80
*/
81
TEST_F(Bmap, bmap)
82
{
83
struct fiobmap2_arg arg;
84
/*
85
* Pick fsize and lbn large enough that max length runs won't reach
86
* either beginning or end of file
87
*/
88
const off_t filesize = 1 << 30;
89
int64_t lbn = 100;
90
int64_t pbn = 12345;
91
const ino_t ino = 42;
92
int fd;
93
94
expect_lookup(RELPATH, 42, filesize);
95
expect_open(ino, 0, 1);
96
expect_bmap(ino, lbn, m_maxbcachebuf, pbn);
97
98
fd = open(FULLPATH, O_RDWR);
99
ASSERT_LE(0, fd) << strerror(errno);
100
101
arg.bn = lbn;
102
arg.runp = -1;
103
arg.runb = -1;
104
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
105
EXPECT_EQ(arg.bn, pbn);
106
/*
107
* XXX The FUSE protocol does not include the runp and runb variables,
108
* so those must be guessed in-kernel. There's no "right" answer, so
109
* just check that they're within reasonable limits.
110
*/
111
EXPECT_LE(arg.runb, lbn);
112
EXPECT_LE((unsigned long)arg.runb, m_maxreadahead / m_maxbcachebuf);
113
EXPECT_LE((unsigned long)arg.runb, m_maxphys / m_maxbcachebuf);
114
EXPECT_GT(arg.runb, 0);
115
EXPECT_LE(arg.runp, filesize / m_maxbcachebuf - lbn);
116
EXPECT_LE((unsigned long)arg.runp, m_maxreadahead / m_maxbcachebuf);
117
EXPECT_LE((unsigned long)arg.runp, m_maxphys / m_maxbcachebuf);
118
EXPECT_GT(arg.runp, 0);
119
120
leak(fd);
121
}
122
123
/*
124
* If the daemon does not implement VOP_BMAP, fusefs should return sensible
125
* defaults.
126
*/
127
TEST_F(Bmap, default_)
128
{
129
struct fiobmap2_arg arg;
130
const off_t filesize = 1 << 30;
131
const ino_t ino = 42;
132
int64_t lbn;
133
int fd;
134
135
expect_lookup(RELPATH, 42, filesize);
136
expect_open(ino, 0, 1);
137
EXPECT_CALL(*m_mock, process(
138
ResultOf([=](auto in) {
139
return (in.header.opcode == FUSE_BMAP);
140
}, Eq(true)),
141
_)
142
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
143
144
fd = open(FULLPATH, O_RDWR);
145
ASSERT_LE(0, fd) << strerror(errno);
146
147
/* First block */
148
lbn = 0;
149
arg.bn = lbn;
150
arg.runp = -1;
151
arg.runb = -1;
152
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
153
EXPECT_EQ(arg.bn, 0);
154
EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
155
EXPECT_EQ(arg.runb, 0);
156
157
/* In the middle */
158
lbn = filesize / m_maxbcachebuf / 2;
159
arg.bn = lbn;
160
arg.runp = -1;
161
arg.runb = -1;
162
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
163
EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
164
EXPECT_EQ((unsigned long )arg.runp, m_maxphys / m_maxbcachebuf - 1);
165
EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
166
167
/* Last block */
168
lbn = filesize / m_maxbcachebuf - 1;
169
arg.bn = lbn;
170
arg.runp = -1;
171
arg.runb = -1;
172
ASSERT_EQ(0, ioctl(fd, FIOBMAP2, &arg)) << strerror(errno);
173
EXPECT_EQ(arg.bn, lbn * m_maxbcachebuf / DEV_BSIZE);
174
EXPECT_EQ(arg.runp, 0);
175
EXPECT_EQ((unsigned long )arg.runb, m_maxphys / m_maxbcachebuf - 1);
176
177
leak(fd);
178
}
179
180
/*
181
* The server returns an error for some reason for FUSE_BMAP. fusefs should
182
* faithfully report that error up to the caller.
183
*/
184
TEST_F(Bmap, einval)
185
{
186
struct fiobmap2_arg arg;
187
const off_t filesize = 1 << 30;
188
int64_t lbn = 100;
189
const ino_t ino = 42;
190
int fd;
191
192
expect_lookup(RELPATH, 42, filesize);
193
expect_open(ino, 0, 1);
194
EXPECT_CALL(*m_mock, process(
195
ResultOf([=](auto in) {
196
return (in.header.opcode == FUSE_BMAP &&
197
in.header.nodeid == ino);
198
}, Eq(true)),
199
_)
200
).WillOnce(Invoke(ReturnErrno(EINVAL)));
201
202
fd = open(FULLPATH, O_RDWR);
203
ASSERT_LE(0, fd) << strerror(errno);
204
205
arg.bn = lbn;
206
arg.runp = -1;
207
arg.runb = -1;
208
ASSERT_EQ(-1, ioctl(fd, FIOBMAP2, &arg));
209
EXPECT_EQ(EINVAL, errno);
210
211
leak(fd);
212
}
213
214
/*
215
* Even if the server returns EINVAL during VOP_BMAP, we should still be able
216
* to successfully read a block. This is a regression test for
217
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=264196 . The bug did not
218
* lie in fusefs, but this is a convenient place for a regression test.
219
*/
220
TEST_F(Bmap, spurious_einval)
221
{
222
const off_t filesize = 4ull << 30;
223
const ino_t ino = 42;
224
int fd, r;
225
char buf[1];
226
227
expect_lookup(RELPATH, 42, filesize);
228
expect_open(ino, 0, 1);
229
EXPECT_CALL(*m_mock, process(
230
ResultOf([=](auto in) {
231
return (in.header.opcode == FUSE_BMAP &&
232
in.header.nodeid == ino);
233
}, Eq(true)),
234
_)
235
).WillRepeatedly(Invoke(ReturnErrno(EINVAL)));
236
EXPECT_CALL(*m_mock, process(
237
ResultOf([=](auto in) {
238
return (in.header.opcode == FUSE_READ &&
239
in.header.nodeid == ino &&
240
in.body.read.offset == 0 &&
241
in.body.read.size == (uint64_t)m_maxbcachebuf);
242
}, Eq(true)),
243
_)
244
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
245
size_t osize = in.body.read.size;
246
247
assert(osize < sizeof(out.body.bytes));
248
out.header.len = sizeof(struct fuse_out_header) + osize;
249
bzero(out.body.bytes, osize);
250
})));
251
252
fd = open(FULLPATH, O_RDWR);
253
ASSERT_LE(0, fd) << strerror(errno);
254
255
/*
256
* Read the same block multiple times. On a system affected by PR
257
* 264196 , the second read will fail.
258
*/
259
r = read(fd, buf, sizeof(buf));
260
EXPECT_EQ(r, 1) << strerror(errno);
261
r = read(fd, buf, sizeof(buf));
262
EXPECT_EQ(r, 1) << strerror(errno);
263
r = read(fd, buf, sizeof(buf));
264
EXPECT_EQ(r, 1) << strerror(errno);
265
}
266
267
/*
268
* VOP_BMAP should not query the server for the file's size, even if its cached
269
* attributes have expired.
270
* Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256937
271
*/
272
TEST_P(BmapEof, eof)
273
{
274
/*
275
* Outline:
276
* 1) lookup the file, setting attr_valid=0
277
* 2) Read more than one block, causing the kernel to issue VOP_BMAP to
278
* plan readahead.
279
* 3) Nothing should panic
280
* 4) Repeat the tests, truncating the file after different numbers of
281
* GETATTR operations.
282
*/
283
Sequence seq;
284
const off_t filesize = 2 * m_maxbcachebuf;
285
const ino_t ino = 42;
286
mode_t mode = S_IFREG | 0644;
287
char *buf;
288
int fd;
289
int ngetattrs;
290
291
ngetattrs = GetParam();
292
FuseTest::expect_lookup(RELPATH, ino, mode, filesize, 1, 0);
293
expect_open(ino, 0, 1);
294
// Depending on ngetattrs, FUSE_READ could be called with either
295
// filesize or filesize / 2 .
296
EXPECT_CALL(*m_mock, process(
297
ResultOf([=](auto in) {
298
return (in.header.opcode == FUSE_READ &&
299
in.header.nodeid == ino &&
300
in.body.read.offset == 0 &&
301
( in.body.read.size == filesize ||
302
in.body.read.size == filesize / 2));
303
}, Eq(true)),
304
_)
305
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
306
size_t osize = in.body.read.size;
307
308
assert(osize < sizeof(out.body.bytes));
309
out.header.len = sizeof(struct fuse_out_header) + osize;
310
bzero(out.body.bytes, osize);
311
})));
312
EXPECT_CALL(*m_mock, process(
313
ResultOf([](auto in) {
314
return (in.header.opcode == FUSE_GETATTR &&
315
in.header.nodeid == ino);
316
}, Eq(true)),
317
_)
318
).Times(Between(ngetattrs - 1, ngetattrs))
319
.InSequence(seq)
320
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
321
SET_OUT_HEADER_LEN(out, attr);
322
out.body.attr.attr_valid = 0;
323
out.body.attr.attr.ino = ino;
324
out.body.attr.attr.mode = S_IFREG | 0644;
325
out.body.attr.attr.size = filesize;
326
})));
327
EXPECT_CALL(*m_mock, process(
328
ResultOf([](auto in) {
329
return (in.header.opcode == FUSE_GETATTR &&
330
in.header.nodeid == ino);
331
}, Eq(true)),
332
_)
333
).InSequence(seq)
334
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
335
SET_OUT_HEADER_LEN(out, attr);
336
out.body.attr.attr_valid = 0;
337
out.body.attr.attr.ino = ino;
338
out.body.attr.attr.mode = S_IFREG | 0644;
339
out.body.attr.attr.size = filesize / 2;
340
})));
341
342
buf = new char[filesize]();
343
fd = open(FULLPATH, O_RDWR);
344
ASSERT_LE(0, fd) << strerror(errno);
345
read(fd, buf, filesize);
346
347
delete[] buf;
348
leak(fd);
349
}
350
351
INSTANTIATE_TEST_SUITE_P(BE, BmapEof,
352
Values(1, 2, 3)
353
);
354
355