Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/lseek.cc
39536 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2020 Alan Somers
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
8
* are met:
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
* SUCH DAMAGE.
26
*/
27
28
extern "C" {
29
#include <sys/param.h>
30
31
#include <fcntl.h>
32
}
33
34
#include "mockfs.hh"
35
#include "utils.hh"
36
37
using namespace testing;
38
39
class Lseek: public FuseTest {};
40
class LseekPathconf: public Lseek {};
41
class LseekPathconf_7_23: public LseekPathconf {
42
public:
43
virtual void SetUp() {
44
m_kernel_minor_version = 23;
45
FuseTest::SetUp();
46
}
47
};
48
class LseekSeekHole: public Lseek {};
49
class LseekSeekData: public Lseek {};
50
51
/*
52
* If a previous lseek operation has already returned enosys, then pathconf can
53
* return EINVAL immediately.
54
*/
55
TEST_F(LseekPathconf, already_enosys)
56
{
57
const char FULLPATH[] = "mountpoint/some_file.txt";
58
const char RELPATH[] = "some_file.txt";
59
const uint64_t ino = 42;
60
off_t fsize = 1 << 30; /* 1 GiB */
61
off_t offset_in = 1 << 28;
62
int fd;
63
64
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
65
expect_open(ino, 0, 1);
66
EXPECT_CALL(*m_mock, process(
67
ResultOf([=](auto in) {
68
return (in.header.opcode == FUSE_LSEEK);
69
}, Eq(true)),
70
_)
71
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
72
73
fd = open(FULLPATH, O_RDONLY);
74
ASSERT_LE(0, fd);
75
76
EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
77
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
78
EXPECT_EQ(EINVAL, errno);
79
80
leak(fd);
81
}
82
83
/*
84
* If a previous lseek operation has already returned successfully, then
85
* pathconf can return 1 immediately. 1 means "holes are reported, but size is
86
* not specified".
87
*/
88
TEST_F(LseekPathconf, already_seeked)
89
{
90
const char FULLPATH[] = "mountpoint/some_file.txt";
91
const char RELPATH[] = "some_file.txt";
92
const uint64_t ino = 42;
93
off_t fsize = 1 << 30; /* 1 GiB */
94
off_t offset = 1 << 28;
95
int fd;
96
97
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
98
expect_open(ino, 0, 1);
99
EXPECT_CALL(*m_mock, process(
100
ResultOf([=](auto in) {
101
return (in.header.opcode == FUSE_LSEEK);
102
}, Eq(true)),
103
_)
104
).WillOnce(Invoke(ReturnImmediate([=](auto i, auto& out) {
105
SET_OUT_HEADER_LEN(out, lseek);
106
out.body.lseek.offset = i.body.lseek.offset;
107
})));
108
fd = open(FULLPATH, O_RDONLY);
109
ASSERT_LE(0, fd);
110
EXPECT_EQ(offset, lseek(fd, offset, SEEK_DATA));
111
112
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
113
114
leak(fd);
115
}
116
117
/*
118
* Use pathconf on a file not already opened. The server returns EACCES when
119
* the kernel tries to open it. The kernel should return EACCES, and make no
120
* judgement about whether the server does or does not support FUSE_LSEEK.
121
*/
122
TEST_F(LseekPathconf, eacces)
123
{
124
const char FULLPATH[] = "mountpoint/some_file.txt";
125
const char RELPATH[] = "some_file.txt";
126
const uint64_t ino = 42;
127
off_t fsize = 1 << 30; /* 1 GiB */
128
129
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
130
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
131
SET_OUT_HEADER_LEN(out, entry);
132
out.body.entry.entry_valid = UINT64_MAX;
133
out.body.entry.attr.mode = S_IFREG | 0644;
134
out.body.entry.nodeid = ino;
135
out.body.entry.attr.size = fsize;
136
})));
137
EXPECT_CALL(*m_mock, process(
138
ResultOf([=](auto in) {
139
return (in.header.opcode == FUSE_OPEN &&
140
in.header.nodeid == ino);
141
}, Eq(true)),
142
_)
143
).Times(2)
144
.WillRepeatedly(Invoke(ReturnErrno(EACCES)));
145
146
EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
147
EXPECT_EQ(EACCES, errno);
148
/* Check again, to ensure that the kernel didn't record the response */
149
EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
150
EXPECT_EQ(EACCES, errno);
151
}
152
153
/*
154
* If the server returns some weird error when we try FUSE_LSEEK, send that to
155
* the caller but don't record the answer.
156
*/
157
TEST_F(LseekPathconf, eio)
158
{
159
const char FULLPATH[] = "mountpoint/some_file.txt";
160
const char RELPATH[] = "some_file.txt";
161
const uint64_t ino = 42;
162
off_t fsize = 1 << 30; /* 1 GiB */
163
int fd;
164
165
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
166
expect_open(ino, 0, 1);
167
EXPECT_CALL(*m_mock, process(
168
ResultOf([=](auto in) {
169
return (in.header.opcode == FUSE_LSEEK);
170
}, Eq(true)),
171
_)
172
).Times(2)
173
.WillRepeatedly(Invoke(ReturnErrno(EIO)));
174
175
fd = open(FULLPATH, O_RDONLY);
176
ASSERT_LE(0, fd);
177
178
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
179
EXPECT_EQ(EIO, errno);
180
/* Check again, to ensure that the kernel didn't record the response */
181
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
182
EXPECT_EQ(EIO, errno);
183
184
leak(fd);
185
}
186
187
/*
188
* If no FUSE_LSEEK operation has been attempted since mount, try once as soon
189
* as a pathconf request comes in.
190
*/
191
TEST_F(LseekPathconf, enosys_now)
192
{
193
const char FULLPATH[] = "mountpoint/some_file.txt";
194
const char RELPATH[] = "some_file.txt";
195
const uint64_t ino = 42;
196
off_t fsize = 1 << 30; /* 1 GiB */
197
int fd;
198
199
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
200
expect_open(ino, 0, 1);
201
EXPECT_CALL(*m_mock, process(
202
ResultOf([=](auto in) {
203
return (in.header.opcode == FUSE_LSEEK);
204
}, Eq(true)),
205
_)
206
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
207
208
fd = open(FULLPATH, O_RDONLY);
209
ASSERT_LE(0, fd);
210
211
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
212
EXPECT_EQ(EINVAL, errno);
213
214
leak(fd);
215
}
216
217
/*
218
* Use pathconf, rather than fpathconf, on a file not already opened.
219
* Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278135
220
*/
221
TEST_F(LseekPathconf, pathconf)
222
{
223
const char FULLPATH[] = "mountpoint/some_file.txt";
224
const char RELPATH[] = "some_file.txt";
225
const uint64_t ino = 42;
226
off_t fsize = 1 << 30; /* 1 GiB */
227
off_t offset_out = 1 << 29;
228
229
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
230
expect_open(ino, 0, 1);
231
EXPECT_CALL(*m_mock, process(
232
ResultOf([=](auto in) {
233
return (in.header.opcode == FUSE_LSEEK);
234
}, Eq(true)),
235
_)
236
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
237
SET_OUT_HEADER_LEN(out, lseek);
238
out.body.lseek.offset = offset_out;
239
})));
240
expect_release(ino, FuseTest::FH);
241
242
EXPECT_EQ(1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)) << strerror(errno);
243
}
244
245
/*
246
* If no FUSE_LSEEK operation has been attempted since mount, try one as soon
247
* as a pathconf request comes in. This is the typical pattern of bsdtar. It
248
* will only try SEEK_HOLE/SEEK_DATA if fpathconf says they're supported.
249
*/
250
TEST_F(LseekPathconf, seek_now)
251
{
252
const char FULLPATH[] = "mountpoint/some_file.txt";
253
const char RELPATH[] = "some_file.txt";
254
const uint64_t ino = 42;
255
off_t fsize = 1 << 30; /* 1 GiB */
256
off_t offset_initial = 1 << 27;
257
off_t offset_out = 1 << 29;
258
int fd;
259
260
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
261
expect_open(ino, 0, 1);
262
EXPECT_CALL(*m_mock, process(
263
ResultOf([=](auto in) {
264
return (in.header.opcode == FUSE_LSEEK);
265
}, Eq(true)),
266
_)
267
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
268
SET_OUT_HEADER_LEN(out, lseek);
269
out.body.lseek.offset = offset_out;
270
})));
271
272
fd = open(FULLPATH, O_RDONLY);
273
ASSERT_LE(0, fd);
274
EXPECT_EQ(offset_initial, lseek(fd, offset_initial, SEEK_SET));
275
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
276
/* And check that the file pointer hasn't changed */
277
EXPECT_EQ(offset_initial, lseek(fd, 0, SEEK_CUR));
278
279
leak(fd);
280
}
281
282
/*
283
* If the user calls pathconf(_, _PC_MIN_HOLE_SIZE) on a fully sparse or
284
* zero-length file, then SEEK_DATA will return ENXIO. That should be
285
* interpreted as success.
286
*/
287
TEST_F(LseekPathconf, zerolength)
288
{
289
const char FULLPATH[] = "mountpoint/some_file.txt";
290
const char RELPATH[] = "some_file.txt";
291
const uint64_t ino = 42;
292
off_t fsize = 0;
293
int fd;
294
295
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
296
expect_open(ino, 0, 1);
297
EXPECT_CALL(*m_mock, process(
298
ResultOf([=](auto in) {
299
return (in.header.opcode == FUSE_LSEEK &&
300
in.header.nodeid == ino &&
301
in.body.lseek.whence == SEEK_DATA);
302
}, Eq(true)),
303
_)
304
).WillOnce(Invoke(ReturnErrno(ENXIO)));
305
306
fd = open(FULLPATH, O_RDONLY);
307
ASSERT_LE(0, fd);
308
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
309
/* Check again, to ensure that the kernel recorded the response */
310
EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
311
312
leak(fd);
313
}
314
315
/*
316
* For servers using older protocol versions, no FUSE_LSEEK should be attempted
317
*/
318
TEST_F(LseekPathconf_7_23, already_enosys)
319
{
320
const char FULLPATH[] = "mountpoint/some_file.txt";
321
const char RELPATH[] = "some_file.txt";
322
const uint64_t ino = 42;
323
off_t fsize = 1 << 30; /* 1 GiB */
324
int fd;
325
326
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
327
expect_open(ino, 0, 1);
328
EXPECT_CALL(*m_mock, process(
329
ResultOf([=](auto in) {
330
return (in.header.opcode == FUSE_LSEEK);
331
}, Eq(true)),
332
_)
333
).Times(0);
334
335
fd = open(FULLPATH, O_RDONLY);
336
ASSERT_LE(0, fd);
337
EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
338
EXPECT_EQ(EINVAL, errno);
339
340
leak(fd);
341
}
342
343
TEST_F(LseekSeekData, ok)
344
{
345
const char FULLPATH[] = "mountpoint/some_file.txt";
346
const char RELPATH[] = "some_file.txt";
347
const uint64_t ino = 42;
348
off_t fsize = 1 << 30; /* 1 GiB */
349
off_t offset_in = 1 << 28;
350
off_t offset_out = 1 << 29;
351
int fd;
352
353
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
354
expect_open(ino, 0, 1);
355
EXPECT_CALL(*m_mock, process(
356
ResultOf([=](auto in) {
357
return (in.header.opcode == FUSE_LSEEK &&
358
in.header.nodeid == ino &&
359
in.body.lseek.fh == FH &&
360
(off_t)in.body.lseek.offset == offset_in &&
361
in.body.lseek.whence == SEEK_DATA);
362
}, Eq(true)),
363
_)
364
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
365
SET_OUT_HEADER_LEN(out, lseek);
366
out.body.lseek.offset = offset_out;
367
})));
368
fd = open(FULLPATH, O_RDONLY);
369
EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_DATA));
370
EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
371
372
leak(fd);
373
}
374
375
/*
376
* If the server returns ENOSYS, fusefs should fall back to the default
377
* behavior, and never query the server again.
378
*/
379
TEST_F(LseekSeekData, enosys)
380
{
381
const char FULLPATH[] = "mountpoint/some_file.txt";
382
const char RELPATH[] = "some_file.txt";
383
const uint64_t ino = 42;
384
off_t fsize = 1 << 30; /* 1 GiB */
385
off_t offset_in = 1 << 28;
386
int fd;
387
388
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
389
expect_open(ino, 0, 1);
390
EXPECT_CALL(*m_mock, process(
391
ResultOf([=](auto in) {
392
return (in.header.opcode == FUSE_LSEEK &&
393
in.header.nodeid == ino &&
394
in.body.lseek.fh == FH &&
395
(off_t)in.body.lseek.offset == offset_in &&
396
in.body.lseek.whence == SEEK_DATA);
397
}, Eq(true)),
398
_)
399
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
400
fd = open(FULLPATH, O_RDONLY);
401
ASSERT_LE(0, fd);
402
403
/*
404
* Default behavior: ENXIO if offset is < 0 or >= fsize, offset
405
* otherwise.
406
*/
407
EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
408
EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
409
EXPECT_EQ(ENXIO, errno);
410
EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
411
EXPECT_EQ(ENXIO, errno);
412
413
leak(fd);
414
}
415
416
TEST_F(LseekSeekHole, ok)
417
{
418
const char FULLPATH[] = "mountpoint/some_file.txt";
419
const char RELPATH[] = "some_file.txt";
420
const uint64_t ino = 42;
421
off_t fsize = 1 << 30; /* 1 GiB */
422
off_t offset_in = 1 << 28;
423
off_t offset_out = 1 << 29;
424
int fd;
425
426
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
427
expect_open(ino, 0, 1);
428
EXPECT_CALL(*m_mock, process(
429
ResultOf([=](auto in) {
430
return (in.header.opcode == FUSE_LSEEK &&
431
in.header.nodeid == ino &&
432
in.body.lseek.fh == FH &&
433
(off_t)in.body.lseek.offset == offset_in &&
434
in.body.lseek.whence == SEEK_HOLE);
435
}, Eq(true)),
436
_)
437
).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
438
SET_OUT_HEADER_LEN(out, lseek);
439
out.body.lseek.offset = offset_out;
440
})));
441
fd = open(FULLPATH, O_RDONLY);
442
ASSERT_LE(0, fd);
443
EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_HOLE));
444
EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
445
446
leak(fd);
447
}
448
449
/*
450
* If the server returns ENOSYS, fusefs should fall back to the default
451
* behavior, and never query the server again.
452
*/
453
TEST_F(LseekSeekHole, enosys)
454
{
455
const char FULLPATH[] = "mountpoint/some_file.txt";
456
const char RELPATH[] = "some_file.txt";
457
const uint64_t ino = 42;
458
off_t fsize = 1 << 30; /* 1 GiB */
459
off_t offset_in = 1 << 28;
460
int fd;
461
462
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
463
expect_open(ino, 0, 1);
464
EXPECT_CALL(*m_mock, process(
465
ResultOf([=](auto in) {
466
return (in.header.opcode == FUSE_LSEEK &&
467
in.header.nodeid == ino &&
468
in.body.lseek.fh == FH &&
469
(off_t)in.body.lseek.offset == offset_in &&
470
in.body.lseek.whence == SEEK_HOLE);
471
}, Eq(true)),
472
_)
473
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
474
fd = open(FULLPATH, O_RDONLY);
475
ASSERT_LE(0, fd);
476
477
/*
478
* Default behavior: ENXIO if offset is < 0 or >= fsize, fsize
479
* otherwise.
480
*/
481
EXPECT_EQ(fsize, lseek(fd, offset_in, SEEK_HOLE));
482
EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
483
EXPECT_EQ(ENXIO, errno);
484
EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
485
EXPECT_EQ(ENXIO, errno);
486
487
leak(fd);
488
}
489
490
/* lseek should return ENXIO when offset points to EOF */
491
TEST_F(LseekSeekHole, enxio)
492
{
493
const char FULLPATH[] = "mountpoint/some_file.txt";
494
const char RELPATH[] = "some_file.txt";
495
const uint64_t ino = 42;
496
off_t fsize = 1 << 30; /* 1 GiB */
497
off_t offset_in = fsize;
498
int fd;
499
500
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
501
expect_open(ino, 0, 1);
502
EXPECT_CALL(*m_mock, process(
503
ResultOf([=](auto in) {
504
return (in.header.opcode == FUSE_LSEEK &&
505
in.header.nodeid == ino &&
506
in.body.lseek.fh == FH &&
507
(off_t)in.body.lseek.offset == offset_in &&
508
in.body.lseek.whence == SEEK_HOLE);
509
}, Eq(true)),
510
_)
511
).WillOnce(Invoke(ReturnErrno(ENXIO)));
512
fd = open(FULLPATH, O_RDONLY);
513
ASSERT_LE(0, fd);
514
EXPECT_EQ(-1, lseek(fd, offset_in, SEEK_HOLE));
515
EXPECT_EQ(ENXIO, errno);
516
517
leak(fd);
518
}
519
520